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.
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 in the chaining sample
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.
[Function("E1_HelloSequence")]
public static async Task> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var outputs = new List();
outputs.Add(await context.CallActivityAsync("E1_SayHello", "Tokyo"));
outputs.Add(await context.CallActivityAsync("E1_SayHello", "Seattle"));
outputs.Add(await context.CallActivityAsync("E1_SayHello", "London"));
return outputs;
}
In the isolated worker model, C# orchestration functions include a parameter of type TaskOrchestrationContext from the Microsoft.DurableTask package. Use this context object to call activity functions and pass input parameters by using its CallActivityAsync method.
In the in-process model, C# orchestration functions include a parameter of type DurableOrchestrationContext, which exists in the Microsoft.Azure.WebJobs.Extensions.DurableTask assembly. Use this context object to call other activity functions and pass input parameters by 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 the function returns at the end.
V3 programming model
function.json
If you develop in Visual Studio Code or the Azure portal, here's the orchestrator function.json file.
The key setting is the orchestrationTrigger binding type. All orchestrator functions must use this trigger type.
Warning
To follow the "no I/O" rule for orchestrator functions, don't use input or output bindings with the orchestrationTrigger trigger binding. If you need other input or output bindings, use them in activityTrigger functions that the orchestrator calls. For more information, see orchestrator function code constraints.
All JavaScript orchestration functions must include the durable-functions module. It's a library that enables you to write Durable Functions in JavaScript. Three key differences between an orchestrator function and other JavaScript functions:
The function is wrapped in a call to the durable-functions module's orchestrator method (here df).
The function is synchronous. Because the orchestrator method calls context.done, the function returns.
The context object contains a df durable orchestration context object that lets you call other activity functions and pass input parameters using its callActivity method. The code calls E1_SayHello three times in sequence with different parameter values, using yield to indicate the execution should wait on the async activity function calls to be returned. The return value of each call is added to the outputs array, which is returned at the end of the function.
All JavaScript orchestration functions must include the durable-functions module. This module enables you to write Durable Functions in JavaScript. To use the V4 node programming model, you need to install the v3.x version of durable-functions.
Two key differences between an orchestrator function and other JavaScript functions:
The function is synchronous. The function returns.
The context object contains a df durable orchestration context object that lets you call other activity functions and pass input parameters using its callActivity method. The code calls sayHello three times in sequence with different parameter values, using yield to indicate the execution should wait on the async activity function calls to be returned. The return value of each call is added to the outputs array, which is returned at the end of the function.
Note
Python Durable Functions are available for the Functions 3.0 runtime only.
function.json
If you use Visual Studio Code or the Azure portal for development, here's the content of the function.json file for the orchestrator function. Most orchestrator function.json files look almost exactly like this.
The important thing is the orchestrationTrigger binding type. All orchestrator functions must use this trigger type.
Warning
To abide by the "no I/O" rule of orchestrator functions, don't use any input or output bindings when using the orchestrationTrigger trigger binding. If other input or output bindings are needed, use them in the context of activityTrigger functions, which the orchestrator calls. For more information, see the orchestrator function code constraints article.
All Python orchestration functions must include the durable-functions package. It's a library that enables you to write Durable Functions in Python. Two key differences between an orchestrator function and other Python functions:
The file registers the orchestrator function by stating main = df.Orchestrator.create() at the end of the file. This statement helps distinguish it from other helper functions declared in the file.
The context object lets you call other activity functions and pass input parameters by using its call_activity method. The code calls E1_SayHello three times in sequence with different parameter values, using yield to indicate the execution should wait on the async activity function calls to be returned. The return value of each call is returned at the end of the function.
The orchestrator calls the Hello activity three times in sequence with different parameter values. The return value of each call is collected in the $output array.
@FunctionName("E1_HelloSequence")
public String helloSequence(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
String result = "";
result += ctx.callActivity("SayHello", "Tokyo", String.class).await();
result += ", " + ctx.callActivity("SayHello", "London", String.class).await();
result += ", " + ctx.callActivity("SayHello", "Seattle", String.class).await();
return result;
}
The orchestrator calls SayHello three times in sequence with different parameter values. The callActivity method schedules activity execution, and await() waits for the result. The return value of each call is appended to the result string, 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 System.Threading.Tasks;
using Microsoft.DurableTask;
[DurableTask]
public class GreetingOrchestration : TaskOrchestrator
{
public override async Task RunAsync(TaskOrchestrationContext context, string name)
{
// Step 1: Say hello to the person
string greeting = await context.CallActivityAsync(nameof(SayHelloActivity), name);
// Step 2: Process the greeting
string processedGreeting = await context.CallActivityAsync(nameof(ProcessGreetingActivity), greeting);
// Step 3: Finalize the response
string finalResponse = await context.CallActivityAsync(nameof(FinalizeResponseActivity), processedGreeting);
return finalResponse;
}
}
All .NET orchestrators inherit from TaskOrchestrator. 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.
The following code shows an orchestrator that calls three activities in sequence:
import {
OrchestrationContext,
TOrchestrator,
} from "@microsoft/durabletask-js";
const greetingOrchestrator: TOrchestrator = async function* (
ctx: OrchestrationContext,
name: string
): any {
// Step 1: Say hello to the person
const greeting: string = yield ctx.callActivity(sayHello, name);
// Step 2: Process the greeting
const processedGreeting: string = yield ctx.callActivity(processGreeting, greeting);
// Step 3: Finalize the response
const finalResponse: string = yield ctx.callActivity(finalizeResponse, processedGreeting);
return finalResponse;
};
All JavaScript orchestrators are generator functions (async function*) that use yield to call activities. The orchestration context's callActivity method schedules activity execution. The code calls three activities in sequence, passing each activity's output to the next.
The following code shows an orchestrator that calls three activities in sequence:
from durabletask import task
# Orchestrator function
def function_chaining_orchestrator(ctx: task.OrchestrationContext, name: str) -> str:
"""Orchestrator that demonstrates function chaining pattern."""
# Call first activity
greeting = yield ctx.call_activity(say_hello, input=name)
# Call second activity with the result from first activity
processed_greeting = yield ctx.call_activity(process_greeting, input=greeting)
# Call third activity with the result from second activity
final_response = yield ctx.call_activity(finalize_response, input=processed_greeting)
return final_response
All Python orchestrators are generator functions that use yield to call activities. The orchestration context's call_activity method schedules activity execution. The code calls three activities in sequence, passing each activity's output to the next.
This sample is shown for .NET, JavaScript, Java, and Python.
The following code shows an orchestrator that calls three activities in sequence:
import com.microsoft.durabletask.DurableTaskGrpcWorker;
import com.microsoft.durabletask.DurableTaskSchedulerWorkerExtensions;
import com.microsoft.durabletask.TaskOrchestration;
import com.microsoft.durabletask.TaskOrchestrationFactory;
DurableTaskGrpcWorker worker = DurableTaskSchedulerWorkerExtensions.createWorkerBuilder(connectionString)
.addOrchestration(new TaskOrchestrationFactory() {
@Override
public String getName() { return "ActivityChaining"; }
@Override
public TaskOrchestration create() {
return ctx -> {
String input = ctx.getInput(String.class);
// Call activities in sequence, passing output from one to the next
String x = ctx.callActivity("Reverse", input, String.class).await();
String y = ctx.callActivity("Capitalize", x, String.class).await();
String z = ctx.callActivity("ReplaceWhitespace", y, String.class).await();
ctx.complete(z);
};
}
})
.build();
In Java, orchestrators are defined using TaskOrchestrationFactory. The context's callActivity method schedules activity execution, and await() waits for the result. The code calls three activities in sequence, passing each activity's output to the next.
In the isolated worker model, activities use the ActivityTrigger attribute and can bind directly to the input type that the orchestrator passes to the activity.
In-process model
[FunctionName("E1_SayHello")]
public static string SayHello([ActivityTrigger] IDurableActivityContext context)
{
string name = context.GetInput();
return $"Hello {name}!";
}
Activities use the ActivityTrigger attribute. Use IDurableActivityContext for activity actions, like reading input with GetInput.
E1_SayHello formats a greeting string.
Instead of binding to IDurableActivityContext, bind directly to the type you pass into the activity function. For example:
The function.json file for the activity function E1_SayHello is similar to that of E1_HelloSequence except that it uses an activityTrigger binding type instead of an orchestrationTrigger binding type.
Use the activityTrigger binding for all activity functions that an orchestration function calls.
The implementation of E1_SayHello is a relatively simple string formatting operation.
E1_SayHello/index.js
module.exports = function (context) {
context.done(null, `Hello ${context.bindings.name}!`);
};
Unlike the orchestration function, an activity function doesn't need special setup. The orchestrator passes input on the context.bindings object under the name of the activityTrigger binding—in this case, context.bindings.name. Set the binding name as a parameter of the exported function to access it directly, as the sample does.
Unlike the orchestration function, an activity function doesn't need special setup. The orchestrator passes input as the first argument to the function. The second argument is the invocation context, which this example doesn't use.
E1_SayHello/function.json
The function.json file for the activity function E1_SayHello is similar to that of E1_HelloSequence except that it uses an activityTrigger binding type instead of an orchestrationTrigger binding type.
Unlike the orchestrator function, an activity function needs no special setup. The input that the orchestrator function passes to it is directly accessible as the parameter to the function.
The Hello activity takes a name and returns a greeting string:
param($name)
"Hello $name!"
Unlike the orchestrator function, an activity function needs no special setup. The input that the orchestrator function passes to it is directly accessible as the parameter to the function.
The SayHello activity takes a name and returns a greeting string:
Unlike the orchestrator function, an activity function needs no special setup. The input that the orchestrator function passes to it is directly accessible as the parameter to the function.
Activities in the Durable Task SDK inherit from TaskActivity:
using System.Threading.Tasks;
using Microsoft.DurableTask;
using Microsoft.Extensions.Logging;
[DurableTask]
public class SayHelloActivity : TaskActivity
{
private readonly ILogger _logger;
public SayHelloActivity(ILogger logger)
{
_logger = logger;
}
public override Task RunAsync(TaskActivityContext context, string name)
{
_logger.LogInformation("Activity SayHello called with name: {Name}", name);
return Task.FromResult($"Hello {name}!");
}
}
[DurableTask]
public class ProcessGreetingActivity : TaskActivity
{
public override Task RunAsync(TaskActivityContext context, string greeting)
{
return Task.FromResult($"{greeting} How are you today?");
}
}
[DurableTask]
public class FinalizeResponseActivity : TaskActivity
{
public override Task 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.
Activities in the Durable Task SDK are simple functions:
Unlike orchestrators, activities can perform I/O operations like HTTP calls, database queries, and file access. You pass the input directly as a parameter.
Activities in the Durable Task SDK are simple functions:
from durabletask import task
def say_hello(ctx: task.ActivityContext, name: str) -> str:
"""First activity that greets the user."""
return f"Hello {name}!"
def process_greeting(ctx: task.ActivityContext, greeting: str) -> str:
"""Second activity that processes the greeting."""
return f"{greeting} How are you today?"
def finalize_response(ctx: task.ActivityContext, response: str) -> str:
"""Third activity that finalizes the response."""
return f"{response} I hope you're doing well!"
Unlike orchestrators, activities can perform I/O operations like HTTP calls, database queries, and file access. You pass the input directly as a parameter.
This sample is shown for .NET, JavaScript, Java, and Python.
Define activities in Java by using TaskActivityFactory:
In the isolated worker model, use DurableTaskClient to schedule an orchestration and return an HTTP response with URLs for managing the new instance.
In-process model
public static class HttpStart
{
[FunctionName("HttpStart")]
public static async Task 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
In the in-process model, 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.
Use df.getClient to get a DurableOrchestrationClient object. Use the client to start an orchestration and return an HTTP response that includes URLs to check the status of the new orchestration.
To manage and interact with orchestrators, add a durableClient input binding. Specify the binding in the extraInputs argument when you register the function. Get the durableClient input by calling df.input.durableClient().
Use df.getClient to get a DurableClient object. Use the client to start an orchestration and return an HTTP response that includes URLs to check the status of the new orchestration.
To interact with orchestrators, the function must include a durableClient input binding.
HttpStart/__init__.py
import logging
import azure.functions as func
import azure.durable_functions as df
async def main(req: func.HttpRequest, starter: str) -> func.HttpResponse:
client = df.DurableOrchestrationClient(starter)
instance_id = await client.start_new(req.route_params["functionName"], None, None)
logging.info(f"Started orchestration with ID = '{instance_id}'.")
return client.create_check_status_response(req, instance_id)
Use the DurableOrchestrationClient constructor to create a Durable Functions client. Use the client to start an orchestration and return an HTTP response that includes URLs to check the status of the new orchestration.
To interact with orchestrators, use Start-DurableOrchestration to start an orchestration and New-DurableOrchestrationCheckStatusResponse to return an HTTP response that includes URLs to check the status.
@FunctionName("StartHelloSequence")
public HttpResponseMessage startHelloSequence(
@HttpTrigger(name = "req", methods = {HttpMethod.POST},
authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage request,
@DurableClientInput(name = "durableContext") DurableClientContext durableContext) {
DurableTaskClient client = durableContext.getClient();
String instanceId = client.scheduleNewOrchestrationInstance("E1_HelloSequence");
Use an HTTP-triggered client function to start the Java chaining orchestration:
```java
@FunctionName("StartOrchestration")
public HttpResponseMessage startOrchestration(
@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage> request,
@DurableClientInput(name = "durableContext") DurableClientContext durableContext,
final ExecutionContext context) {
DurableTaskClient client = durableContext.getClient();
String instanceId = client.scheduleNewOrchestrationInstance("HelloCities");
context.getLogger().info("Started orchestration with ID = '" + instanceId + "'.");
return durableContext.createCheckStatusResponse(request, instanceId);
}
To interact with orchestrators, add a DurableClientInput binding. Use the client to start an orchestration and return an HTTP response that includes URLs to check the status of the new orchestration.
using System;
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()}");
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.
import {
DurableTaskAzureManagedClientBuilder,
} from "@microsoft/durabletask-js-azuremanaged";
const connectionString =
process.env.DURABLE_TASK_SCHEDULER_CONNECTION_STRING ||
"Endpoint=http://localhost:8080;Authentication=None;TaskHub=default";
const client = new DurableTaskAzureManagedClientBuilder()
.connectionString(connectionString)
.build();
// Schedule a new orchestration instance
const instanceId = await client.scheduleNewOrchestration(greetingOrchestrator, "World");
console.log(`Started orchestration with ID: ${instanceId}`);
// Wait for the orchestration to complete
const state = await client.waitForOrchestrationCompletion(instanceId, true, 30);
console.log(`Orchestration completed with result: ${state?.serializedOutput}`);
Create the DurableTaskAzureManagedClientBuilder by using a connection string to the Durable Task Scheduler. Use scheduleNewOrchestration to start an orchestration and waitForOrchestrationCompletion to wait for completion.
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
# Create the client
client = DurableTaskSchedulerClient(
host_address=endpoint,
secure_channel=endpoint != "http://localhost:8080",
taskhub=taskhub_name,
token_credential=credential
)
# Schedule a new orchestration instance
instance_id = client.schedule_new_orchestration(
function_chaining_orchestrator,
input="World"
)
print(f"Started orchestration with ID: {instance_id}")
# Wait for the orchestration to complete
result = client.wait_for_orchestration_completion(instance_id, timeout=60)
if result and result.runtime_status == "COMPLETED":
print(f"Orchestration completed with result: {result.serialized_output}")
The DurableTaskSchedulerClient connects to the Durable Task Scheduler. Use schedule_new_orchestration to start an orchestration and wait_for_orchestration_completion to wait for completion.
This sample is shown for .NET, JavaScript, Java, and Python.
import java.time.Duration;
import com.microsoft.durabletask.DurableTaskClient;
import com.microsoft.durabletask.NewOrchestrationInstanceOptions;
import com.microsoft.durabletask.OrchestrationMetadata;
import com.microsoft.durabletask.azuremanaged.DurableTaskSchedulerClientExtensions;
// Create the client
DurableTaskClient client = DurableTaskSchedulerClientExtensions
.createClientBuilder(connectionString)
.build();
// Schedule a new orchestration instance
String instanceId = client.scheduleNewOrchestrationInstance(
"ActivityChaining",
new NewOrchestrationInstanceOptions().setInput("Hello, world!"));
System.out.println("Started orchestration with ID: " + instanceId);
// Wait for the orchestration to complete
OrchestrationMetadata result = client.waitForInstanceCompletion(
instanceId,
Duration.ofSeconds(30),
true);
System.out.println("Orchestration completed with result: " + result.readOutputAs(String.class));
Create the DurableTaskClient by using a connection string. Use scheduleNewOrchestrationInstance to start an orchestration and waitForInstanceCompletion to wait for completion.
Run the function chaining 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):
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):
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:
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
Start the worker to register the orchestrator and activities.
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.