Back to Blog
2 min read

Azure Durable Functions: Orchestration Patterns

Durable Functions extend Azure Functions with stateful workflows. Chain, fan-out/fan-in, and human interaction patterns become simple to implement.

Function Chaining

Execute functions in sequence, passing outputs as inputs.

[FunctionName("ChainOrchestrator")]
public static async Task<string> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var x = await context.CallActivityAsync<string>("Step1", null);
    var y = await context.CallActivityAsync<string>("Step2", x);
    var z = await context.CallActivityAsync<string>("Step3", y);
    return z;
}

[FunctionName("Step1")]
public static string Step1([ActivityTrigger] string input) => "Result1";

[FunctionName("Step2")]
public static string Step2([ActivityTrigger] string input) => $"{input}-Result2";

[FunctionName("Step3")]
public static string Step3([ActivityTrigger] string input) => $"{input}-Result3";

Fan-Out/Fan-In

Process items in parallel, then aggregate results.

[FunctionName("FanOutFanIn")]
public static async Task<int[]> RunFanOut(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var tasks = new List<Task<int>>();

    // Fan out
    for (int i = 0; i < 10; i++)
    {
        tasks.Add(context.CallActivityAsync<int>("ProcessItem", i));
    }

    // Fan in
    int[] results = await Task.WhenAll(tasks);
    return results;
}

[FunctionName("ProcessItem")]
public static int ProcessItem([ActivityTrigger] int item)
{
    return item * item;
}

Human Interaction Pattern

Wait for external events with timeout.

[FunctionName("ApprovalWorkflow")]
public static async Task<string> RunApproval(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var requestId = context.GetInput<string>();

    // Send approval request
    await context.CallActivityAsync("SendApprovalRequest", requestId);

    // Wait for approval with timeout
    using var cts = new CancellationTokenSource();
    var timeoutTask = context.CreateTimer(
        context.CurrentUtcDateTime.AddHours(72), cts.Token);
    var approvalTask = context.WaitForExternalEvent<bool>("ApprovalEvent");

    var winner = await Task.WhenAny(timeoutTask, approvalTask);

    if (winner == approvalTask)
    {
        cts.Cancel();
        return approvalTask.Result ? "Approved" : "Rejected";
    }

    return "Timed out";
}

Raising External Events

# HTTP endpoint to raise event
POST /runtime/webhooks/durabletask/instances/{instanceId}/raiseEvent/ApprovalEvent
Content-Type: application/json

true

Sub-Orchestrations

Compose complex workflows from smaller orchestrations.

[FunctionName("MainOrchestrator")]
public static async Task RunMain(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    // Call sub-orchestration
    await context.CallSubOrchestratorAsync("SubOrchestrator", "input-data");
}

Eternal Orchestrations

Long-running workflows that restart themselves.

[FunctionName("EternalOrchestrator")]
public static async Task RunEternal(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var counter = context.GetInput<int>();

    await context.CallActivityAsync("DoWork", counter);

    // Wait before next iteration
    await context.CreateTimer(
        context.CurrentUtcDateTime.AddMinutes(5), CancellationToken.None);

    // Restart with incremented counter
    context.ContinueAsNew(counter + 1);
}

Durable Functions make stateful serverless simple.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.