Design, develop and deploy a serverless low-code solution in Azure
Published Jun 08 2022 01:03 PM 2,568 Views
Microsoft

Introduction

 

Imagine that you are part of an organization where your colleagues are either dispersed across a region or across the globe.

PairUp is an automation solution deployed in Azure that will pair Azure FastTrack colleagues together to catch up and expand their network. As the system is completely automated there are several must haves to make this an efficient process that's able to scale as more people are added to an organization but can also be extended to other regions.

 

High level summary of the system capabilities

 

Here's a short summary of what PairUp does:

  • Runs monthly, so it's possible to include new hires that could otherwise be missed
  • Sends a personalized registration email to organization users. You only need to select the top level leader.
  • After registrations are closed, a matching algorithm process will take place, with the following restraints:
    • A person must not be matched with their direct manager, as 1:1's between them should already be in place
    • A person must not be matched with another person they were matched with during a previous month/round.
    • A person must not be matched with a direct team member
  • Uses an exception list in case there are people without a match due to the above costraints
  • Sends an email to the matched pairs, where the email will trigger a meeting invite to be scheduled, via Cortana scheduler.
  • The "Pairs" meet up, have a chat, and find out more about each other
  • Provides mechanism to request feedback from the matched pairs on their experience

 

Key design principles

 

As a team we came up with the following design principles:

  • Embrace Azure PaaS offerings and DevOps automations, for the system to be maintainable
  • Carefully select Azure services based on cost, value, and performance evaluation
  • Ready to pass strict security and compliance reviews

 

Architectural components used

 

The whole solution was developed in Azure and for that purpose we used various components, as you can see in the following figure:

 

ormeikopmsft_0-1652730432579.png

Figure 1 - Architecture Components

 

Regarding the compute components, the orchestration of the whole business logic was implemented using logic apps, to keep the coding part of the solution to a minimum and follow a K.I.S.S. approach. We also used parameterized options to be able to scale to other regions and different organizations and we tried to break complex steps to multiple logic apps for maintainability. We just kept the complex pairing logic algorithm inside an Azure Function. Logic apps are using various connectors to integrate with other Azure components, like:

  • Microsoft Graph API connector, for fetching the direct reports of the top-level manager, to generate recursively the user base that will participate in the PairUp cycle. 
  • Office365 Outlook connector, for sending the registration email to all organization members, fetched from Microsoft Graph
  • Table Storage connector, for CRUD operations regarding the state of the solution

For monitoring purposes, we used Azure Monitor and in particular Application Insights, especially for gathering logs from the executions of the pair matching algorithm, through Azure Functions.

The solution was deployed into three different environments: Sandpit, UAT and Production. In the Sandpit environment, we deployed everything manually through the Azure portal and we initially developed and tested our solution there. After having a stable solution, we then used the UAT and Production environments to deploy the finalized components. This was done through Azure DevOps Server (ADO) pipelines, that were written in yaml files and through leveraging custom JSON Azure Resource Manager (ARM) templates, to fully comply and follow Continuous Integration / Continuous Deployment (CI/CD) and Infrastructure as Code (IaC) principles. All those files are kept into GIT source control in ADO.

 

Resources naming conventions

 

Based on the Cloud Adoption Framework (CAF) naming convention guidelines, the names of the resources follow the below rule:

  • pu-{domain}-{environment}-{resourceName} which was inspired from the following naming convention p<air>u<p>-e<mea>-u<at>-<resource name>. For example our EMEA based log analytics account for the UAT environment is named pu-e-u-log-analytics.

Logic apps include a step number which will go from 000, 010, 020 etc, to allow us to interleave missing steps.

  • pu-{domain}-{environment}-step{XXX}-{resourceName}
    • p<air>u<p>-e<mea>-u<at>-s<tep>###-<name>
    • for example, pu-e-u-s000-populate-user-tables

Storage or any other resource that doesn't support - in the name, we will skip the ‘-‘ characters, for example, our UAT storage is called pueustg01.

 

ormeikopmsft_1-1652730432591.png

Figure 2 - Azure Resources

 

Database design

For storing the state of our solution, we used the Table Storage Service of a newly provisioned Azure Storage Account, and we defined the following “tables”:

  • Users: The Azure FastTrack organization users in scope for the PairUp round, along with managers and team members. All the information here is being taken from a REST API call to the Microsoft Graph API
    • PartitionKey: “latest” (For now we store only the latest version of the users, but we could easily keep older versions of the org tree if needed)
    • RowKey: Participant principal name (email address)
    • It also includes details like the user’s name, job title and direct manager’s principal name.
  • Rounds: Table to keep the list of rounds executed so far
    • PartitionKey: Year of the round (for example, 2022)
    • RowKey: In the format Round-{Year}-{Month}-{Day} that is sortable (for example, “Round-2022-01-04)
    • It also includes details like whether feedback or meetings for this round have been sent to the round participants
  • RoundRegistrations: The list of organization users that have registered for every PairUp round
    • PartitionKey: The Round-{Year}-{Month}-{Day} key that uniquely identifies the round.
    • RowKey: Participant’s principal name (email address)
    • It also includes the date the organization user accepted their registration for this round
  • Pairings: The list of pairings that have been computed for a particular round
    • PartitionKey:  The Round-{Year}-{Month}-{Day} key that uniquely identifies the round.
    • RowKey: Concat of pairing participant’s aliases, in the form {alias1}-{alias2}, ordered alphabetically (so alias1 always comes before alias2).
    • It also includes details like the pairing participant’s email addresses, names, the date they were matched and their pairing score
  • PairingVolunteers: A backup list of members used for matching whenever we cannot match someone with another person (for example, odd number of PairUp round participants or someone is a manager to all the members that have signed up for this PairUp round)
    • PartitionKey: “latest” (For now we store only the latest version of the users, but we could easily keep older versions if needed)
    • RowKey: The principal name (email address) of the PairUp volunteer
    • It also includes details like what was the last time this person volunteered and the number of times the person has volunteered in the past

 

High Level Solution Architecture

 

The workflow of the PairUp at a very high level contains four main steps as you can see in the image below.

 

ormeikopmsft_2-1652730432608.png

Figure 3 - PairUp Workflow

We will discuss each step in the following sections.

 

Populate Users

 

The first step is to populate the Users table, which will be our user base for the PairUp rounds. We need to send out emails to all potential participants, but to do that we need to know who they are. To do this, a Logic App called pu-e-u-s000-populate-user-table runs, deletes all previous entries in the Users table, and eventually uses the Graph API connector to do a REST API call to list everyone who is a direct report of a top-level manager in the organization. The manager alias is a parameter that is passed into the Logic App. The Graph API call returns the necessary user information list and from that we are populating our Users list once again using a recursion mechanism. This step is an asynchronous one and happens once every week, to be able to account for people who join or leave the organization. You can see a detailed view of the workflow followed in the image below.

 

ormeikopmsft_3-1652730432617.png

Figure 4 - pu-e-u-s000-populate-user-table & pu-e-u-s000-populate-user-table-recursion-XX logic apps flow

 

The pu-e-u-s000-populate-user-table logic app relies to a couple of other logic apps:

  • pu-e-u-s000-populate-user-table-recursion-01 and
  • pu-e-u-s000-populate-user-table-recursion-02

These two logic apps are HTTP trigger based (they are modelled through the “Process User” part seen in Figure 4) and essentially have the responsibility of traversing the list of direct reports recursively, starting with the top-level manager of the organization. In our implementation the code base for these logic apps is identical (deploy the same ARM template for both Azure resources).

 

User registration

 

Now that we have all the user details from the organization we want to target, the next step is to provide a registration process to sign up for the next PairUp round. A Logic App called pu-e-u-s010-initialize-pairing-round, starts at 8:00 (UTC+00:00) on Monday every 4 weeks and firstly initializes the next pairing round and saves it in the Rounds list. It then reads all users saved inside the Users list from the “Populate users” step, and for each of these users, it calls an HTTP trigger-based logic app called pu-e-u-s010-request-single-user-to-enroll. This logic app has the responsibility to fetch the details of the user from the “Users” list and sends out an adaptive card.

 

ormeikopmsft_4-1652730432623.png

Figure 5 - pu-e-u-s010-initialize-pairing-round & pu-e-u-s010-request-single-user-to-enroll logic apps flow

 

The card contains a button embedded. When the user clicks it, then he / she is being automatically added to the RoundRegistrations list and is an active registrant of the current pairing round.

 

ormeikopmsft_5-1652730432627.png

Figure 6 - User registers for this pairing round

 

The result of the above steps is a registration email sent to each user in the organization. The flow expires after 3 days, which means that users won't be able to register after this deadline.

 

Pairing users

 

With the users that want to participate for the current round having already registered, it's time for PairUp to match them together. To do that though we need to make sure the following constraints are being met:

  • A participant must not be matched with their manager
  • A participant must not be matched with a person they were matched in the previous X waves, where X is variable and can be configured.
  • A participant must not be matched with a direct team member

The above pairing algorithm logic is implemented in an Azure Functions App and is triggered by another logic app called pu-e-u-s020-compute-pairs. This logic app runs at 10:00 AM (UTC+00:00) on Thursday every 4 weeks. This is 3 days and 2 hours after the user registration flow has been triggered, which means that participants have 3 days in total to register for the current round. The Function App does the following things:

  • Fetches the following information to be used as input
    • All available organization users from Users list
    • Registered users for the current round from RoundRegistrations list
    • Previous computed pairings from Pairings list
  • Uses the above input, applies the abovementioned constraints, computes the pairings for the current round and saves them to the Pairings list
  • Assign the unpaired users (for example, if we had an odd number of participants) with some of the users that are registered inside the PairingVolunteers list

In a high level, the actual pairing algorithm works as follows:

  • Gets a list of people that need to be paired
  • For each person
    • Detect how many options this person has (no immediate manager, no previous matched person, no team member) and calculate the pairing scores the person has with the other participants
      • If the participant is the person’s immediate manager, then the score assigned for pairing equals to -999
      • If the participant has matched with the person in a previous round, then the score assigned for pairing will equal to -2 * (number of previous meetups)
      • If the participant is in the same team as the person, then the score assigned will equal to -1
    • Stack rank people based on the list of available options
    • Pair folks going down
    • If left over match with a volunteering list member

 

ormeikopmsft_6-1652730432636.png

Figure 7 - Select user to pair

 

Scheduling User Meetings

 

After the Function App logic has finished its job, the pu-e-u-s020-compute-pairs logic app continues its execution by triggering another HTTP based logic app called pu-e-u-s020-schedule-meetings, that orchestrates the meeting invitation scheduling process.

The scheduling Logic App reads through the Pairings list, and for each pairing, it sends out an email invitation template to both users, with Cortana in CC. The body of the email template asks Cortana to automatically schedule a call at a mutual time within the next week.

 

ormeikopmsft_7-1652730432641.png

Figure 8 - Send meeting invitations

 

The result of the above steps is the generation of two different emails. The first one is of the pairing email that is being sent to both pairing participants, with Cortana in CC. The second one is the actual meeting invitation sent by Cortana Scheduler to both paired persons.

 

Feedback

 

The last part of the PairUp process is the feedback request from all participants in the current round. This is done by, yet another logic app called pu-e-u-s030-request-user-feedback. This is also a time triggered one and is being run at 8:00 AM (UTC+00:00) on Monday every 4 weeks.

Whenever it gets triggered, it first calls another function app called GetFeedbackRound, which returns from the Rounds list, the current PairUp round, where no feedback emails are yet sent to the round participants. The logic app then fetches from the Pairings list the total pairings for the current round and for each pairing it sends the actual feedback email to each pair participant.

 

ormeikopmsft_8-1652730432651.png

Figure 9 - Feedback round

 

Application Lifecycle Management & DevOps

 

As far as ALM and DevOps are concerned, the whole infrastructure and source code builds and deployments for the PairUp system are being processed in Azure DevOps (ADO) inside a dedicated Project called PairUp. Using the principles of CI/CD and IaC, the solution is fully automated and uses the following dedicated pipelines:

  • PairUp-Infrastructure
    • The pipeline for the entire environment provisioning in PairUp’s resource group in Azure.
  • PairUp-LogicApps
    • Deploys the “business logic” that all our logic apps contain
  • PairUp-FunctionApp
    • Builds the source code of the pairing algorithm contained in the Function App project, runs any tests, and deploys the produced artifact in Azure Functions App running in the cloud

 

ormeikopmsft_9-1652730432660.png

Figure 10 - Azure DevOps Pipelines

 

All above pipelines are implemented using yaml files that are being kept inside source control inside a dedicated folder called “devops”, along with the source code (“src” folder), tests (“tests” folder) and documentation (“wiki” folder) of the entire solution.

 

ormeikopmsft_10-1652730432663.png

Figure 11 – Project Folder Structure

 

Infrastructure

 

The infrastructure of the solution is being developed using ARM template JSON files, that are used by the PairUp-Infrastructure pipeline, to deploy everything needed as Azure resources, to run the whole system.

 

ormeikopmsft_11-1652730432672.png

Figure 12 - Infrastructure ARM template json files

 

To provision the infrastructure, we use the PairUp-Infrastructure pipeline, which runs automatically after every commit that is merged in the main branch, only for the changes in the paths that you can see in the following figure:

 

ormeikopmsft_12-1652730432673.png

Figure 13 - PairUp Infrastructure Pipeline Trigger Paths

 

The pipeline, in general, implements the steps below in three separate stages:

  • Copies all json, ps1 and sh files from the devops/infrastructure source folder and publishes a build artifact
  • Provisions of the infrastructure in the UAT environment
  • Provisions of the infrastructure in the Production environment

The provisioning of the infrastructure in the UAT environment is being done automatically. On the other hand, in the Production environment there is a manual approval mechanism set, that blocks the deployment, until a member of the admin security group reviews and explicitly approves it.

 

ormeikopmsft_13-1652730432675.png

Figure 14 - PairUp Infrastructure Pipeline Stages

 

Logic Apps

 

The “business logic” of our logic apps and things like triggers, actions and outputs are being kept also in JSON files and stored in source control under src/logic-apps folder. These JSON files are being used by the PairUp-LogicApps ADO pipeline, which deploys the app code in the logic apps provisioned in the resource group from the infrastructure pipeline.

To deploy the code, we use the PairUp-LogicApps pipeline, which runs automatically after every commit that is merged in the main branch, only for the changes in the paths that you can see in the following figure:

 

ormeikopmsft_14-1652730432677.png

Figure 15 - PairUp Logic Apps Pipeline Trigger Paths

 

The pipeline, in general, implements the steps below in three separate stages:

  • Copies all json, and html files from the src/logic-apps source folder and publishes a build artifact
  • Deploys the logic apps code in the UAT environment
  • Deploys the logic apps code in the Production environment

 

ormeikopmsft_15-1652730432679.png

Figure 16 - PairUp Logic Apps Pipeline Stages

 

Inside the src/logic-apps/html-templates folder, we can also find the HTML templates that are being used to compose the various email bodies that the PairUp workflow sends, like registration, meeting schedules and feedback requests.

For the templates we only read the internal HTML, after the `<!-- START TEMPLATE -->` comment up to the `<!-- END TEMPLATE -->` comment.

To extract the HTML, we use the following command:

 

ormeikopmsft_16-1652730432680.png

Figure 17 - Command to extract the HTML from a template

 

Function Apps

 

The function app contains the logic of the pairing algorithm for the PairUp workflow, the source code of which can be found inside the src/function-apps folder.

To deploy the code, we use the PairUp-FunctionApp pipeline, which runs automatically after every commit that is merged in the main branch, only for the changes in the paths that you can see in the following figure:

 

ormeikopmsft_17-1652730432682.png

Figure 18 - PairUp Function Apps Pipeline Trigger Paths

 

The pipeline, in general, implements the steps below in three separate stages:

  • Builds function apps source code, runs unit / integration tests and publishes necessary artifacts
  • Deploys the function apps code in the UAT environment
  • Deploys the function apps code in the Production environment

 

ormeikopmsft_18-1652730432684.png

Figure 19 - PairUp Function Apps Pipeline Stages

 

In all cases, the deployment in the UAT environment is being done automatically. On the other hand, in the Production environment there is a manual approval mechanism set, that blocks the deployment, until a member of the admin security group reviews and explicitly approves it.

Version history
Last update:
‎Jun 08 2022 06:03 AM
Updated by: