Bindings for Durable Functions (Azure Functions)

The Durable Functions extension introduces two new trigger bindings that control the execution of orchestrator and activity functions. It also introduces an output binding that acts as a client for the Durable Functions runtime.

Orchestration triggers

The orchestration trigger enables you to author durable orchestrator functions. This trigger supports starting new orchestrator function instances and resuming existing orchestrator function instances that are "awaiting" a task.

When you use the Visual Studio tools for Azure Functions, the orchestration trigger is configured using the OrchestrationTriggerAttribute .NET attribute.

When you write orchestrator functions in scripting languages (for example, in the Azure portal), 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. This is the value 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.

Internally this trigger binding polls a series of queues in the default storage account for the function app. These queues are internal implementation details of the extension, which is why they are not explicitly configured in the binding properties.

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 is 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 is 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.

Trigger usage

The orchestration trigger binding supports both inputs and outputs. Here are some things to know about input and output handling:

  • inputs - Orchestration functions support only DurableOrchestrationContext as a parameter type. Deserialization of inputs directly in the function signature is not supported. Code must use the GetInput<T> method to fetch orchestrator function inputs. These inputs must be JSON-serializable types.
  • 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. If a function returns Task or void, a null value will be saved as the output.

Trigger sample

The following is an example of what the simplest "Hello World" orchestrator function might look like:

C#

[FunctionName("HelloWorld")]
public static string Run([OrchestrationTrigger] DurableOrchestrationContext context)
{
    string name = context.GetInput<string>();
    return $"Hello {name}!";
}

JavaScript (Functions v2 only)

const df = require("durable-functions");

module.exports = df.orchestrator(function*(context) {
    const name = context.df.getInput();
    return `Hello ${name}!`;
});

Note

JavaScript orchestrators should use return. The durable-functions library takes care of calling the context.done method.

Most orchestrator functions call activity functions, so here is a "Hello World" example that demonstrates how to call an activity function:

C#

[FunctionName("HelloWorld")]
public static async Task<string> Run(
    [OrchestrationTrigger] DurableOrchestrationContext context)
{
    string name = context.GetInput<string>();
    string result = await context.CallActivityAsync<string>("SayHello", name);
    return result;
}

JavaScript (Functions v2 only)

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;
});

Activity triggers

The activity trigger enables you to author functions that are called by orchestrator functions.

If you're using Visual Studio, the activity trigger is configured using the ActivityTriggerAttribute .NET attribute.

If you're using VS Code or the Azure portal for development, 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 is the value that orchestrator functions use to invoke this activity function. This property is optional. If not specified, the name of the function is used.

Internally this trigger binding polls a queue in the default storage account for the function app. This queue is an internal implementation detail of the extension, which is why it is not explicitly configured in the binding properties.

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 is 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 orchestration history table in Azure Table storage.

Warning

The storage backend for activity functions is an implementation detail and user code should not interact with these storage entities directly.

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 functions natively use DurableActivityContext as a parameter type. Alternatively, an activity function can be declared with any parameter type that is JSON-serializable. When you use DurableActivityContext, you can call GetInput<T> to fetch and deserialize the activity function input.
  • 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. If a function returns Task or void, a null value will be saved as the output.
  • metadata - Activity functions can bind to a string instanceId parameter to get the instance ID of the parent orchestration.

Trigger sample

The following is an example of what a simple "Hello World" activity function might look like:

C#

[FunctionName("SayHello")]
public static string SayHello([ActivityTrigger] DurableActivityContext helloContext)
{
    string name = helloContext.GetInput<string>();
    return $"Hello {name}!";
}

JavaScript (Functions v2 only)

module.exports = function(context) {
    context.done(null, `Hello ${context.bindings.name}!`);
};

The default parameter type for the ActivityTriggerAttribute binding is DurableActivityContext. However, activity triggers also support binding directly to JSON-serializeable types (including primitive types), so the same function could be simplified as follows:

C#

[FunctionName("SayHello")]
public static string SayHello([ActivityTrigger] string name)
{
    return $"Hello {name}!";
}

JavaScript (Functions v2 only)

module.exports = function(context, name) {
    context.done(null, `Hello ${name}!`);
};

Passing multiple parameters

It is not possible to pass multiple parameters to an activity function directly. The recommendation in this case is to pass in an array of objects or to use ValueTuples objects.

The following sample is using new features of ValueTuples added with C# 7:

[FunctionName("GetCourseRecommendations")]
public static async Task<dynamic> RunOrchestrator(
    [OrchestrationTrigger] DurableOrchestrationContext context)
{
    string major = "ComputerScience";
    int universityYear = context.GetInput<int>();

    dynamic courseRecommendations = await context.CallActivityAsync<dynamic>("CourseRecommendations", (major, universityYear));
    return courseRecommendations;
}

[FunctionName("CourseRecommendations")]
public static async Task<dynamic> Mapper([ActivityTrigger] DurableActivityContext inputs)
{
    // parse input for student's major and year in university 
    (string Major, int UniversityYear) studentInfo = inputs.GetInput<(string, int)>();

    // retrieve and return course recommendations by major and university year
    return new {
        major = studentInfo.Major,
        universityYear = studentInfo.UniversityYear,
        recommendedCourses = new []
        {
            "Introduction to .NET Programming",
            "Introduction to Linux",
            "Becoming an Entrepreneur"
        }
    };
}

Orchestration client

The orchestration client binding enables you to write functions which interact with orchestrator 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.

If you're using Visual Studio, you can bind to the orchestration client by using the OrchestrationClientAttribute .NET attribute.

If you're using scripting languages (e.g. .csx or .js files) for development, the orchestration 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 from host.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.

Client usage

In C# functions, you typically bind to DurableOrchestrationClient, which gives you full access to all client APIs supported by Durable Functions. APIs on the client object include:

Alternatively, you can bind to IAsyncCollector<T> where T is StartOrchestrationArgs or JObject.

See the DurableOrchestrationClient API documentation for additional details on these operations.

Client sample (Visual Studio development)

Here is an example queue-triggered function that starts a "HelloWorld" orchestration.

[FunctionName("QueueStart")]
public static Task Run(
    [QueueTrigger("durable-function-trigger")] string input,
    [OrchestrationClient] DurableOrchestrationClient starter)
{
    // Orchestration input comes from the queue message content.
    return starter.StartNewAsync("HelloWorld", input);
}

Client sample (not Visual Studio)

If you're not using Visual Studio for development, you can create the following function.json file. This example shows how to configure a queue-triggered function that uses the durable orchestration client binding:

{
  "bindings": [
    {
      "name": "input",
      "type": "queueTrigger",
      "queueName": "durable-function-trigger",
      "direction": "in"
    },
    {
      "name": "starter",
      "type": "orchestrationClient",
      "direction": "in"
    }
  ],
  "disabled": false
} 

Following are language-specific samples that start new orchestrator function instances.

C# Sample

The following sample shows how to use the durable orchestration client binding to start a new function instance from a C# script function:

#r "Microsoft.Azure.WebJobs.Extensions.DurableTask"

public static Task<string> Run(string input, DurableOrchestrationClient starter)
{
    return starter.StartNewAsync("HelloWorld", input);
}

JavaScript Sample

The following sample shows how to use the durable orchestration client binding to start a new function instance from a JavaScript function:

module.exports = function (context, input) {
    var id = generateSomeUniqueId();
    context.bindings.starter = [{
        FunctionName: "HelloWorld",
        Input: input,
        InstanceId: id
    }];

    context.done(null, id);
};

More details on starting instances can be found in Instance management.

host.json settings

Configuration settings for Durable Functions.

{
  "durableTask": {
    "HubName": "MyTaskHub",
    "ControlQueueBatchSize": 32,
    "PartitionCount": 4,
    "ControlQueueVisibilityTimeout": "00:05:00",
    "WorkItemQueueVisibilityTimeout": "00:05:00",
    "MaxConcurrentActivityFunctions": 10,
    "MaxConcurrentOrchestratorFunctions": 10,
    "AzureStorageConnectionStringName": "AzureWebJobsStorage",
    "TraceInputsAndOutputs": false,
    "LogReplayEvents": false,
    "EventGridTopicEndpoint": "https://topic_name.westus2-1.eventgrid.azure.net/api/events",
    "EventGridKeySettingName":  "EventGridKey",
    "EventGridPublishRetryCount": 3,
    "EventGridPublishRetryInterval": "00:00:30"
  }
}

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 DurableFunctionsHub. For more information, see Task hubs.

Property Default Description
HubName DurableFunctionsHub Alternate task hub names can be used to isolate multiple Durable Functions applications from each other, even if theyre using the same storage backend.
ControlQueueBatchSize 32 The number of messages to pull from the control queue at a time.
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 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 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.
AzureStorageConnectionStringName AzureWebJobsStorage The name of the app setting that has the Azure Storage connection string used to manage the underlying Azure Storage resources.
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 provides minimal information about what the inputs and outputs look like without bloating the logs or inadvertently exposing sensitive information to the logs. Setting this property to true causes the default function logging to log the entire contents of function inputs and outputs.
LogReplayEvents 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.

Many of these are for optimizing performance. For more information, see Performance and scale.

Next steps