From 7388050e78c77e5006ce44e9c5e0bf026a68ce9e Mon Sep 17 00:00:00 2001 From: Federico Arambarri Date: Thu, 16 Oct 2025 11:54:42 -0300 Subject: [PATCH 1/6] Some updates and rewording --- .../PriorityQueueConsumerHigh.csproj | 8 ++--- .../local.settings.template.json | 2 +- .../PriorityQueueConsumerLow.csproj | 8 ++--- .../local.settings.template.json | 2 +- .../PriorityQueueSender.csproj | 11 +++--- .../local.settings.template.json | 2 +- priority-queue/Readme.md | 36 +++++++++---------- 7 files changed, 36 insertions(+), 33 deletions(-) diff --git a/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHigh.csproj b/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHigh.csproj index 0eef047b..8ff73c13 100644 --- a/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHigh.csproj +++ b/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHigh.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 v4 Exe enabled @@ -15,8 +15,8 @@ - - - + + + \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerHigh/local.settings.template.json b/priority-queue/PriorityQueueConsumerHigh/local.settings.template.json index 8cbbeb91..643657ce 100644 --- a/priority-queue/PriorityQueueConsumerHigh/local.settings.template.json +++ b/priority-queue/PriorityQueueConsumerHigh/local.settings.template.json @@ -3,6 +3,6 @@ "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", - "ServiceBusConnection__fullyQualifiedNamespace": "{SERVICE_BUS_CONNECTION}" + "ServiceBusConnection__fullyQualifiedNamespace": "{SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE}" } } \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLow.csproj b/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLow.csproj index 0eef047b..8ff73c13 100644 --- a/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLow.csproj +++ b/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLow.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 v4 Exe enabled @@ -15,8 +15,8 @@ - - - + + + \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerLow/local.settings.template.json b/priority-queue/PriorityQueueConsumerLow/local.settings.template.json index 8cbbeb91..643657ce 100644 --- a/priority-queue/PriorityQueueConsumerLow/local.settings.template.json +++ b/priority-queue/PriorityQueueConsumerLow/local.settings.template.json @@ -3,6 +3,6 @@ "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", - "ServiceBusConnection__fullyQualifiedNamespace": "{SERVICE_BUS_CONNECTION}" + "ServiceBusConnection__fullyQualifiedNamespace": "{SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE}" } } \ No newline at end of file diff --git a/priority-queue/PriorityQueueSender/PriorityQueueSender.csproj b/priority-queue/PriorityQueueSender/PriorityQueueSender.csproj index 81ebc955..29f8cb92 100644 --- a/priority-queue/PriorityQueueSender/PriorityQueueSender.csproj +++ b/priority-queue/PriorityQueueSender/PriorityQueueSender.csproj @@ -1,15 +1,18 @@  - net8.0 + net9.0 v4 Exe enabled enable - - - + + + + + + diff --git a/priority-queue/PriorityQueueSender/local.settings.template.json b/priority-queue/PriorityQueueSender/local.settings.template.json index 8cbbeb91..643657ce 100644 --- a/priority-queue/PriorityQueueSender/local.settings.template.json +++ b/priority-queue/PriorityQueueSender/local.settings.template.json @@ -3,6 +3,6 @@ "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", - "ServiceBusConnection__fullyQualifiedNamespace": "{SERVICE_BUS_CONNECTION}" + "ServiceBusConnection__fullyQualifiedNamespace": "{SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE}" } } \ No newline at end of file diff --git a/priority-queue/Readme.md b/priority-queue/Readme.md index e7c9ceaa..179be30d 100644 --- a/priority-queue/Readme.md +++ b/priority-queue/Readme.md @@ -2,9 +2,9 @@ This directory contains an example of the [Priority Queue pattern](https://learn.microsoft.com/azure/architecture/patterns/priority-queue). -This example demonstrates how priority queues can be implemented by using Service Bus topics and subscriptions. A time-triggered Azure Function is responsible for sending messages to a topic, each with a priority assigned. The receiving Azure Functions read messages from subscriptions that have the corresponding priority. In this example, the _PriorityQueueConsumerHigh_ Azure function can scale out to 200 instances, while the _PriorityQueueConsumerLow_ function runs only with one instance. This example simulates high priority messages being read from the queue more urgently than low priority messages. +This example demonstrates how priority queues can be implemented by using Service Bus topics and subscriptions. A time-triggered Azure Function is responsible for sending messages to a topic, each with a priority assigned. The receiving Azure Functions read messages from subscriptions that have the corresponding priority. In this example, the _PriorityQueueConsumerHigh_ Azure function can scale out to 200 instances, while the _PriorityQueueConsumerLow_ Azure function runs only with one instance. This example simulates high priority messages being read from the queue more urgently than low priority messages. -This example also demonstrates operational aspects of applications running on Azure. Monitoring tools need to be used in order to understand how the sample works. Azure Functions in the solution **must** be configured to use the diagnostics mechanism. If not, you will not see the trace information generated by the example. +This example also demonstrates operational aspects of applications running on Azure. Monitoring tools are essential to understand how the sample operates. Azure Functions in the solution **must** be configured to use the diagnostics mechanism. Otherwise, trace information generated by the example will not be visible. ## :rocket: Deployment guide @@ -15,7 +15,7 @@ Install the prerequisites and follow the steps to deploy and run an example of t - Permission to create a new resource group and resources in an [Azure subscription](https://azure.com/free). - [Git](https://git-scm.com/downloads) - [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) -- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) +- [.NET 9 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) - [Azure Functions Core Tools v4.x](https://learn.microsoft.com/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools) ### Steps @@ -49,7 +49,7 @@ Install the prerequisites and follow the steps to deploy and run an example of t CURRENT_USER_OBJECT_ID=$(az ad signed-in-user show -o tsv --query id) SERVICE_BUS_NAMESPACE_NAME="sbns-priority-queue-$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | fold -w 7 | head -n 1)" - # This takes about two minute + # This takes about two minutes az deployment group create -n deploy-priority-queue -f bicep/main.bicep -g "${RESOURCE_GROUP_NAME}" -p queueNamespaces=$SERVICE_BUS_NAMESPACE_NAME principalId=$CURRENT_USER_OBJECT_ID ``` @@ -57,55 +57,55 @@ Install the prerequisites and follow the steps to deploy and run an example of t ```bash # Retrieve the primary connection string for the Service Bus namespace. - SERVICE_BUS_CONNECTION="${SERVICE_BUS_NAMESPACE_NAME}.servicebus.windows.net" + SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE="${SERVICE_BUS_NAMESPACE_NAME}.servicebus.windows.net" - sed "s|{SERVICE_BUS_CONNECTION}|${SERVICE_BUS_CONNECTION}|g" ./PriorityQueueSender/local.settings.template.json > ./PriorityQueueSender/local.settings.json - sed "s|{SERVICE_BUS_CONNECTION}|${SERVICE_BUS_CONNECTION}|g" ./PriorityQueueConsumerHigh/local.settings.template.json > ./PriorityQueueConsumerHigh/local.settings.json - sed "s|{SERVICE_BUS_CONNECTION}|${SERVICE_BUS_CONNECTION}|g" ./PriorityQueueConsumerLow/local.settings.template.json > ./PriorityQueueConsumerLow/local.settings.json + sed "s|{SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE}|${SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE}|g" ./PriorityQueueSender/local.settings.template.json > ./PriorityQueueSender/local.settings.json + sed "s|{SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE}|${SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE}|g" ./PriorityQueueConsumerHigh/local.settings.template.json > ./PriorityQueueConsumerHigh/local.settings.json + sed "s|{SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE}|${SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE}|g" ./PriorityQueueConsumerLow/local.settings.template.json > ./PriorityQueueConsumerLow/local.settings.json ``` 1. [Run Azurite](https://learn.microsoft.com/azure/storage/common/storage-use-azurite#run-azurite) blob storage emulation service. > The local storage emulator is required as an Azure Storage account is a required "backing resource" for Azure Functions. -1. Launch the Function PriorityQueueSender to generate Low and High messages. +1. Launch the Azure Function PriorityQueueSender to generate Low and High messages. ```bash cd ./PriorityQueueSender func start ``` -1. In a new terminal, launch the Function PriorityQueueConsumerLow to consume messages. +1. In a new terminal, launch the Azure Function PriorityQueueConsumerLow to consume messages. ```bash cd ./PriorityQueueConsumerLow func start -p 15000 ``` - > Please note: For demo purposes, the sample application will write content to the the screen. + > Please note: For demo purposes, the sample application will write content to the screen. -1. In a new terminal, launch the Function PriorityQueueConsumerHigh to consume messages. +1. In a new terminal, launch the Azure Function PriorityQueueConsumerHigh to consume messages. ```bash cd ./PriorityQueueConsumerHigh func start -p 15001 ``` - > Please note: For demo purposes, the sample application will write content to the the screen. + > Please note: For demo purposes, the sample application will write content to the screen. ## Deploy the example to Azure (Optional) To deploy the example to Azure, you need to publish each Azure Functions to Azure. You can do so from Visual Studio by right clicking each function and selecting `Publish` from the menu. Use the same resource group and region from earlier. Be sure to enable Application Insights. -Once each function is published, a new App Setting must be added to store the connection string to the Service Bus namespace. This is the same value that was used in the `SERVICE_BUS_CONNECTION_STRING` variable in the previous steps. +Once each Azure Function is published, a new App Setting must be added to store the Service Bus fully qualified namespace. This is the same value that was used in the `SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE` variable in the previous steps. The solution is using Managed Identity to access Service Bus. -For each function, run the following: +For each Azure Function, run the following: ```bash -az webapp config appsettings set -n -g $RESOURCE_GROUP_NAME --settings ServiceBusConnection__fullyQualifiedNamespace=$SERVICE_BUS_CONNECTION +az webapp config appsettings set -n -g $RESOURCE_GROUP_NAME --settings ServiceBusConnection__fullyQualifiedNamespace=$SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE ``` -Once the functions are deployed you need to restrict the maximum number of instances the `PriorityQueueConsumerLow` function can scale out to. +Once the Azure Functions are deployed you need to restrict the maximum number of instances the `PriorityQueueConsumerLow` function can scale out to. From the Azure portal: @@ -114,7 +114,7 @@ From the Azure portal: - On the App Scale Out dialog, set the `Enforce Scale Out Limit` to `Yes` - Set the `Maximum Scale Out Limit` to `1` instance -Once the functions are deployed you can visit Application Insights to view the most recent activity for each function. +Once the Azure Functions are deployed you can visit Application Insights to view the most recent activity for each function. ## :broom: Clean up resources From e3b38ae667676ee00bc22ff861b591a435eeb109 Mon Sep 17 00:00:00 2001 From: Federico Arambarri Date: Thu, 16 Oct 2025 15:24:56 -0300 Subject: [PATCH 2/6] Update API and Readme --- priority-queue/Readme.md | 3 ++- priority-queue/bicep/main.bicep | 31 ++++++++++++------------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/priority-queue/Readme.md b/priority-queue/Readme.md index 179be30d..3fefc66f 100644 --- a/priority-queue/Readme.md +++ b/priority-queue/Readme.md @@ -66,7 +66,8 @@ Install the prerequisites and follow the steps to deploy and run an example of t 1. [Run Azurite](https://learn.microsoft.com/azure/storage/common/storage-use-azurite#run-azurite) blob storage emulation service. - > The local storage emulator is required as an Azure Storage account is a required "backing resource" for Azure Functions. + > Azure Functions require an Azure Storage account as a backing resource. When running locally, you can use Azurite, the local storage emulator, to fulfill this requirement. +Alternatively, you may configure the AzureWebJobsStorage setting to use a real Azure Storage account if preferred. 1. Launch the Azure Function PriorityQueueSender to generate Low and High messages. diff --git a/priority-queue/bicep/main.bicep b/priority-queue/bicep/main.bicep index ba5d4b23..b8328b2c 100644 --- a/priority-queue/bicep/main.bicep +++ b/priority-queue/bicep/main.bicep @@ -9,7 +9,7 @@ param location string = resourceGroup().location param queueNamespaces string @minLength(36) -@description('The guid of the principal running the valet key generation code. In Azure this would be replaced with the managed identity of the Azure Function, when running locally it will be your user.') +@description('The principal ID used to run the Azure Functions. In Azure, this should be the managed identity (system-assigned or user-assigned) of the Azure Function. When running locally, it should be your user identity.') param principalId string var logAnalyticsName = 'loganalytics-${uniqueString(subscription().subscriptionId, resourceGroup().id)}' @@ -23,7 +23,7 @@ var receiverServiceBusRole = subscriptionResourceId( '4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0' ) // Azure Service Bus Data Receiver -resource queueNamespacesResource 'Microsoft.ServiceBus/namespaces@2023-01-01-preview' = { +resource queueNamespacesResource 'Microsoft.ServiceBus/namespaces@2025-05-01-preview' = { name: queueNamespaces location: location sku: { @@ -48,10 +48,9 @@ resource queueNamespacesResource 'Microsoft.ServiceBus/namespaces@2023-01-01-pre } } -resource queueNamespacesResourceRootManageSharedAccessKey 'Microsoft.ServiceBus/namespaces/authorizationrules@2023-01-01-preview' = { +resource queueNamespacesResourceRootManageSharedAccessKey 'Microsoft.ServiceBus/namespaces/authorizationrules@2025-05-01-preview' = { parent: queueNamespacesResource name: 'RootManageSharedAccessKey' - location: location properties: { rights: [ 'Listen' @@ -61,10 +60,9 @@ resource queueNamespacesResourceRootManageSharedAccessKey 'Microsoft.ServiceBus/ } } -resource queueNamespacesResourceNetworkRules 'Microsoft.ServiceBus/namespaces/networkrulesets@2023-01-01-preview' = { +resource queueNamespacesResourceNetworkRules 'Microsoft.ServiceBus/namespaces/networkrulesets@2025-05-01-preview' = { parent: queueNamespacesResource name: 'default' - location: location properties: { publicNetworkAccess: 'Enabled' defaultAction: 'Allow' @@ -74,10 +72,9 @@ resource queueNamespacesResourceNetworkRules 'Microsoft.ServiceBus/namespaces/ne } } -resource queueNamespacesResourceTopic 'Microsoft.ServiceBus/namespaces/topics@2023-01-01-preview' = { +resource queueNamespacesResourceTopic 'Microsoft.ServiceBus/namespaces/topics@2025-05-01-preview' = { parent: queueNamespacesResource name: 'messages' - location: location properties: { maxMessageSizeInKilobytes: 256 maxSizeInMegabytes: 1024 @@ -91,10 +88,9 @@ resource queueNamespacesResourceTopic 'Microsoft.ServiceBus/namespaces/topics@20 } } -resource queueNamespacesResourceTopicHigPriority 'Microsoft.ServiceBus/namespaces/topics/subscriptions@2023-01-01-preview' = { +resource queueNamespacesResourceTopicHigPriority 'Microsoft.ServiceBus/namespaces/topics/subscriptions@2025-05-01-preview' = { parent: queueNamespacesResourceTopic name: 'highPriority' - location: location properties: { isClientAffine: false lockDuration: 'PT1M' @@ -107,10 +103,9 @@ resource queueNamespacesResourceTopicHigPriority 'Microsoft.ServiceBus/namespace } } -resource queueNamespacesResourceTopicLowPriority 'Microsoft.ServiceBus/namespaces/topics/subscriptions@2023-01-01-preview' = { +resource queueNamespacesResourceTopicLowPriority 'Microsoft.ServiceBus/namespaces/topics/subscriptions@2025-05-01-preview' = { parent: queueNamespacesResourceTopic name: 'lowPriority' - location: location properties: { isClientAffine: false lockDuration: 'PT1M' @@ -123,10 +118,9 @@ resource queueNamespacesResourceTopicLowPriority 'Microsoft.ServiceBus/namespace } } -resource queueNamespacesResourceTopicHigPriorityRules 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules@2023-01-01-preview' = { +resource queueNamespacesResourceTopicHigPriorityRules 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules@2025-05-01-preview' = { parent: queueNamespacesResourceTopicHigPriority name: 'priorityFilter' - location: location properties: { action: {} filterType: 'SqlFilter' @@ -137,10 +131,9 @@ resource queueNamespacesResourceTopicHigPriorityRules 'Microsoft.ServiceBus/name } } -resource LqueueNamespacesResourceTopicLowPriorityRules 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules@2023-01-01-preview' = { +resource LqueueNamespacesResourceTopicLowPriorityRules 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules@2025-05-01-preview' = { parent: queueNamespacesResourceTopicLowPriority name: 'priorityFilter' - location: location properties: { action: {} filterType: 'SqlFilter' @@ -151,7 +144,7 @@ resource LqueueNamespacesResourceTopicLowPriorityRules 'Microsoft.ServiceBus/nam } } -resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2025-02-01' = { name: logAnalyticsName location: location properties: { @@ -200,7 +193,7 @@ resource serviceBusSenderRoleAssignment 'Microsoft.Authorization/roleAssignments properties: { roleDefinitionId: senderServiceBusRole principalId: principalId - principalType: 'User' // 'ServicePrincipal' if this was a managed identity + principalType: 'User' // 'ServicePrincipal' if this was App Service with a managed identity } } @@ -211,6 +204,6 @@ resource serviceBusReceiverRoleAssignment 'Microsoft.Authorization/roleAssignmen properties: { roleDefinitionId: receiverServiceBusRole principalId: principalId - principalType: 'User' // 'ServicePrincipal' if this was a managed identity + principalType: 'User' // 'ServicePrincipal' if this was App Service with a managed identity } } From b1d7190165fcf3d19a02f26469acf016690e39ae Mon Sep 17 00:00:00 2001 From: Federico Arambarri Date: Fri, 17 Oct 2025 11:28:49 -0300 Subject: [PATCH 3/6] Readme Improvement --- priority-queue/Readme.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/priority-queue/Readme.md b/priority-queue/Readme.md index 3fefc66f..444413cb 100644 --- a/priority-queue/Readme.md +++ b/priority-queue/Readme.md @@ -2,9 +2,13 @@ This directory contains an example of the [Priority Queue pattern](https://learn.microsoft.com/azure/architecture/patterns/priority-queue). -This example demonstrates how priority queues can be implemented by using Service Bus topics and subscriptions. A time-triggered Azure Function is responsible for sending messages to a topic, each with a priority assigned. The receiving Azure Functions read messages from subscriptions that have the corresponding priority. In this example, the _PriorityQueueConsumerHigh_ Azure function can scale out to 200 instances, while the _PriorityQueueConsumerLow_ Azure function runs only with one instance. This example simulates high priority messages being read from the queue more urgently than low priority messages. +This example demonstrates how priority queues can be implemented by using Service Bus topics and subscriptions. A time-triggered Azure Function is responsible for sending messages to a topic, each with a priority assigned. The receiving Azure Functions read messages from subscriptions that have the corresponding priority. -This example also demonstrates operational aspects of applications running on Azure. Monitoring tools are essential to understand how the sample operates. Azure Functions in the solution **must** be configured to use the diagnostics mechanism. Otherwise, trace information generated by the example will not be visible. +For local execution, the sample demonstrates the producer/consumer model, where each consumer processes only one type of message based on its priority. + +In the Azure Deployment, the _PriorityQueueConsumerHigh_ Azure function could scale out to 200 instances, while the _PriorityQueueConsumerLow_ Azure function runs only with one instance. It simulates high priority messages being read from the queue more urgently than low priority messages. + +The Azure deployment also demonstrates operational aspects of applications running on Azure. Monitoring tools are essential to understand how the sample operates. Azure Functions in the solution **must** be configured to use the diagnostics mechanism. Otherwise, trace information generated by the example will not be visible. ## :rocket: Deployment guide From 4e7e749105a34d73d82f4ca8fe0742ed42b2346e Mon Sep 17 00:00:00 2001 From: Federico Arambarri Date: Tue, 21 Oct 2025 13:57:04 -0300 Subject: [PATCH 4/6] Moving forward --- priority-queue/Readme.md | 40 +++++- .../bicep/azure/azure-function-app.bicep | 72 ++++++++++ priority-queue/bicep/azure/functionApp.bicep | 133 ++++++++++++++++++ 3 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 priority-queue/bicep/azure/azure-function-app.bicep create mode 100644 priority-queue/bicep/azure/functionApp.bicep diff --git a/priority-queue/Readme.md b/priority-queue/Readme.md index 444413cb..31cbf399 100644 --- a/priority-queue/Readme.md +++ b/priority-queue/Readme.md @@ -54,7 +54,7 @@ Install the prerequisites and follow the steps to deploy and run an example of t SERVICE_BUS_NAMESPACE_NAME="sbns-priority-queue-$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | fold -w 7 | head -n 1)" # This takes about two minutes - az deployment group create -n deploy-priority-queue -f bicep/main.bicep -g "${RESOURCE_GROUP_NAME}" -p queueNamespaces=$SERVICE_BUS_NAMESPACE_NAME principalId=$CURRENT_USER_OBJECT_ID + az deployment group create -n deploy-priority-queue -f bicep/main.bicep -g $RESOURCE_GROUP_NAME -p queueNamespaces=$SERVICE_BUS_NAMESPACE_NAME principalId=$CURRENT_USER_OBJECT_ID ``` 1. Configure the samples to use the created Azure resources. @@ -100,6 +100,44 @@ Alternatively, you may configure the AzureWebJobsStorage setting to use a real A ## Deploy the example to Azure (Optional) +```bash + # This takes about five minutes + az deployment group create -n deploy-priority-queue-sites -f bicep/azure/azure-function-app.bicep -g $RESOURCE_GROUP_NAME -p serviceBusNamespaceName=$SERVICE_BUS_NAMESPACE_NAME + + # Deploy using Azure Functions Core Tools + cd .\PriorityQueueSender\ + func azure functionapp publish funcPriorityQueueSender + cd .. + cd .\PriorityQueueConsumerLow\ + func azure functionapp publish funcPriorityQueueConsumerLow + cd .. + cd .\PriorityQueueConsumerHigh\ + func azure functionapp publish funcPriorityQueueConsumerHigh +``` + +``` + // Recent requests with key details + requests + | project timestamp, operation_Name, cloud_RoleName, id, success, resultCode, duration, operation_Id + | order by timestamp desc + + // Count of requests by operation name + requests + | summarize RequestCount = count() by operation_Name + | order by RequestCount desc + + // Traces filtered by keywords + traces + | where operation_Name contains "High" + + traces + | where operation_Name contains "Low" + + traces + | where operation_Name contains "Sender" +``` + + To deploy the example to Azure, you need to publish each Azure Functions to Azure. You can do so from Visual Studio by right clicking each function and selecting `Publish` from the menu. Use the same resource group and region from earlier. Be sure to enable Application Insights. Once each Azure Function is published, a new App Setting must be added to store the Service Bus fully qualified namespace. This is the same value that was used in the `SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE` variable in the previous steps. The solution is using Managed Identity to access Service Bus. diff --git a/priority-queue/bicep/azure/azure-function-app.bicep b/priority-queue/bicep/azure/azure-function-app.bicep new file mode 100644 index 00000000..dddb6153 --- /dev/null +++ b/priority-queue/bicep/azure/azure-function-app.bicep @@ -0,0 +1,72 @@ +param location string = resourceGroup().location +param storageAccountName string = 'st${uniqueString(resourceGroup().id)}' +param serviceBusNamespaceName string +param appInsightsName string = 'ai${uniqueString(resourceGroup().id)}' + +var senderRoleId = '69a216fc-b8fb-44d8-bc22-1f3c2cd27a39' +var receiverRoleId = '4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0' + +resource storageAccount 'Microsoft.Storage/storageAccounts@2025-01-01' = { + name: storageAccountName + location: location + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' + properties: { + defaultToOAuthAuthentication: true + publicNetworkAccess: 'Enabled' + allowCrossTenantReplication: false + minimumTlsVersion: 'TLS1_2' + allowBlobPublicAccess: false + allowSharedKeyAccess: false + networkAcls: { + bypass: 'AzureServices' + virtualNetworkRules: [] + ipRules: [] + defaultAction: 'Allow' + } + supportsHttpsTrafficOnly: true + encryption: { + services: { + file: { + keyType: 'Account' + enabled: true + } + blob: { + keyType: 'Account' + enabled: true + } + } + keySource: 'Microsoft.Storage' + } + } +} + +resource appInsights 'Microsoft.Insights/components@2020-02-02' = { + name: appInsightsName + location: location + kind: 'web' + properties: { + Application_Type: 'web' + } +} + +module functionApp './functionApp.bicep' = [ + for name in [ + 'funcPriorityQueueSender' + 'funcPriorityQueueConsumerLow' + 'funcPriorityQueueConsumerHigh' + ]: { + name: name + params: { + location: location + functionAppName: name + storageAccountName: storageAccount.name + serviceBusNamespaceName: serviceBusNamespaceName + roleId: name == 'funcPriorityQueueSender' ? senderRoleId : receiverRoleId + appInsightsName: appInsights.name + scaleUp: name == 'funcPriorityQueueConsumerHigh' ? 200 : 40 + } + } +] diff --git a/priority-queue/bicep/azure/functionApp.bicep b/priority-queue/bicep/azure/functionApp.bicep new file mode 100644 index 00000000..e41aa01e --- /dev/null +++ b/priority-queue/bicep/azure/functionApp.bicep @@ -0,0 +1,133 @@ +param location string = resourceGroup().location +param functionAppName string +param storageAccountName string +param serviceBusNamespaceName string +param roleId string +param appInsightsName string +param scaleUp int = 1 + +@description('Generates a unique container name for deployments') +var deploymentStorageContainerName = toLower('package-${take(functionAppName, 32)}') + +@description('Built-in role definition ID for Storage Blob Data Owner') +var azureStorageBlobDataOwnerRole = subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' +) // Storage Blob Data Owner + +resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces@2025-05-01-preview' existing = { + name: serviceBusNamespaceName +} + +resource storageAccount 'Microsoft.Storage/storageAccounts@2025-01-01' existing = { + name: storageAccountName +} + +// Define the blob service under the existing storage account for deployments +resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2021-09-01' = { + parent: storageAccount + name: 'default' + properties: { + deleteRetentionPolicy: { + enabled: false + } + } + + // Define the container inside the blob service + resource deploymentContainer 'containers' = { + name: deploymentStorageContainerName + properties: { + publicAccess: 'None' + } + } +} + +resource appInsights 'Microsoft.Insights/components@2020-02-02' existing = { + name: appInsightsName +} + +resource appServicePlan 'Microsoft.Web/serverfarms@2024-11-01' = { + name: '${functionAppName}-plan' + location: location + sku: { + name: 'FC1' + tier: 'FlexConsumption' + } + kind: 'functionapp' + properties: { + reserved: true + } +} + +resource functionApp 'Microsoft.Web/sites@2024-11-01' = { + name: functionAppName + location: location + kind: 'functionapp,linux' + identity: { + type: 'SystemAssigned' + } + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + minTlsVersion: '1.2' + appSettings: [ + { + name: 'AzureWebJobsStorage__accountName' + value: storageAccount.name + } + { + name: 'ServiceBusConnection__fullyQualifiedNamespace' + value: '${serviceBusNamespaceName}.servicebus.windows.net' + } + { + name: 'APPINSIGHTS_INSTRUMENTATIONKEY' + value: appInsights.properties.InstrumentationKey + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: appInsights.properties.ConnectionString + } + ] + } + functionAppConfig: { + deployment: { + storage: { + type: 'blobContainer' + value: '${storageAccount.properties.primaryEndpoints.blob}${blobService::deploymentContainer.name}' + authentication: { + type: 'SystemAssignedIdentity' + } + } + } + runtime: { + name: 'dotnet-isolated' + version: '9.0' + } + scaleAndConcurrency: { + maximumInstanceCount: scaleUp + instanceMemoryMB: 2048 + } + } + httpsOnly: true + } +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(functionApp.id, roleId) + scope: serviceBusNamespace + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId) + principalId: functionApp.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource storageBlobDataOwnerRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(functionApp.id, storageAccount.id, 'Storage Blob Data Owner') + scope: storageAccount + properties: { + roleDefinitionId: azureStorageBlobDataOwnerRole + principalId: functionApp.identity.principalId + principalType: 'ServicePrincipal' + } +} From b58280b353a37571a146dd6ff9e23d0b59a1a46d Mon Sep 17 00:00:00 2001 From: Federico Arambarri Date: Wed, 22 Oct 2025 08:37:48 -0300 Subject: [PATCH 5/6] Changing names --- priority-queue/Readme.md | 2 +- .../{azure-function-app.bicep => azure-function-apps.bicep} | 2 +- priority-queue/bicep/azure/{functionApp.bicep => sites.bicep} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename priority-queue/bicep/azure/{azure-function-app.bicep => azure-function-apps.bicep} (97%) rename priority-queue/bicep/azure/{functionApp.bicep => sites.bicep} (100%) diff --git a/priority-queue/Readme.md b/priority-queue/Readme.md index 31cbf399..61c28bee 100644 --- a/priority-queue/Readme.md +++ b/priority-queue/Readme.md @@ -102,7 +102,7 @@ Alternatively, you may configure the AzureWebJobsStorage setting to use a real A ```bash # This takes about five minutes - az deployment group create -n deploy-priority-queue-sites -f bicep/azure/azure-function-app.bicep -g $RESOURCE_GROUP_NAME -p serviceBusNamespaceName=$SERVICE_BUS_NAMESPACE_NAME + az deployment group create -n deploy-priority-queue-sites -f bicep/azure/azure-function-apps.bicep -g $RESOURCE_GROUP_NAME -p serviceBusNamespaceName=$SERVICE_BUS_NAMESPACE_NAME # Deploy using Azure Functions Core Tools cd .\PriorityQueueSender\ diff --git a/priority-queue/bicep/azure/azure-function-app.bicep b/priority-queue/bicep/azure/azure-function-apps.bicep similarity index 97% rename from priority-queue/bicep/azure/azure-function-app.bicep rename to priority-queue/bicep/azure/azure-function-apps.bicep index dddb6153..ebda3cbd 100644 --- a/priority-queue/bicep/azure/azure-function-app.bicep +++ b/priority-queue/bicep/azure/azure-function-apps.bicep @@ -52,7 +52,7 @@ resource appInsights 'Microsoft.Insights/components@2020-02-02' = { } } -module functionApp './functionApp.bicep' = [ +module functionApp './sites.bicep' = [ for name in [ 'funcPriorityQueueSender' 'funcPriorityQueueConsumerLow' diff --git a/priority-queue/bicep/azure/functionApp.bicep b/priority-queue/bicep/azure/sites.bicep similarity index 100% rename from priority-queue/bicep/azure/functionApp.bicep rename to priority-queue/bicep/azure/sites.bicep From 4e281ff358510f45dee5d0ce486df8d1f1dc550b Mon Sep 17 00:00:00 2001 From: Federico Arambarri Date: Wed, 22 Oct 2025 16:16:02 -0300 Subject: [PATCH 6/6] Improve Information --- priority-queue/Readme.md | 58 ++++++++----------- .../bicep/azure/azure-function-apps.bicep | 16 ++++- priority-queue/bicep/azure/sites.bicep | 15 +++++ 3 files changed, 53 insertions(+), 36 deletions(-) diff --git a/priority-queue/Readme.md b/priority-queue/Readme.md index 61c28bee..4e6b9bbc 100644 --- a/priority-queue/Readme.md +++ b/priority-queue/Readme.md @@ -6,7 +6,7 @@ This example demonstrates how priority queues can be implemented by using Servic For local execution, the sample demonstrates the producer/consumer model, where each consumer processes only one type of message based on its priority. -In the Azure Deployment, the _PriorityQueueConsumerHigh_ Azure function could scale out to 200 instances, while the _PriorityQueueConsumerLow_ Azure function runs only with one instance. It simulates high priority messages being read from the queue more urgently than low priority messages. +In the Azure Deployment, the _PriorityQueueConsumerHigh_ Azure function could scale out to 200 instances, while the _PriorityQueueConsumerLow_ Azure function could scale out only to 40 instances. It simulates high priority messages being read from the queue more urgently than low priority messages. The Azure deployment also demonstrates operational aspects of applications running on Azure. Monitoring tools are essential to understand how the sample operates. Azure Functions in the solution **must** be configured to use the diagnostics mechanism. Otherwise, trace information generated by the example will not be visible. @@ -100,11 +100,17 @@ Alternatively, you may configure the AzureWebJobsStorage setting to use a real A ## Deploy the example to Azure (Optional) +This Bicep template sets up the core infrastructure for a priority-based message processing system using Azure Functions. It creates a secure Storage Account, an Application Insights instance for monitoring, and uses a previously created Service Bus namespace to enable communication between the sender and consumer functions. The deployment includes three Azure Function Apps: one sender and two consumers, each with different scaling limits to simulate message prioritization. +The funcPriorityQueueConsumerHigh function can scale out to 200 instances, allowing it to process high-priority messages quickly. The funcPriorityQueueConsumerLow function is limited to 40 instances, handling lower-priority messages with less urgency. All function apps use the FlexConsumption plan and are connected to Application Insights for diagnostics and monitoring. Role assignments are configured to securely grant access to the Service Bus and Storage resources using managed identities. +All Azure Function Apps share the same Storage Account and Application Insights instance (It is essential to understand how the sample operates), which centralizes observability and logging. + ```bash - # This takes about five minutes + # This takes about three minutes az deployment group create -n deploy-priority-queue-sites -f bicep/azure/azure-function-apps.bicep -g $RESOURCE_GROUP_NAME -p serviceBusNamespaceName=$SERVICE_BUS_NAMESPACE_NAME +``` +After deploying the infrastructure, you need to publish each Azure Function to its corresponding Function App using Azure Functions Core Tools: - # Deploy using Azure Functions Core Tools +```bash cd .\PriorityQueueSender\ func azure functionapp publish funcPriorityQueueSender cd .. @@ -115,50 +121,36 @@ Alternatively, you may configure the AzureWebJobsStorage setting to use a real A func azure functionapp publish funcPriorityQueueConsumerHigh ``` -``` - // Recent requests with key details - requests - | project timestamp, operation_Name, cloud_RoleName, id, success, resultCode, duration, operation_Id - | order by timestamp desc +You can view the maximum scaling configuration in the Azure Portal. Go to each Function App, then under Settings, select Scale and concurrency. There, you'll see the Maximum instance count setting. - // Count of requests by operation name - requests - | summarize RequestCount = count() by operation_Name - | order by RequestCount desc +Once the Azure Functions are deployed, you can use Application Insights to monitor their activity. In the Azure portal, go to the Application Insights resource, then select Logs. Switch to KQL mode and run the following queries to view trace logs for each function: - // Traces filtered by keywords +``` + // Traces for the High priority consumer traces | where operation_Name contains "High" + // Traces for the Low priority consumer traces | where operation_Name contains "Low" + // Traces for the Sender function traces | where operation_Name contains "Sender" ``` +In addition to viewing trace logs, you can also use Application Insights to analyze request-level data for each Azure Function. The following queries help you inspect recent requests and understand how frequently each function is being called: - -To deploy the example to Azure, you need to publish each Azure Functions to Azure. You can do so from Visual Studio by right clicking each function and selecting `Publish` from the menu. Use the same resource group and region from earlier. Be sure to enable Application Insights. - -Once each Azure Function is published, a new App Setting must be added to store the Service Bus fully qualified namespace. This is the same value that was used in the `SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE` variable in the previous steps. The solution is using Managed Identity to access Service Bus. - -For each Azure Function, run the following: - -```bash -az webapp config appsettings set -n -g $RESOURCE_GROUP_NAME --settings ServiceBusConnection__fullyQualifiedNamespace=$SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE ``` + // Recent requests with key details + requests + | project timestamp, operation_Name, cloud_RoleName, id, success, resultCode, duration, operation_Id + | order by timestamp desc -Once the Azure Functions are deployed you need to restrict the maximum number of instances the `PriorityQueueConsumerLow` function can scale out to. - -From the Azure portal: - -- Visit the Function App that contains `PriorityQueueConsumerLow` -- Navigate to Scale Out on the left menu -- On the App Scale Out dialog, set the `Enforce Scale Out Limit` to `Yes` -- Set the `Maximum Scale Out Limit` to `1` instance - -Once the Azure Functions are deployed you can visit Application Insights to view the most recent activity for each function. - + // Count of requests by Function + requests + | summarize RequestCount = count() by operation_Name + | order by RequestCount desc +``` ## :broom: Clean up resources Be sure to delete Azure resources when not using them. Since all resources were deployed into a new resource group, you can simply delete the resource group. diff --git a/priority-queue/bicep/azure/azure-function-apps.bicep b/priority-queue/bicep/azure/azure-function-apps.bicep index ebda3cbd..007f4b67 100644 --- a/priority-queue/bicep/azure/azure-function-apps.bicep +++ b/priority-queue/bicep/azure/azure-function-apps.bicep @@ -1,10 +1,20 @@ +targetScope = 'resourceGroup' + +@minLength(5) +@description('Location of the resources. Defaults to resource group location.') param location string = resourceGroup().location -param storageAccountName string = 'st${uniqueString(resourceGroup().id)}' + +@description('The name of the existing Service Bus namespace used for message queuing between the sender and consumer functions.') param serviceBusNamespaceName string + +@description('Defines the name of the Storage Account used by the Function Apps. It uses a unique string based on the resource group ID to ensure global uniqueness.') +param storageAccountName string = 'st${uniqueString(resourceGroup().id)}' + +@description('Sets the name of the Application Insights resource for monitoring and diagnostics. Like the storage account, it uses a unique string based on the resource group ID.') param appInsightsName string = 'ai${uniqueString(resourceGroup().id)}' -var senderRoleId = '69a216fc-b8fb-44d8-bc22-1f3c2cd27a39' -var receiverRoleId = '4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0' +var senderRoleId = '69a216fc-b8fb-44d8-bc22-1f3c2cd27a39' //Azure Service Bus Data Sender +var receiverRoleId = '4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0' //Azure Service Bus Data Receiver resource storageAccount 'Microsoft.Storage/storageAccounts@2025-01-01' = { name: storageAccountName diff --git a/priority-queue/bicep/azure/sites.bicep b/priority-queue/bicep/azure/sites.bicep index e41aa01e..e939e82f 100644 --- a/priority-queue/bicep/azure/sites.bicep +++ b/priority-queue/bicep/azure/sites.bicep @@ -1,9 +1,24 @@ +targetScope = 'resourceGroup' + +@minLength(5) +@description('Location of the resources. Defaults to resource group location.') param location string = resourceGroup().location + param functionAppName string + +@description('Defines the name of the Storage Account used by the Function Apps.') param storageAccountName string + +@description('The name of the existing Service Bus namespace used for message queuing between the sender and consumer functions.') param serviceBusNamespaceName string + +@description('The built-in role definition ID to assign to the Function App for accessing the Service Bus namespace.') param roleId string + +@description('Sets the name of the Application Insights resource for monitoring and diagnostics. ') param appInsightsName string + +@description('Specifies the maximum number of instances to which the Function App can scale out.') param scaleUp int = 1 @description('Generates a unique container name for deployments')