Function chaining in Durable Functions - Hello sequence sample

Function chaining refers to the pattern of executing a sequence of functions in a particular order. Often the output of one function needs to be applied to the input of another function. This article describes the chaining sequence that you create when you complete the Durable Functions quickstart (C#, JavaScript, TypeScript, Python, PowerShell, or Java). For more information about Durable Functions, see Durable Functions overview.

Prerequisites

Note

Version 4 of the Node.js programming model for Azure Functions is generally available. The new v4 model is designed to have a more flexible and intuitive experience for JavaScript and TypeScript developers. Learn more about the differences between v3 and v4 in the migration guide.

In the following code snippets, JavaScript (PM4) denotes programming model V4, the new experience.

The functions

This article explains the following functions in the sample app:

  • E1_HelloSequence: An orchestrator function that calls E1_SayHello multiple times in a sequence. It stores the outputs from the E1_SayHello calls and records the results.
  • E1_SayHello: An activity function that prepends a string with "Hello".
  • HttpStart: An HTTP triggered durable client function that starts an instance of the orchestrator.

E1_HelloSequence orchestrator function

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

E1_SayHello activity function

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

Activities use the ActivityTrigger attribute. Use the provided IDurableActivityContext to perform activity related actions, such as accessing the input value using GetInput<T>.

The implementation of E1_SayHello is a relatively trivial string formatting operation.

Instead of binding to an IDurableActivityContext, you can bind directly to the type that is passed into the activity function. For example:

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

HttpStart client function

You can start an instance of orchestrator function using a client function. You will 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, the function must include a DurableClient input binding. You use the client to start an orchestration. It can also help you return an HTTP response containing URLs for checking the status of the new orchestration.

Run the sample

To execute the E1_HelloSequence orchestration, send the following HTTP POST request to the HttpStart function.

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

Note

The previous HTTP snippet assumes there is an entry in the host.json file which removes the default api/ prefix from all HTTP trigger functions URLs. You can find the markup for this configuration in the host.json file in the samples.

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

The result is an HTTP 202 response, like this (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...)

At this point, the orchestration is queued up and begins to run immediately. The URL in the Location header can be used to check the status of the execution.

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

The result is the status of the orchestration. It runs and completes quickly, so you see it in the Completed state with a response that looks 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"}

As you can see, the runtimeStatus of the instance is Completed and the output contains the JSON-serialized result of the orchestrator function execution.

Note

You can implement similar starter logic for other trigger types, like queueTrigger, eventHubTrigger, or timerTrigger.

Look at the function execution logs. The E1_HelloSequence function started and completed multiple times due to the replay behavior described in the orchestration reliability topic. On the other hand, there were only three executions of E1_SayHello since those function executions do not get replayed.

Next steps

This sample has demonstrated a simple function-chaining orchestration. The next sample shows how to implement the fan-out/fan-in pattern.