Edit

Share via


Function chaining

Function chaining is a pattern where you run a sequence of functions in order. It's common to pass the output of one function to the input of the next. This article describes the chaining sequence you build when you complete the Durable Functions quickstart (C#, JavaScript, TypeScript, Python, PowerShell, or Java). Learn more in Durable Functions overview.

Prerequisites

Function chaining is a pattern where you run a sequence of activities in order. It's common to pass the output of one activity to the input of the next. This article describes the chaining sequence for the Durable Task SDKs for .NET, JavaScript, Python, and Java.

Functions

This article describes these functions in the sample app:

  • E1_HelloSequence: An orchestrator function that calls E1_SayHello multiple times in sequence. It stores each output and records the results.
  • E1_SayHello: An activity function that adds "Hello" to the start of a string.
  • HttpStart: An HTTP-triggered durable client function that starts an instance of the orchestrator.

This article describes these components in the sample app:

  • GreetingOrchestration, greetingOrchestrator, function_chaining_orchestrator, or ActivityChaining: An orchestrator that calls multiple activities in sequence. It stores each output and records the results.
  • Activity functions: Activities that process input and return results. Each activity performs a simple transformation on the input.
  • Client: A client app that starts an instance of the orchestrator and waits for the result.

Orchestrator

[FunctionName("E1_HelloSequence")]
public static async Task<List<string>> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var outputs = new List<string>();

    outputs.Add(await context.CallActivityAsync<string>("E1_SayHello", "Tokyo"));
    outputs.Add(await context.CallActivityAsync<string>("E1_SayHello", "Seattle"));
    outputs.Add(await context.CallActivityAsync<string>("E1_SayHello_DirectInput", "London"));

    // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
    return outputs;
}

All C# orchestration functions must have a parameter of type DurableOrchestrationContext, which exists in the Microsoft.Azure.WebJobs.Extensions.DurableTask assembly. This context object lets you call other activity functions and pass input parameters using its CallActivityAsync method.

The code calls E1_SayHello three times in sequence with different parameter values. The return value of each call is added to the outputs list, which is returned at the end of the function.

This code shows an orchestrator that calls three activities in sequence and passes each output to the next activity:

using Microsoft.DurableTask;

[DurableTask]
public class GreetingOrchestration : TaskOrchestrator<string, string>
{
    public override async Task<string> RunAsync(TaskOrchestrationContext context, string name)
    {
        // Step 1: Say hello to the person
        string greeting = await context.CallActivityAsync<string>(nameof(SayHelloActivity), name);

        // Step 2: Process the greeting
        string processedGreeting = await context.CallActivityAsync<string>(nameof(ProcessGreetingActivity), greeting);

        // Step 3: Finalize the response
        string finalResponse = await context.CallActivityAsync<string>(nameof(FinalizeResponseActivity), processedGreeting);

        return finalResponse;
    }
}

All .NET orchestrators inherit from TaskOrchestrator<TInput, TOutput>. The TaskOrchestrationContext lets you call activities using CallActivityAsync. The code calls three activities in sequence, where each activity receives the output of the previous activity.

Activity

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

Activities use the ActivityTrigger attribute. Use IDurableActivityContext for activity actions, like reading input with GetInput<T>.

E1_SayHello formats a greeting string.

Instead of binding to IDurableActivityContext, bind directly to the type passed into the activity function. For example:

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

Activities in the Durable Task SDK inherit from TaskActivity<TInput, TOutput>:

using Microsoft.DurableTask;
using Microsoft.Extensions.Logging;

[DurableTask]
public class SayHelloActivity : TaskActivity<string, string>
{
    private readonly ILogger<SayHelloActivity> _logger;

    public SayHelloActivity(ILogger<SayHelloActivity> logger)
    {
        _logger = logger;
    }

    public override Task<string> RunAsync(TaskActivityContext context, string name)
    {
        _logger.LogInformation("Activity SayHello called with name: {Name}", name);
        return Task.FromResult($"Hello {name}!");
    }
}

[DurableTask]
public class ProcessGreetingActivity : TaskActivity<string, string>
{
    public override Task<string> RunAsync(TaskActivityContext context, string greeting)
    {
        return Task.FromResult($"{greeting} How are you today?");
    }
}

[DurableTask]
public class FinalizeResponseActivity : TaskActivity<string, string>
{
    public override Task<string> RunAsync(TaskActivityContext context, string response)
    {
        return Task.FromResult($"{response} I hope you're doing well!");
    }
}

Use dependency injection to get services like ILogger. Add the [DurableTask] attribute to register the activity with the worker.

Client

Start an orchestrator function instance from a client function. Use the HttpStart HTTP-triggered function to start instances of E1_HelloSequence.

public static class HttpStart
{
    [FunctionName("HttpStart")]
    public static async Task<HttpResponseMessage> Run(
        [HttpTrigger(AuthorizationLevel.Function, methods: "post", Route = "orchestrators/{functionName}")] HttpRequestMessage req,
        [DurableClient] IDurableClient starter,
        string functionName,
        ILogger log)
    {
        // Function input comes from the request content.
        object eventData = await req.Content.ReadAsAsync<object>();
        string instanceId = await starter.StartNewAsync(functionName, eventData);

        log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

        return starter.CreateCheckStatusResponse(req, instanceId);
    }
}

To interact with orchestrators, add a DurableClient input binding. Use the client to start an orchestration and return an HTTP response that includes URLs to check the status of the new orchestration.

Start an orchestration from a client application. The client schedules the orchestration and can wait for completion.

using Microsoft.DurableTask.Client;

// Create the client
var client = DurableTaskClientBuilder.UseDurableTaskScheduler(connectionString).Build();

// Schedule a new orchestration instance
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
    nameof(GreetingOrchestration),
    input: "World");

Console.WriteLine($"Started orchestration with ID: {instanceId}");

// Wait for the orchestration to complete
OrchestrationMetadata result = await client.WaitForInstanceCompletionAsync(
    instanceId,
    getInputsAndOutputs: true);

Console.WriteLine($"Orchestration completed with result: {result.ReadOutputAs<string>()}");

Create the DurableTaskClient by using a connection string to the Durable Task Scheduler. Use ScheduleNewOrchestrationInstanceAsync to start an orchestration and WaitForInstanceCompletionAsync to wait for completion.

Run the sample

To run the E1_HelloSequence orchestration, send this HTTP POST request to the HttpStart function.

POST http://{host}/orchestrators/E1_HelloSequence

Note

The previous HTTP snippet assumes the sample's host.json file removes the default api/ prefix from all HTTP trigger function URLs. Find this configuration in the sample's host.json file.

For example, if you're running the sample in a function app named myfunctionapp, replace {host} with myfunctionapp.azurewebsites.net.

The request returns HTTP 202 (trimmed for brevity):

HTTP/1.1 202 Accepted
Content-Length: 719
Content-Type: application/json; charset=utf-8
Location: http://{host}/runtime/webhooks/durabletask/instances/96924899c16d43b08a536de376ac786b?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}

(...trimmed...)

The orchestration queues and starts running immediately. Use the URL in the Location header to check execution status.

GET http://{host}/runtime/webhooks/durabletask/instances/96924899c16d43b08a536de376ac786b?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}

The response shows the orchestration status. Because it finishes quickly, the instance is often in the Completed state and returns a response like this (trimmed for brevity):

HTTP/1.1 200 OK
Content-Length: 179
Content-Type: application/json; charset=utf-8

{"runtimeStatus":"Completed","input":null,"output":["Hello Tokyo!","Hello Seattle!","Hello London!"],"createdTime":"2017-06-29T05:24:57Z","lastUpdatedTime":"2017-06-29T05:24:59Z"}

The instance runtimeStatus is Completed, and output contains the JSON-serialized result of the orchestrator function execution.

Note

Implement similar starter logic for other trigger types, like queueTrigger, eventHubTrigger, or timerTrigger.

Review the function execution logs. The E1_HelloSequence function starts and completes multiple times because of the replay behavior described in orchestration reliability. But E1_SayHello runs only three times because activity function executions don't replay.

To run the sample, you need:

  1. Start the Durable Task Scheduler emulator (for local development):

    docker run -d -p 8080:8080 -p 8082:8082 --name dts-emulator mcr.microsoft.com/dts/dts-emulator:latest
    
  2. Start the worker to register the orchestrator and activities.

  3. Run the client to schedule an orchestration and wait for the result.

The client output shows the chained orchestration result:

Started orchestration with ID: abc123
Orchestration completed with result: "Hello World! How are you today? I hope you're doing well!"

The worker logs show each activity runs in sequence and passes its output to the next activity.

Next steps

This sample demonstrates a simple function chaining orchestration. Next, implement the fan-out/fan-in pattern.

This sample demonstrates a simple function chaining orchestration. Next, explore more patterns.

For complete JavaScript SDK examples, see the Durable Task JavaScript SDK samples.