I've shared the failures, now it's time for the successes. We learned a lot in that first week the application was in production, and we spent long days and nights to get it back in a stable state (with multiple deployments). This is what runs in production now, and what I refer to as being version 2.
Technologies & Components
Azure Cosmos DB
We decided to replace the SharePoint list that contained the requests for a Teams team with an Azure Cosmos DB. The instability we experienced with the SharePoint connector for Logic Apps was the main reason to make a switch, we chose Cosmos DB because it was the only other SaaS database in Azure that did offer a connector.
Cosmos DB offers an out of the box connection with Azure Search. While unexpected, this was highly valued as it allowed us improve the My Requests user interface without too much effort.
We kept SharePoint for the configuration values, as it's the easiest UI everybody already knows.
Microsoft Graph wasn't giving us neither the speed nor the functionality to implement a highly performant search engine, with full text search and refiners.
We switched it out with an Azure Search instance, having two indexes:
- One with the requests, fed by the Cosmos DB
- One with the existing Teams teams, their default metadata and the additional metadata we stored in the schema extension
The speed and responsiveness of Azure Search, together with the very rich REST API blew me away at first sight. This offer in Azure is what saved the core functionality of our application, without it, we would never have been able to turn the application into a success.
Azure Search is the reason why I mentioned in the previous post that I now would go with Open Extensions instead of Schema Extensions. We chose the latter for the filtering capabilities, but that is now handled by Azure Search. Open Extensions would have provided us with better multi-value support for custom properties.
ASP.NET Core Web Application with React.js
We liked the UI of our application, it looked like it belonged right inside Microsoft Teams with the matching styling and colors. Our users liked it too. We didn't like the logic behind it, so we made very significant changes to the point that possibly everything had been changed except for authentication and rendering.
Using localStorage might not have been our best move, but we still needed a client side caching mechanism for some things that don't change often (like Teams pictures and configuration values). So we switched to using IndexedDB with the localforage npm package.
We switched out the whole Discover Teams page with a search based on Azure Search, and the same happened with the My Requests overview page.
The Request Form used to submit the data to an Azure Function which would then put the data into a SharePoint list, and then the Azure Logic App would start the provisioning. This process got replaced as well: submission now happened directly to the Logic App (HTTP trigger) which would then save it into the Azure Cosmos DB.
Azure Logic Apps
The Logic App responsible for the provisioning process got a complete do over as well. As said previously, we switched it from being SharePoint triggered to HTTP triggered.
The most important change was making sure that a failed Logic App could be resubmitted. This resubmit process available out of the box but you need to design your workflow for it. In our case, we had to put in checks before every action: Does the Team already exist, yes or no? Are the channels already added, yes or no? etc. The workflow got a lot more complex compared to the previous version, but it was worth the rework as it reduced calls to second line support. In most cases, a resubmit done by first line would now solve the issue.
In line with the rest of the application, we also skipped the Azure Function here and went directly to the Microsoft Graph. Since most of the API's required delegated access, we created a custom connector to all the necessary actions in Microsoft Graph.
We used a service account to make the connection and the connector could then be used in our Logic App. Over time, most of these API's added support for application permissions so we ended up using the HTTP action for them. This HTTP action allows making calls with client id and client secret as an authentication mechanism.
In v1 we first created the Office 365 Group, then activated the Microsoft Teams on it and then created the necessary channels. This wasn't very stable, in v2 we switched to using Clone Team. For each type of team there was a "template"-Team that we cloned in the Logic App. This ended up being much more reliable, eliminating most of the failures that occurred during creation of Teams teams.
This is where we mostly eliminated code, as in v1 we mimicked the endpoints of Microsoft Graph but in v2 all components accessed the Graph directly. A lot of work became obsolete.
The Azure Function now served only one (new) purpose anymore: making sure the Azure Search index was up to date. Microsoft Graph does not have an automatic connection with Azure Search, so the Azure Function became responsible for this.
Every night at 2am, the function would recreate the Azure Search index using the Azure Search .NET SDK. It would read the complete list of Microsoft Teams teams and their additional metadata from the Microsoft Graph, and insert it in the Azure Search index.
Every 10 minutes, another function would use the delta queries to read the last changes that happened to Office 365 Groups and update the search index.
It took two takes to get this right, and even now there are some things I would do differently if I had to start over. Some of them because of progressive insight, others because of newly released functionalities. We'll touch on this in the last post of the series.
Overall, after finishing v2, we were proud of the results and we received a lot of praise from the users. So we can consider this a success!
Only thing left to discuss, in the next post, the follow-up reporting and administration.
This post is part of the series Automate governance in Microsoft Teams: