Bindings for Durable Functions (Azure Functions)
The Durable Functions extension introduces three trigger bindings that control the execution of orchestrator, entity, and activity functions. It also introduces an output binding that acts as a client for the Durable Functions runtime.
Make sure to choose your Durable Functions development language at the top of the article.
Important
This article supports both Python v1 and Python v2 programming models for Durable Functions.
Python v2 programming model
Durable Functions is supported in the new Python v2 programming model. To use the v2 model, you must install the Durable Functions SDK, which is the PyPI package azure-functions-durable
, version 1.2.2
or a later version. You must also check host.json
to make sure your app is referencing Extension Bundles version 4.x to use the v2 model with Durable Functions.
You can provide feedback and suggestions in the Durable Functions SDK for Python repo.
Orchestration trigger
The orchestration trigger enables you to author durable orchestrator functions. This trigger executes when a new orchestration instance is scheduled and when an existing orchestration instance receives an event. Examples of events that can trigger orchestrator functions include durable timer expirations, activity function responses, and events raised by external clients.
When you author functions in .NET, the orchestration trigger is configured using the OrchestrationTriggerAttribute .NET attribute.
For Java, the @DurableOrchestrationTrigger
annotation is used to configure the orchestration trigger.
When you write orchestrator functions, the orchestration trigger is defined by the following JSON object in the bindings
array of the function.json file:
{
"name": "<Name of input parameter in function signature>",
"orchestration": "<Optional - name of the orchestration>",
"type": "orchestrationTrigger",
"direction": "in"
}
orchestration
is the name of the orchestration that clients must use when they want to start new instances of this orchestrator function. This property is optional. If not specified, the name of the function is used.
Azure Functions supports two programming models for Python. The way that you define an orchestration trigger depends on your chosen programming model.
The Python v2 programming model lets you define an orchestration trigger using the orchestration_trigger
decorator directly in your Python function code.
In the v2 model, the Durable Functions triggers and bindings are accessed from an instance of DFApp
, which is a subclass of FunctionApp
that additionally exports Durable Functions-specific decorators.
Internally, this trigger binding polls the configured durable store for new orchestration events, such as orchestration start events, durable timer expiration events, activity function response events, and external events raised by other functions.
Trigger behavior
Here are some notes about the orchestration trigger:
- Single-threading - A single dispatcher thread is used for all orchestrator function execution on a single host instance. For this reason, it's important to ensure that orchestrator function code is efficient and doesn't perform any I/O. It is also important to ensure that this thread does not do any async work except when awaiting on Durable Functions-specific task types.
- Poison-message handling - There's no poison message support in orchestration triggers.
- Message visibility - Orchestration trigger messages are dequeued and kept invisible for a configurable duration. The visibility of these messages is renewed automatically as long as the function app is running and healthy.
- Return values - Return values are serialized to JSON and persisted to the orchestration history table in Azure Table storage. These return values can be queried by the orchestration client binding, described later.
Warning
Orchestrator functions should never use any input or output bindings other than the orchestration trigger binding. Doing so has the potential to cause problems with the Durable Task extension because those bindings may not obey the single-threading and I/O rules. If you'd like to use other bindings, add them to an activity function called from your orchestrator function. For more information about coding constraints for orchestrator functions, see the Orchestrator function code constraints documentation.
Warning
Orchestrator functions should never be declared async
.
Trigger usage
The orchestration trigger binding supports both inputs and outputs. Here are some things to know about input and output handling:
- inputs - Orchestration triggers can be invoked with inputs, which are accessed through the context input object. All inputs must be JSON-serializable.
- outputs - Orchestration triggers support output values as well as inputs. The return value of the function is used to assign the output value and must be JSON-serializable.
Trigger sample
The following example code shows what the simplest "Hello World" orchestrator function might look like. Note that this example orchestrator doesn't actually schedule any tasks.
The specific attribute used to define the trigger depends on whether you are running your C# functions in-process or in an isolated worker process.
[FunctionName("HelloWorld")]
public static string Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
string name = context.GetInput<string>();
return $"Hello {name}!";
}
Note
The previous code is for Durable Functions 2.x. For Durable Functions 1.x, you must use DurableOrchestrationContext
instead of IDurableOrchestrationContext
. For more information about the differences between versions, see the Durable Functions Versions article.
const df = require("durable-functions");
module.exports = df.orchestrator(function*(context) {
const name = context.df.getInput();
return `Hello ${name}!`;
});
Note
The durable-functions
library takes care of calling the synchronous context.done
method when the generator function exits.
param($Context)
$InputData = $Context.Input
$InputData
@FunctionName("HelloWorldOrchestration")
public String helloWorldOrchestration(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
return String.format("Hello %s!", ctx.getInput(String.class));
}
Most orchestrator functions call activity functions, so here is a "Hello World" example that demonstrates how to call an activity function:
[FunctionName("HelloWorld")]
public static async Task<string> Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
string name = context.GetInput<string>();
string result = await context.CallActivityAsync<string>("SayHello", name);
return result;
}
Note
The previous code is for Durable Functions 2.x. For Durable Functions 1.x, you must use DurableOrchestrationContext
instead of IDurableOrchestrationContext
. For more information about the differences between versions, see the Durable Functions versions article.
const df = require("durable-functions");
module.exports = df.orchestrator(function*(context) {
const name = context.df.getInput();
const result = yield context.df.callActivity("SayHello", name);
return result;
});
@FunctionName("HelloWorld")
public String helloWorldOrchestration(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
String input = ctx.getInput(String.class);
String result = ctx.callActivity("SayHello", input, String.class).await();
return result;
}
Activity trigger
The activity trigger enables you to author functions that are called by orchestrator functions, known as activity functions.
The activity trigger is configured using the ActivityTriggerAttribute .NET attribute.
The activity trigger is configured using the @DurableActivityTrigger
annotation.
The activity trigger is defined by the following JSON object in the bindings
array of function.json:
{
"name": "<Name of input parameter in function signature>",
"activity": "<Optional - name of the activity>",
"type": "activityTrigger",
"direction": "in"
}
activity
is the name of the activity. This value is the name that orchestrator functions use to invoke this activity function. This property is optional. If not specified, the name of the function is used.
The way that you define an activity trigger depends on your chosen programming model.
Internally, this trigger binding polls the configured durable store for new activity execution events.
Trigger behavior
Here are some notes about the activity trigger:
- Threading - Unlike the orchestration trigger, activity triggers don't have any restrictions around threading or I/O. They can be treated like regular functions.
- Poison-message handling - There's no poison message support in activity triggers.
- Message visibility - Activity trigger messages are dequeued and kept invisible for a configurable duration. The visibility of these messages is renewed automatically as long as the function app is running and healthy.
- Return values - Return values are serialized to JSON and persisted to the configured durable store.
Trigger usage
The activity trigger binding supports both inputs and outputs, just like the orchestration trigger. Here are some things to know about input and output handling:
- inputs - Activity triggers can be invoked with inputs from an orchestrator function. All inputs must be JSON-serializable.
- outputs - Activity functions support output values as well as inputs. The return value of the function is used to assign the output value and must be JSON-serializable.
- metadata - .NET activity functions can bind to a
string instanceId
parameter to get the instance ID of the calling orchestration.
Trigger sample
The following example code shows what a simple SayHello
activity function might look like.
[FunctionName("SayHello")]
public static string SayHello([ActivityTrigger] IDurableActivityContext helloContext)
{
string name = helloContext.GetInput<string>();
return $"Hello {name}!";
}
The default parameter type for the .NET ActivityTriggerAttribute
binding is IDurableActivityContext (or DurableActivityContext for Durable Functions v1). However, .NET activity triggers also support binding directly to JSON-serializeable types (including primitive types), so the same function could be simplified as follows:
[FunctionName("SayHello")]
public static string SayHello([ActivityTrigger] string name)
{
return $"Hello {name}!";
}
module.exports = async function(context) {
return `Hello ${context.bindings.name}!`;
};
JavaScript bindings can also be passed in as additional parameters, so the same function could be simplified as follows:
module.exports = async function(context, name) {
return `Hello ${name}!`;
};
param($name)
"Hello $name!"
@FunctionName("SayHello")
public String sayHello(@DurableActivityTrigger(name = "name") String name) {
return String.format("Hello %s!", name);
}
Using input and output bindings
You can use regular input and output bindings in addition to the activity trigger binding.
For example, you can take the input to your activity binding, and send a message to an Event Hub using the Event Hubs output binding:
{
"bindings": [
{
"name": "message",
"type": "activityTrigger",
"direction": "in"
},
{
"type": "eventHub",
"name": "outputEventHubMessage",
"connection": "EventhubConnectionSetting",
"eventHubName": "eh_messages",
"direction": "out"
}
]
}
module.exports = async function (context) {
context.bindings.outputEventHubMessage = context.bindings.message;
};
Orchestration client
The orchestration client binding enables you to write functions that interact with orchestrator functions. These functions are often referred to as client functions. For example, you can act on orchestration instances in the following ways:
- Start them.
- Query their status.
- Terminate them.
- Send events to them while they're running.
- Purge instance history.
You can bind to the orchestration client by using the DurableClientAttribute attribute (OrchestrationClientAttribute in Durable Functions v1.x).
You can bind to the orchestration client by using the @DurableClientInput
annotation.
The durable client trigger is defined by the following JSON object in the bindings
array of function.json:
{
"name": "<Name of input parameter in function signature>",
"taskHub": "<Optional - name of the task hub>",
"connectionName": "<Optional - name of the connection string app setting>",
"type": "orchestrationClient",
"direction": "in"
}
taskHub
- Used in scenarios where multiple function apps share the same storage account but need to be isolated from each other. If not specified, the default value fromhost.json
is used. This value must match the value used by the target orchestrator functions.connectionName
- The name of an app setting that contains a storage account connection string. The storage account represented by this connection string must be the same one used by the target orchestrator functions. If not specified, the default storage account connection string for the function app is used.
Note
In most cases, we recommend that you omit these properties and rely on the default behavior.
The way that you define a durable client trigger depends on your chosen programming model.
Client usage
You typically bind to IDurableClient (DurableOrchestrationClient in Durable Functions v1.x), which gives you full access to all orchestration client APIs supported by Durable Functions.
You typically bind to the DurableClientContext
class.
You must use the language-specific SDK to get access to a client object.
Here's an example queue-triggered function that starts a "HelloWorld" orchestration.
[FunctionName("QueueStart")]
public static Task Run(
[QueueTrigger("durable-function-trigger")] string input,
[DurableClient] IDurableOrchestrationClient starter)
{
// Orchestration input comes from the queue message content.
return starter.StartNewAsync<string>("HelloWorld", input);
}
Note
The previous C# code is for Durable Functions 2.x. For Durable Functions 1.x, you must use OrchestrationClient
attribute instead of the DurableClient
attribute, and you must use the DurableOrchestrationClient
parameter type instead of IDurableOrchestrationClient
. For more information about the differences between versions, see the Durable Functions Versions article.
function.json
{
"bindings": [
{
"name": "input",
"type": "queueTrigger",
"queueName": "durable-function-trigger",
"direction": "in"
},
{
"name": "starter",
"type": "durableClient",
"direction": "in"
}
]
}
index.js
const df = require("durable-functions");
module.exports = async function (context) {
const client = df.getClient(context);
return instanceId = await client.startNew("HelloWorld", undefined, context.bindings.input);
};
run.ps1
param([string] $input, $TriggerMetadata)
$InstanceId = Start-DurableOrchestration -FunctionName $FunctionName -Input $input
import azure.functions as func
import azure.durable_functions as df
myApp = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
@myApp.route(route="orchestrators/{functionName}")
@myApp.durable_client_input(client_name="client")
async def durable_trigger(req: func.HttpRequest, client):
function_name = req.route_params.get('functionName')
instance_id = await client.start_new(function_name)
response = client.create_check_status_response(req, instance_id)
return response
function.json
{
"bindings": [
{
"name": "input",
"type": "queueTrigger",
"queueName": "durable-function-trigger",
"direction": "in"
},
{
"name": "starter",
"type": "durableClient",
"direction": "in"
}
]
}
run.ps1
param([string]$InputData, $TriggerMetadata)
$InstanceId = Start-DurableOrchestration -FunctionName 'HelloWorld' -Input $InputData
@FunctionName("QueueStart")
public void queueStart(
@QueueTrigger(name = "input", queueName = "durable-function-trigger", connection = "Storage") String input,
@DurableClientInput(name = "durableContext") DurableClientContext durableContext) {
// Orchestration input comes from the queue message content.
durableContext.getClient().scheduleNewOrchestrationInstance("HelloWorld", input);
}
More details on starting instances can be found in Instance management.
Entity trigger
Entity triggers allow you to author entity functions. This trigger supports processing events for a specific entity instance.
Note
Entity triggers are available starting in Durable Functions 2.x.
Internally, this trigger binding polls the configured durable store for new entity operations that need to be executed.
The entity trigger is configured using the EntityTriggerAttribute .NET attribute.
The entity trigger is defined by the following JSON object in the bindings
array of function.json:
{
"name": "<Name of input parameter in function signature>",
"entityName": "<Optional - name of the entity>",
"type": "entityTrigger",
"direction": "in"
}
By default, the name of an entity is the name of the function.
Note
Entity triggers aren't yet supported for Java.
The way that you define a entity trigger depends on your chosen programming model.
Trigger behavior
Here are some notes about the entity trigger:
- Single-threaded: A single dispatcher thread is used to process operations for a particular entity. If multiple messages are sent to a single entity concurrently, the operations will be processed one-at-a-time.
- Poison-message handling - There's no poison message support in entity triggers.
- Message visibility - Entity trigger messages are dequeued and kept invisible for a configurable duration. The visibility of these messages is renewed automatically as long as the function app is running and healthy.
- Return values - Entity functions don't support return values. There are specific APIs that can be used to save state or pass values back to orchestrations.
Any state changes made to an entity during its execution will be automatically persisted after execution has completed.
For more information and examples on defining and interacting with entity triggers, see the Durable Entities documentation.
Entity client
The entity client binding enables you to asynchronously trigger entity functions. These functions are sometimes referred to as client functions.
You can bind to the entity client by using the DurableClientAttribute .NET attribute in .NET class library functions.
Note
The [DurableClientAttribute]
can also be used to bind to the orchestration client.
The entity client is defined by the following JSON object in the bindings
array of function.json:
{
"name": "<Name of input parameter in function signature>",
"taskHub": "<Optional - name of the task hub>",
"connectionName": "<Optional - name of the connection string app setting>",
"type": "durableClient",
"direction": "in"
}
taskHub
- Used in scenarios where multiple function apps share the same storage account but need to be isolated from each other. If not specified, the default value fromhost.json
is used. This value must match the value used by the target entity functions.connectionName
- The name of an app setting that contains a storage account connection string. The storage account represented by this connection string must be the same one used by the target entity functions. If not specified, the default storage account connection string for the function app is used.
Note
In most cases, we recommend that you omit the optional properties and rely on the default behavior.
The way that you define a entity client depends on your chosen programming model.
Note
Entity clients aren't yet supported for Java.
For more information and examples on interacting with entities as a client, see the Durable Entities documentation.
host.json settings
Configuration settings for Durable Functions.
Note
All major versions of Durable Functions are supported on all versions of the Azure Functions runtime. However, the schema of the host.json configuration is slightly different depending on the version of the Azure Functions runtime and the Durable Functions extension version you use. The following examples are for use with Azure Functions 2.0 and 3.0. In both examples, if you're using Azure Functions 1.0, the available settings are the same, but the "durableTask" section of the host.json should go in the root of the host.json configuration instead of as a field under "extensions".
{
"extensions": {
"durableTask": {
"hubName": "MyTaskHub",
"storageProvider": {
"connectionStringName": "AzureWebJobsStorage",
"controlQueueBatchSize": 32,
"controlQueueBufferThreshold": 256,
"controlQueueVisibilityTimeout": "00:05:00",
"maxQueuePollingInterval": "00:00:30",
"partitionCount": 4,
"trackingStoreConnectionStringName": "TrackingStorage",
"trackingStoreNamePrefix": "DurableTask",
"useLegacyPartitionManagement": true,
"workItemQueueVisibilityTimeout": "00:05:00",
},
"tracing": {
"traceInputsAndOutputs": false,
"traceReplayEvents": false,
},
"notifications": {
"eventGrid": {
"topicEndpoint": "https://topic_name.chinanorth2-1.eventgrid.chinacloudapi.cn/api/events",
"keySettingName": "EventGridKey",
"publishRetryCount": 3,
"publishRetryInterval": "00:00:30",
"publishEventTypes": [
"Started",
"Pending",
"Failed",
"Terminated"
]
}
},
"maxConcurrentActivityFunctions": 10,
"maxConcurrentOrchestratorFunctions": 10,
"extendedSessionsEnabled": false,
"extendedSessionIdleTimeoutInSeconds": 30,
"useAppLease": true,
"useGracefulShutdown": false,
"maxEntityOperationBatchSize": 50
}
}
}
Task hub names must start with a letter and consist of only letters and numbers. If not specified, the default task hub name for a function app is TestHubName. For more information, see Task hubs.
Property | Default | Description |
---|---|---|
hubName | TestHubName (DurableFunctionsHub if using Durable Functions 1.x) | Alternate task hub names can be used to isolate multiple Durable Functions applications from each other, even if they're using the same storage backend. |
controlQueueBatchSize | 32 | The number of messages to pull from the control queue at a time. |
controlQueueBufferThreshold | Consumption plan for Python: 32 Consumption plan for JavaScript and C#: 128 Dedicated/Premium plan: 256 |
The number of control queue messages that can be buffered in memory at a time, at which point the dispatcher will wait before dequeuing any additional messages. |
partitionCount | 4 | The partition count for the control queue. May be a positive integer between 1 and 16. |
controlQueueVisibilityTimeout | 5 minutes | The visibility timeout of dequeued control queue messages. |
workItemQueueVisibilityTimeout | 5 minutes | The visibility timeout of dequeued work item queue messages. |
maxConcurrentActivityFunctions | Consumption plan: 10 Dedicated/Premium plan: 10X the number of processors on the current machine |
The maximum number of activity functions that can be processed concurrently on a single host instance. |
maxConcurrentOrchestratorFunctions | Consumption plan: 5 Dedicated/Premium plan: 10X the number of processors on the current machine |
The maximum number of orchestrator functions that can be processed concurrently on a single host instance. |
maxQueuePollingInterval | 30 seconds | The maximum control and work-item queue polling interval in the hh:mm:ss format. Higher values can result in higher message processing latencies. Lower values can result in higher storage costs because of increased storage transactions. |
connectionName (2.7.0 and later) connectionStringName (2.x) azureStorageConnectionStringName (1.x) |
AzureWebJobsStorage | The name of an app setting or setting collection that specifies how to connect to the underlying Azure Storage resources. When a single app setting is provided, it should be an Azure Storage connection string. |
trackingStoreConnectionName (2.7.0 and later) trackingStoreConnectionStringName |
The name of an app setting or setting collection that specifies how to connect to the History and Instances tables. When a single app setting is provided, it should be an Azure Storage connection string. If not specified, the connectionStringName (Durable 2.x) or azureStorageConnectionStringName (Durable 1.x) connection is used. |
|
trackingStoreNamePrefix | The prefix to use for the History and Instances tables when trackingStoreConnectionStringName is specified. If not set, the default prefix value will be DurableTask . If trackingStoreConnectionStringName is not specified, then the History and Instances tables will use the hubName value as their prefix, and any setting for trackingStoreNamePrefix will be ignored. |
|
traceInputsAndOutputs | false | A value indicating whether to trace the inputs and outputs of function calls. The default behavior when tracing function execution events is to include the number of bytes in the serialized inputs and outputs for function calls. This behavior provides minimal information about what the inputs and outputs look like without bloating the logs or inadvertently exposing sensitive information. Setting this property to true causes the default function logging to log the entire contents of function inputs and outputs. |
traceReplayEvents | false | A value indicating whether to write orchestration replay events to Application Insights. |
eventGridTopicEndpoint | The URL of an Azure Event Grid custom topic endpoint. When this property is set, orchestration life-cycle notification events are published to this endpoint. This property supports App Settings resolution. | |
eventGridKeySettingName | The name of the app setting containing the key used for authenticating with the Azure Event Grid custom topic at EventGridTopicEndpoint . |
|
eventGridPublishRetryCount | 0 | The number of times to retry if publishing to the Event Grid Topic fails. |
eventGridPublishRetryInterval | 5 minutes | The Event Grid publishes retry interval in the hh:mm:ss format. |
eventGridPublishEventTypes | A list of event types to publish to Event Grid. If not specified, all event types will be published. Allowed values include Started , Completed , Failed , Terminated . |
|
useAppLease | true | When set to true , apps will require acquiring an app-level blob lease before processing task hub messages. For more information, see the disaster recovery and geo-distribution documentation. Available starting in v2.3.0. |
useLegacyPartitionManagement | false | When set to false , uses a partition management algorithm that reduces the possibility of duplicate function execution when scaling out. Available starting in v2.3.0. |
useGracefulShutdown | false | (Preview) Enable gracefully shutting down to reduce the chance of host shutdowns failing in-process function executions. |
maxEntityOperationBatchSize(2.6.1) | Consumption plan: 50 Dedicated/Premium plan: 5000 |
The maximum number of entity operations that are processed as a batch. If set to 1, batching is disabled, and each operation message is processed by a separate function invocation. |
Many of these settings are for optimizing performance. For more information, see Performance and scale.