Decouple backend processing from a frontend host, where backend processing needs to be asynchronous, but the frontend still needs a clear response.
For more information about this pattern, see Asynchronous Request-Reply pattern on the Azure Architecture Center.
The implementation uses a managed identity to control access to your storage accounts and Service Bus in the code, which is highly recommended wherever possible as a security best practice.
The reference implementation was uses the Azure Functions Flex Consumption. Flex Consumption is a Linux-based serverless hosting plan that extends the Consumption billing model with additional enterprise capabilities: private networking (virtual network integration), selectable instance sizes, and faster/larger scale-out over other plans.
The typical way to generate a SAS token in code requires the storage account key. In this scenario, you won't have a storage account key, as that feature is distable in this implementation, so you'll need to find another way to generate the shared access signatures. To do that, we need to use an approach called user delegation SAS. By using a user delegation SAS, we can sign the signature with the Microsoft Entra ID credentials instead of the storage account key.
-
Clone or download this repo.
-
Navigate to the
async-request-replyfolder.cd async-request-reply -
Azure Login
Our journey begins with logging into Azure. Use the command below:
az login # Set the subscription az account set --subscription <subscription_id>
-
Environment Setup
We need to prepare our environment:
LOCATION=eastus RESOURCEGROUP=rg-asyncrequestreply-${LOCATION} -
Create a resource group.
az group create --name ${RESOURCEGROUP} --location ${LOCATION}
-
Deploy the template. All the resources are going to be created on the resource group location.
az deployment group create -g ${RESOURCEGROUP} -f deploy.bicep -
Wait for the deployment to complete.
-
Navigate to the
async-request-reply/srcfolder.cd src -
Deploy the app.
FUNC_APP_NAME=$(az deployment group show -g ${RESOURCEGROUP} -n deploy --query properties.outputs.functionAppName.value -o tsv) func azure functionapp publish $FUNC_APP_NAME --dotnetIsolated
-
Send an http request through the Async Processor Work Acceptor
curl -X POST "https://${FUNC_APP_NAME}.azurewebsites.net/api/asyncprocessingworkacceptor" --header 'Content-Type: application/json' --header 'Accept: application/json' -k -i -d '{ "id": "1234", "customername": "Contoso" }'
The response will be something like:
HTTP/1.1 202 Accepted Content-Length: 155 Content-Type: application/json; charset=utf-8 Date: Wed, 13 Dec 2023 20:18:55 GMT Location: https://<appservice-name>.azurewebsites.net/api/RequestStatus/<guid>
Using a browser open the url from the Location field in the response. A file with the data you have sent will be downloaded.
Note the app uses the WEBSITE_HOSTNAME environment variable. This environment variable is set automatically by the Azure App Service runtime environment. For more information, see Azure runtime environment
Most of the Azure resources deployed in the prior steps will incur ongoing charges unless removed.
az group delete -n ${RESOURCEGROUP} -yYou could open the solution with Visual Studio, then you need to create on the root local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"ServiceBusConnection__fullyQualifiedNamespace": "<yourData>",
"DataStorage__blobServiceUri": "<yourData>"
}
}As far the implementation is using managed identity, you need to assign the role to your developer identity.
You need to add the following lines to the Bicep file to assign roles for Service Bus and Azure Storage permissions.
// Assign Role to allow sending messages to the Service Bus
resource serviceBusSenderRoleAssignmentUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, functionApp.id, 'LocalUser', 'ServiceBusSenderRole')
scope: serviceBusNamespace
properties: {
roleDefinitionId: senderServiceBusRole
principalId: <your user object id>
principalType: 'User'
}
}
// Assign Role to allow receiving messages from the Service Bus
resource serviceBusReceiverRoleAssignmentUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, functionApp.id, 'LocalUser', 'ServiceBusReceiverRole')
scope: serviceBusNamespace
properties: {
roleDefinitionId: receiverServiceBusRole
principalId: <your user object id>
principalType: 'User'
}
}
// Assign Role to allow Read, write, and delete Azure Storage containers and blobs.
resource dataStorageBlobDataContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, dataStorageAccount.id, 'LocalUser', 'StorageBlobDataContributorRole')
scope: dataStorageAccount
properties: {
roleDefinitionId: storageBlobDataContributorRole
principalId: <your user object id>
principalType: 'User'
}
}
Please see our Contributor guide.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
With ❤️ from Azure Patterns & Practices, Azure Architecture Center.
