From 71a88290e6b6db1c37153d8bc74d56ef6532cc46 Mon Sep 17 00:00:00 2001 From: Nick Greenfield Date: Tue, 17 Feb 2026 16:10:28 -0800 Subject: [PATCH] Switch durable backend to Durable Task Scheduler (Consumption SKU) - Add DurableTask.AzureManaged NuGet package - Update host.json to use azureManaged storage provider - Add dts.bicep and dts-Access.bicep for scheduler/taskhub provisioning - Update main.bicep with DTS modules and RBAC role assignments - Update processor.bicep to inject DTS connection string app settings - Add durableTaskSchedulers/durableTaskHubs abbreviations - Use Consumption SKU (no capacity) for DTS --- Durable-Functions-Order-Processing.sln | 24 +++++++++++ OrderProcessor/OrderProcessor.csproj | 3 +- OrderProcessor/host.json | 13 +++--- infra/abbreviations.json | 2 + infra/app/dts-Access.bicep | 18 +++++++++ infra/app/dts.bicep | 31 +++++++++++++++ infra/app/processor.bicep | 11 +++++- infra/main.bicep | 55 ++++++++++++++++++++++++++ 8 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 Durable-Functions-Order-Processing.sln create mode 100644 infra/app/dts-Access.bicep create mode 100644 infra/app/dts.bicep diff --git a/Durable-Functions-Order-Processing.sln b/Durable-Functions-Order-Processing.sln new file mode 100644 index 0000000..2b82ed5 --- /dev/null +++ b/Durable-Functions-Order-Processing.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderProcessor", "OrderProcessor\OrderProcessor.csproj", "{0288CA01-7BAB-2761-1E65-8139AFEA16AA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0288CA01-7BAB-2761-1E65-8139AFEA16AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0288CA01-7BAB-2761-1E65-8139AFEA16AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0288CA01-7BAB-2761-1E65-8139AFEA16AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0288CA01-7BAB-2761-1E65-8139AFEA16AA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {55E95CFE-B613-4782-B6F6-7A40F4BF2A62} + EndGlobalSection +EndGlobal diff --git a/OrderProcessor/OrderProcessor.csproj b/OrderProcessor/OrderProcessor.csproj index 146780d..77a4a68 100644 --- a/OrderProcessor/OrderProcessor.csproj +++ b/OrderProcessor/OrderProcessor.csproj @@ -10,7 +10,8 @@ - + + diff --git a/OrderProcessor/host.json b/OrderProcessor/host.json index 15bb085..abac283 100644 --- a/OrderProcessor/host.json +++ b/OrderProcessor/host.json @@ -6,17 +6,18 @@ "isEnabled": true, "excludedTypes": "Request" }, - "enableLiveMetricsFilters": true, - "logLevel": { - "Host.Triggers.DurableTask": "Information" - } + "enableLiveMetricsFilters": true + }, + "logLevel": { + "DurableTask.AzureManagedBackend": "Information" } }, "extensions": { "durableTask": { + "hubName": "%TASKHUB_NAME%", "storageProvider": { - "type": "AzureStorage", - "maxQueuePollingInterval": "00:00:01" + "type": "azureManaged", + "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING" } } } diff --git a/infra/abbreviations.json b/infra/abbreviations.json index a4fc9df..d7677a1 100644 --- a/infra/abbreviations.json +++ b/infra/abbreviations.json @@ -37,6 +37,8 @@ "devicesProvisioningServices": "provs-", "devicesProvisioningServicesCertificates": "pcert-", "documentDBDatabaseAccounts": "cosmos-", + "durableTaskSchedulers": "dts-", + "durableTaskHubs": "th-", "eventGridDomains": "evgd-", "eventGridDomainsTopics": "evgt-", "eventGridEventSubscriptions": "evgs-", diff --git a/infra/app/dts-Access.bicep b/infra/app/dts-Access.bicep new file mode 100644 index 0000000..1c435b9 --- /dev/null +++ b/infra/app/dts-Access.bicep @@ -0,0 +1,18 @@ +param principalID string +param roleDefinitionID string +param dtsName string +param principalType string + +resource dts 'Microsoft.DurableTask/schedulers@2025-04-01-preview' existing = { + name: dtsName +} + +resource dtsRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(dts.id, principalID, roleDefinitionID) + scope: dts + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionID) + principalId: principalID + principalType: principalType + } +} diff --git a/infra/app/dts.bicep b/infra/app/dts.bicep new file mode 100644 index 0000000..e3c77b7 --- /dev/null +++ b/infra/app/dts.bicep @@ -0,0 +1,31 @@ +param ipAllowlist array +param location string +param tags object = {} +param name string +param taskhubname string +param skuName string +param skuCapacity int = 0 + +resource dts 'Microsoft.DurableTask/schedulers@2025-04-01-preview' = { + location: location + tags: tags + name: name + properties: { + ipAllowlist: ipAllowlist + sku: skuName == 'Consumption' ? { + name: skuName + } : { + name: skuName + capacity: skuCapacity + } + } +} + +resource taskhub 'Microsoft.DurableTask/schedulers/taskHubs@2025-04-01-preview' = { + parent: dts + name: taskhubname +} + +output dts_NAME string = dts.name +output dts_URL string = dts.properties.endpoint +output TASKHUB_NAME string = taskhub.name diff --git a/infra/app/processor.bicep b/infra/app/processor.bicep index 17ebd2b..b34e529 100644 --- a/infra/app/processor.bicep +++ b/infra/app/processor.bicep @@ -13,9 +13,17 @@ param instanceMemoryMB int = 2048 param maximumInstanceCount int = 100 param identityId string = '' param identityClientId string = '' +param dtsURL string = '' +param taskHubName string = '' var applicationInsightsIdentity = 'ClientId=${identityClientId};Authorization=AAD' +// Durable Task Scheduler settings +var dtsSettings = !empty(dtsURL) ? { + DURABLE_TASK_SCHEDULER_CONNECTION_STRING: 'Endpoint=${dtsURL};Authentication=ManagedIdentity;ClientID=${identityClientId}' + TASKHUB_NAME: taskHubName +} : {} + module processor '../core/host/functions-flexconsumption.bicep' = { name: '${serviceName}-functions-module' params: { @@ -28,7 +36,8 @@ module processor '../core/host/functions-flexconsumption.bicep' = { { AzureWebJobsStorage__clientId : identityClientId APPLICATIONINSIGHTS_AUTHENTICATION_STRING: applicationInsightsIdentity - }) + }, + dtsSettings) applicationInsightsName: applicationInsightsName appServicePlanId: appServicePlanId runtimeName: runtimeName diff --git a/infra/main.bicep b/infra/main.bicep index c073a39..489fd39 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -24,10 +24,19 @@ param resourceGroupName string = '' param storageAccountName string = '' param vNetName string = '' param disableLocalAuth bool = true +param dtsName string = '' +param taskHubName string = '' +param dtsLocation string = location +param dtsSkuName string = 'Consumption' +param dtsCapacity int = 1 +@description('Id of the user identity to be used for testing and debugging. This is not required in production. Leave empty if not needed.') +param principalId string = deployer().objectId var abbrs = loadJsonContent('./abbreviations.json') var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) var tags = { 'azd-env-name': environmentName } +var dtsResourceName = !empty(dtsName) ? dtsName : '${abbrs.durableTaskSchedulers}${resourceToken}' +var taskHubResourceName = !empty(taskHubName) ? taskHubName : '${abbrs.durableTaskHubs}${resourceToken}' // Organize resources in a resource group resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { @@ -65,6 +74,8 @@ module processor './app/processor.bicep' = { appSettings: { } virtualNetworkSubnetId: serviceVirtualNetwork.outputs.appSubnetID + dtsURL: dts.outputs.dts_URL + taskHubName: dts.outputs.TASKHUB_NAME } } @@ -191,6 +202,50 @@ module appInsightsRoleAssignmentApi './core/monitor/appinsights-access.bicep' = } } +// Durable Task Scheduler +module dts './app/dts.bicep' = { + scope: rg + name: 'dtsResource' + params: { + name: dtsResourceName + taskhubname: taskHubResourceName + location: dtsLocation + tags: tags + ipAllowlist: [ + '0.0.0.0/0' + ] + skuName: dtsSkuName + skuCapacity: dtsCapacity + } +} + +// Durable Task Data Contributor role ID +var dtsRoleDefinitionId = '0ad04412-c4d5-4796-b79c-f76d14c8d402' + +// Allow access from function app to DTS using user assigned managed identity +module dtsRoleAssignment 'app/dts-Access.bicep' = { + name: 'dtsRoleAssignment' + scope: rg + params: { + roleDefinitionID: dtsRoleDefinitionId + principalID: processorUserAssignedIdentity.outputs.identityPrincipalId + principalType: 'ServicePrincipal' + dtsName: dts.outputs.dts_NAME + } +} + +// Allow the deployer identity to access the DTS dashboard +module dtsDashboardRoleAssignment 'app/dts-Access.bicep' = { + name: 'dtsDashboardRoleAssignment' + scope: rg + params: { + roleDefinitionID: dtsRoleDefinitionId + principalID: principalId + principalType: 'User' + dtsName: dts.outputs.dts_NAME + } +} + // App outputs output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString output AZURE_LOCATION string = location