Durable Functions types and features (Azure Functions)

Durable Functions is an extension of Azure Functions. You can use Durable Functions for stateful orchestration of function execution. A durable function is a solution that's made up of different Azure functions. Functions can play different roles in a durable function orchestration.

This article gives you an overview of the types of functions you can use in a Durable Functions orchestration. The article includes some common patterns you can use to connect functions. Learn how Durable Functions can help you solve your app development challenges.

An image that shows the types of durable functions

Types of durable functions

You can use four durable function types in Azure Functions: activity, orchestrator, entity, and client.

Activity functions

Activity functions are the basic unit of work in a durable function orchestration. Activity functions are the functions and tasks that are orchestrated in the process. For example, you might create a durable function to process an order. The tasks involve checking the inventory, charging the customer, and creating a shipment. Each task would be an activity function.

Activity functions aren't restricted in the type of work you can do in them. You can write an activity function in any language that Durable Functions support. The durable task framework guarantees that each called activity function will be executed at least once during an orchestration.

Use an activity trigger to trigger an activity function. .NET functions receive a DurableActivityContext as a parameter. You can also bind the trigger to any other object to pass in inputs to the function. In JavaScript, you can access an input via the <activity trigger binding name> property on the context.bindings object.

Your activity function can also return values to the orchestrator. If you send or return a large number of values from an activity function, you can use tuples or arrays. You can trigger an activity function only from an orchestration instance. Although an activity function and another function (like an HTTP-triggered function) might share some code, each function can have only one trigger.

For more information and for examples, see Activity functions.

Orchestrator functions

Orchestrator functions describe how actions are executed and the order in which actions are executed. Orchestrator functions describe the orchestration in code (C# or JavaScript) as shown in Durable Functions patterns and technical concepts. An orchestration can have many different types of actions, including activity functions, sub-orchestrations, waiting for external events, and timers. Orchestrator functions can also interact with entity functions.

An orchestrator function must be triggered by an orchestration trigger.

An orchestrator is started by an orchestrator client. You can trigger the orchestrator from any source (HTTP, queue, event stream). Each instance of an orchestration has an instance identifier. The instance identifier can be autogenerated (recommended) or user-generated. You can use the instance identifier to manage instances of the orchestration.

For more information and for examples, see Orchestration triggers.

Entity functions (preview)

Entity functions define operations for reading and updating small pieces of state, known as durable entities. Like orchestrator functions, entity functions are functions with a special trigger type, entity trigger. Unlike orchestrator functions, entity functions do not have any specific code constraints. Entity functions also manage state explicitly rather than implicitly representing state via control flow.

Note

Entity functions and related functionality is only available in Durable Functions 2.0 and above.

For more information about entity functions, see the Entity Functions preview feature documentation.

Client functions

Client functions are triggered functions that create and manage instances of orchestrations and entities. They are effectively the entry point for interacting with Durable Functions. You can trigger a client function from any source (HTTP, queue, event stream, etc.). A client function uses the orchestration client binding to create and manage durable orchestrations and entities.

The most basic example of a client function is an HTTP-triggered function that starts an orchestrator function, and then returns a check status response. For an example, see HTTP API URL discovery.

For more information and for examples, see Orchestration client.

Features and patterns

The next sections describe the features and patterns of Durable Functions types.

Sub-orchestrations

Orchestrator functions can call activity functions, but they can also call other orchestrator functions. For example, you can build a larger orchestration out of a library of orchestrator functions. Or, you can run multiple instances of an orchestrator function in parallel.

For more information and for examples, see Sub-orchestrations.

Durable timers

Durable Functions provides durable timers that you can use in orchestrator functions to implement delays or to set up timeouts on async actions. Use durable timers in orchestrator functions instead of Thread.Sleep and Task.Delay (C#) or setTimeout() and setInterval() (JavaScript).

For more information and for examples, see Durable timers.

External events

Orchestrator functions can wait for external events to update an orchestration instance. This Durable Functions feature often is useful for handling a human interaction or other external callbacks.

For more information and for examples, see External events.

Error handling

Use code to implement Durable Functions orchestrations. You can use the error-handling features of the programming language. Patterns like try/catch work in your orchestration.

Durable Functions also come with built-in retry policies. An action can delay and retry activities automatically when an exception occurs. You can use retries to handle transient exceptions without abandoning the orchestration.

For more information and for examples, see Error handling.

Cross-function app communication

Although a durable orchestration runs in the context of a single function app, you can use patterns to coordinate orchestrations across many function apps. Cross-app communication might occur over HTTP, but using the durable framework for each activity means you can still maintain a durable process across two apps.

The following examples demonstrate cross-function app orchestration in C# and JavaScript. In each example, one activity starts the external orchestration. Another activity retrieves and returns the status. The orchestrator waits for the status to be Complete before it continues.

Here are some examples of cross-function app orchestration:

C#

[FunctionName("OrchestratorA")]
public static async Task RunRemoteOrchestrator(
    [OrchestrationTrigger] DurableOrchestrationContext context)
{
    // Do some work...

    // Call a remote orchestration.
    string statusUrl = await context.CallActivityAsync<string>(
        "StartRemoteOrchestration", "OrchestratorB");

    // Wait for the remote orchestration to complete.
    while (true)
    {
        bool isComplete = await context.CallActivityAsync<bool>("CheckIsComplete", statusUrl);
        if (isComplete)
        {
            break;
        }

        await context.CreateTimer(context.CurrentUtcDateTime.AddMinutes(1), CancellationToken.None);
    }

    // B is done. Now, go do more work...
}

[FunctionName("StartRemoteOrchestration")]
public static async Task<string> StartRemoteOrchestration([ActivityTrigger] string orchestratorName)
{
    using (var response = await HttpClient.PostAsync(
        $"https://appB.azurewebsites.net/orchestrations/{orchestratorName}",
        new StringContent("")))
    {
        string statusUrl = await response.Content.ReadAsAsync<string>();
        return statusUrl;
    }
}

[FunctionName("CheckIsComplete")]
public static async Task<bool> CheckIsComplete([ActivityTrigger] string statusUrl)
{
    using (var response = await HttpClient.GetAsync(statusUrl))
    {
        // 200 = Complete, 202 = Running
        return response.StatusCode == HttpStatusCode.OK;
    }
}

JavaScript (Functions 2.x only)

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

module.exports = df.orchestrator(function*(context) {
    // Do some work...

    // Call a remote orchestration.
    const statusUrl = yield context.df.callActivity("StartRemoteOrchestration", "OrchestratorB");

    // Wait for the remote orchestration to complete.
    while (true) {
        const isComplete = yield context.df.callActivity("CheckIsComplete", statusUrl);
        if (isComplete) {
            break;
        }

        const waitTime = moment(context.df.currentUtcDateTime).add(1, "m").toDate();
        yield context.df.createTimer(waitTime);
    }

    // B is done. Now, go do more work...
});
const request = require("request-promise-native");

module.exports = async function(context, orchestratorName) {
    const options = {
        method: "POST",
        uri: `https://appB.azurewebsites.net/orchestrations/${orchestratorName}`,
        body: ""
    };

    const statusUrl = await request(options);
    return statusUrl;
};
const request = require("request-promise-native");

module.exports = async function(context, statusUrl) {
    const options = {
        method: "GET",
        uri: statusUrl,
        resolveWithFullResponse: true,
    };

    const response = await request(options);
    // 200 = Complete, 202 = Running
    return response.statusCode === 200;
};

Next steps

To get started, create your first durable function in C# or JavaScript.