Edit

Quickstart: Build a serverless workflow using Durable Functions

In this quickstart, you use Azure Developer command-line tools to build a serverless workflow that orchestrates multiple tasks running in parallel. You create resources in Azure, verify the code locally, and then deploy it to a new serverless function app running in a Flex Consumption plan in Azure Functions.

The project uses the Azure Developer CLI (azd) to simplify deploying your code to Azure. This deployment follows current best practices for secure and scalable Azure Functions deployments. This quickstart demonstrates the fan-out/fan-in pattern in Durable Functions, an extension that orchestrates stateful workflows with durable execution. The sample uses the Durable Task Scheduler, a fully managed backend for Durable Functions that replaces the Azure Storage backend. The sample fetches article titles in parallel—the orchestration fans out to multiple activities running concurrently, then fans back in to aggregate the results.

By default, the Flex Consumption plan follows a pay-for-what-you-use billing model, which means completing this quickstart incurs a small cost of a few USD cents or less in your Azure account.

Prerequisites

Initialize the project

Use the azd init command to create a local Durable Functions code project from a template.

  1. In your local terminal or command prompt, run this azd init command in an empty folder:

    azd init --template durable-functions-quickstart-dotnet-azd -e dfquickstart-dotnet
    

    This command pulls the project files from the template repository and initializes the project in a new folder. The -e flag sets a name for the current environment. In azd, the environment maintains a unique deployment context for your app, and you can define more than one. The environment name is also used in the name of the resource group you create in Azure.

  2. Change to the project directory and then navigate to the fanoutfanin app folder:

    cd durable-functions-quickstart-dotnet-azd/fanoutfanin
    
  1. In your local terminal or command prompt, run this azd init command in an empty folder:

    azd init --template durable-functions-quickstart-python-azd -e dfquickstart-python
    

    This command pulls the project files from the template repository and initializes the project in a new folder. The -e flag sets a name for the current environment. In azd, the environment maintains a unique deployment context for your app, and you can define more than one. The environment name is also used in the name of the resource group you create in Azure.

  2. Change to the project directory and then navigate to the src app folder:

    cd durable-functions-quickstart-python-azd/src
    
  1. In your local terminal or command prompt, run this azd init command in an empty folder:

    azd init --template durable-functions-quickstart-typescript-azd -e dfquickstart-typescript
    

    This command pulls the project files from the template repository and initializes the project in a new folder. The -e flag sets a name for the current environment. In azd, the environment maintains a unique deployment context for your app, and you can define more than one. The environment name is also used in the name of the resource group you create in Azure.

  2. Change to the project directory and then navigate to the src app folder:

    cd durable-functions-quickstart-typescript-azd/src
    
  1. In your local terminal or command prompt, run this azd init command in an empty folder:

    azd init --template durable-functions-quickstart-java-azd -e dfquickstart-java
    

    This command pulls the project files from the template repository and initializes the project in a new folder. The -e flag sets a name for the current environment. In azd, the environment maintains a unique deployment context for your app, and you can define more than one. The environment name is also used in the name of the resource group you create in Azure.

  2. Change to the project directory:

    cd durable-functions-quickstart-java-azd
    
  1. In your local terminal or command prompt, run this azd init command in an empty folder:

    azd init --template durable-functions-quickstart-javascript-azd -e dfquickstart-javascript
    

    This command pulls the project files from the template repository and initializes the project in a new folder. The -e flag sets a name for the current environment. In azd, the environment maintains a unique deployment context for your app, and you can define more than one. The environment name is also used in the name of the resource group you create in Azure.

  2. Change to the project directory and then navigate to the src app folder:

    cd durable-functions-quickstart-javascript-azd/src
    
  1. In your local terminal or command prompt, run this azd init command in an empty folder:

    azd init --template durable-functions-quickstart-powershell-azd -e dfquickstart-powershell
    

    This command pulls the project files from the template repository and initializes the project in a new folder. The -e flag sets a name for the current environment. In azd, the environment maintains a unique deployment context for your app, and you can define more than one. The environment name is also used in the name of the resource group you create in Azure.

  2. Change to the project directory and then navigate to the src app folder:

    cd durable-functions-quickstart-powershell-azd/src
    

Provision Azure resources

This project uses the azd provision command to create the required Azure resources, including a new function app in a Flex Consumption plan, a Durable Task Scheduler, and a task hub.

Tip

The project includes a set of Bicep files (in the infra folder) that azd uses to create a secure deployment to a Flex consumption plan that follows best practices.

  1. Go back to the root project folder (the folder that contains the azure.yaml file):

    cd ..
    
  1. Make sure you're still in the root project folder (the folder that contains the azure.yaml file).
  1. Run this command to authenticate with your Azure account:

    azd auth login
    
  2. Run this command from the root project folder to have azd create the required Azure resources:

    azd provision
    

    The root folder contains the azure.yaml definition file required by azd.

  3. When prompted, provide these required deployment parameters:

    Parameter Description
    Azure subscription Subscription in which your resources are created. If your account has more than one tenant, you must first choose your tenant.
    Azure location Azure region in which to create the resource group that contains the new Azure resources. Only regions that currently support the Flex Consumption plan are shown.
    vnetEnabled Whether to deploy in a virtual network. For this quickstart, select false.

    The azd provision command uses your responses to these prompts with the Bicep configuration files to create and configure these required Azure resources:

    • Flex Consumption plan and function app
    • Azure Storage (required) and Application Insights (recommended)
    • Durable Task Scheduler and task hub
    • Access policies and roles for your account
    • Service-to-service connections using managed identities (instead of stored connection strings)

    After the command completes successfully, you see links to the resources you created.

    Provisioning also runs a post-provision script that generates a local.settings.json file in your app folder. This file contains the connection settings for the deployed Durable Task Scheduler endpoint, which you need to run the project locally.

Start Azurite

To run the project locally, the Functions runtime needs a local storage emulator. The "AzureWebJobsStorage": "UseDevelopmentStorage=true" setting in the local.settings.json file directs the runtime to use Azurite for this purpose.

In a new terminal window, run this command to start Azurite:

azurite --skipApiVersionCheck --location ~/azurite-data

Keep Azurite running in this terminal window while testing locally.

Run in your local environment

  1. In your original terminal, go to the fanoutfanin app folder and start the Functions host:

    cd fanoutfanin
    func start
    

    When the Functions host starts in your local project folder, it writes the local URL endpoints of your HTTP triggered functions to the terminal output.

    Note

    Because access key authorization isn't enforced when running locally, you don't need an access key to call your function.

  2. In your browser, make a GET request to the endpoint that starts the orchestration:

    http://localhost:7071/api/FetchOrchestration_HttpStart

    This request starts a new orchestration instance. The orchestration fans out to several activities to fetch the titles of Microsoft Learn articles in parallel. When the activities finish, the orchestration fans back in and returns the titles as a formatted string.

  1. In your original terminal, go to the src app folder, create and activate a virtual environment, install dependencies, and start the Functions host:

    cd src
    

    The way that you create and activate your virtual environment (named .venv) depends on your terminal:

    python3 -m venv .venv
    source .venv/bin/activate
    pip install -r requirements.txt
    func start
    

    When the Functions host starts in your local project folder, it writes the local URL endpoints of your HTTP triggered functions to the terminal output.

    Note

    Because access key authorization isn't enforced when running locally, you don't need an access key to call your function.

  2. In your browser, make a GET request to the HTTP start endpoint:

    http://localhost:7071/api/orchestrators/fetch_orchestration

    This request starts a new orchestration instance. The orchestration fans out to several activities to fetch the titles of Microsoft Learn articles in parallel. When the activities finish, the orchestration fans back in and returns the titles as a formatted string.

  1. In your original terminal, go to the src app folder, install dependencies, build the project, and start the Functions host:

    cd src
    npm install
    npm run build
    func start
    

    When the Functions host starts in your local project folder, it writes the local URL endpoints of your HTTP triggered functions to the terminal output.

    Note

    Because access key authorization isn't enforced when running locally, you don't need an access key to call your function.

  2. In your browser, make a GET request to the HTTP start endpoint:

    http://localhost:7071/api/orchestrators/fetchOrchestration

    This request starts a new orchestration instance. The orchestration fans out to several activities to fetch the titles of Microsoft Learn articles in parallel. When the activities finish, the orchestration fans back in and returns the titles as a formatted string.

  1. In your original terminal, build the project and then start the Functions host from the build output folder:

    mvn clean package
    cd target/azure-functions/durable-functions-quickstart
    func start
    

    When the Functions host starts in your local project folder, it writes the local URL endpoints of your HTTP triggered functions to the terminal output.

    Note

    Because access key authorization isn't enforced when running locally, you don't need an access key to call your function.

  2. In your browser, make a GET request to the endpoint that starts the orchestration:

    http://localhost:7071/api/FetchOrchestration_HttpStart

    This request starts a new orchestration instance. The orchestration fans out to several activities to fetch the titles of Microsoft Learn articles in parallel. When the activities finish, the orchestration fans back in and returns the titles as a formatted string.

  1. In your original terminal, go to the src app folder, install dependencies, and start the Functions host:

    cd src
    npm install
    func start
    

    When the Functions host starts in your local project folder, it writes the local URL endpoints of your HTTP triggered functions to the terminal output.

    Note

    Because access key authorization isn't enforced when running locally, you don't need an access key to call your function.

  2. In your browser, make a GET request to the endpoint that starts the orchestration:

    http://localhost:7071/api/FetchOrchestration_HttpStart

    This request starts a new orchestration instance. The orchestration fans out to several activities to fetch the titles of Microsoft Learn articles in parallel. When the activities finish, the orchestration fans back in and returns the titles as a formatted string.

  1. In your original terminal, go to the src app folder and start the Functions host:

    cd src
    func start
    

    When the Functions host starts in your local project folder, it writes the local URL endpoints of your HTTP triggered functions to the terminal output.

    Note

    Because access key authorization isn't enforced when running locally, you don't need an access key to call your function.

  2. In your browser, make a GET request to the endpoint that starts the orchestration:

    http://localhost:7071/api/FetchOrchestration_HttpStart

    This request starts a new orchestration instance. The orchestration fans out to several activities to fetch the titles of Microsoft Learn articles in parallel. When the activities finish, the orchestration fans back in and returns the titles as a formatted string.

  1. The HTTP endpoint returns a JSON response with several URLs. The statusQueryGetUri endpoint provides the orchestration status.

  2. Copy the statusQueryGetUri value and paste it into your browser or HTTP test tool to check the status of the orchestration. When the orchestration completes, you see the fetched article titles in the response.

  3. When you're done, press Ctrl+C in the terminal window to stop the func host process.

  1. Run deactivate to shut down the virtual environment.

Review the code (optional)

You can review the code that implements the fan-out/fan-in pattern:

The title fetching activities are tracked using a dynamic task list. The line await Task.WhenAll(parallelTasks); waits for all the called activities, which run concurrently, to complete. When done, all outputs are aggregated as a formatted string.

[Function(nameof(FetchOrchestration))]
public static async Task<string> RunOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    ILogger logger = context.CreateReplaySafeLogger(nameof(FetchOrchestration));
    logger.LogInformation("Fetching data.");
    var parallelTasks = new List<Task<string>>();
    
    // List of URLs to fetch titles from
    var urls = new List<string>
    {
        "https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview",
        "https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler",
        "https://learn.microsoft.com/azure/azure-functions/functions-scenarios",
        "https://learn.microsoft.com/azure/azure-functions/functions-create-ai-enabled-apps",
    };

    // Run fetching tasks in parallel
    foreach (var url in urls)
    {
        Task<string> task = context.CallActivityAsync<string>(nameof(FetchTitleAsync), url);
        parallelTasks.Add(task);
    }
    
    // Wait for all the parallel tasks to complete before continuing
    await Task.WhenAll(parallelTasks);
   
    // Return fetched titles as a formatted string
    return string.Join("; ", parallelTasks.Select(t => t.Result));
}

You can review the complete template project here.

The title fetching activities are tracked using a dynamic task list. The line yield context.task_all(tasks) waits for all the called activities, which run concurrently, to complete. When done, all outputs are aggregated as a formatted string.

    # List of URLs to fetch titles from
    urls = [
        "https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview",
        "https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler",
        "https://learn.microsoft.com/azure/azure-functions/functions-scenarios",
        "https://learn.microsoft.com/azure/azure-functions/functions-create-ai-enabled-apps",
    ]
    
    # Run fetching tasks in parallel
    tasks = []
    for url in urls:
        task = context.call_activity("fetch_title", url)
        tasks.append(task)
    
    # Wait for all the parallel tasks to complete before continuing
    results = yield context.task_all(tasks)
    
    # Return fetched titles as a formatted string
    return "; ".join(results)


@myApp.activity_trigger(input_name="url")
async def fetch_title(url: str):
    """Activity function that fetches the title from a URL."""
    logger = logging.getLogger("FetchTitle")
    logger.info(f"Fetching from url {url}.")
    
    try:
        async with ClientSession() as session:

You can review the complete template project here.

The title fetching activities are tracked using a dynamic task list. The line yield context.df.Task.all(parallelTasks) waits for all the called activities, which run concurrently, to complete. When done, all outputs are aggregated as a formatted string.

    const urls = [
        "https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview",
        "https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler",
        "https://learn.microsoft.com/azure/azure-functions/functions-scenarios",
        "https://learn.microsoft.com/azure/azure-functions/functions-create-ai-enabled-apps",
    ];

    // Run fetching tasks in parallel
    const parallelTasks = [];
    for (const url of urls) {
        const task = context.df.callActivity(fetchTitleActivityName, url);
        parallelTasks.push(task);
    }

    // Wait for all the parallel tasks to complete before continuing
    const results: string[] = yield context.df.Task.all(parallelTasks);

    // Return fetched titles as a formatted string
    return results.join("; ");
};
df.app.orchestration("fetchOrchestration", fetchOrchestration);

const fetchTitleAsync: ActivityHandler = async function (
    url: string,
    context: InvocationContext

You can review the complete template project here.

The title fetching activities are tracked using a dynamic task list. The line ctx.allOf(parallelTasks).await() waits for all the called activities, which run concurrently, to complete. When done, all outputs are aggregated as a formatted string.

@FunctionName("FetchOrchestration")
public String runOrchestrator(
        @DurableOrchestrationTrigger(name = "taskOrchestrationContext") TaskOrchestrationContext ctx) {

    List<String> urls = List.of(
        "https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview",
        "https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler",
        "https://learn.microsoft.com/azure/azure-functions/functions-scenarios",
        "https://learn.microsoft.com/azure/azure-functions/functions-create-ai-enabled-apps"
    );

    // Fan out: run fetching tasks in parallel
    List<Task<String>> parallelTasks = urls.stream()
        .map(url -> ctx.callActivity("FetchTitle", url, String.class))
        .collect(Collectors.toList());

    // Fan in: wait for all parallel tasks to complete
    List<String> results = ctx.allOf(parallelTasks).await();

    // Return fetched titles as a formatted string
    return String.join("; ", results);
}

You can review the complete template project here.

The title fetching activities are tracked using a dynamic task list. The line yield context.df.Task.all(parallelTasks) waits for all the called activities, which run concurrently, to complete. When done, all outputs are aggregated as a formatted string.

df.app.orchestration('fetchOrchestration', function* (context) {
    context.log("Fetching data.");

    const urls = [
        "https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview",
        "https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler",
        "https://learn.microsoft.com/azure/azure-functions/functions-scenarios",
        "https://learn.microsoft.com/azure/azure-functions/functions-create-ai-enabled-apps",
    ];

    // Fan out: run fetching tasks in parallel
    const parallelTasks = urls.map(url => context.df.callActivity(activityName, url));

    // Fan in: wait for all parallel tasks to complete
    const results = yield context.df.Task.all(parallelTasks);

    // Return fetched titles as a formatted string
    return results.join("; ");
});

You can review the complete template project here.

The title fetching activities are tracked using a dynamic task list. The line Wait-ActivityFunction -Task $parallelTasks waits for all the called activities, which run concurrently, to complete. When done, all outputs are aggregated as a formatted string.

param($Context)

$urls = @(
    "https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview",
    "https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler",
    "https://learn.microsoft.com/azure/azure-functions/functions-scenarios",
    "https://learn.microsoft.com/azure/azure-functions/functions-create-ai-enabled-apps"
)

# Fan out: run fetching tasks in parallel
$parallelTasks = @()
foreach ($url in $urls) {
    $parallelTasks += Invoke-DurableActivity -FunctionName 'FetchTitle' -Input $url -NoWait
}

# Fan in: wait for all parallel tasks to complete
$results = Wait-ActivityFunction -Task $parallelTasks

# Return fetched titles as a formatted string
$output = $results -join "; "
$output

You can review the complete template project here.

After you verify your functions locally, it's time to deploy them to Azure.

Deploy to Azure

Run this command from the root project folder to deploy your code project to the function app in Azure:

azd deploy

The azd deploy command packages and deploys your code to the function app created during provisioning. After the command completes, the app starts and runs in the deployed package.

Invoke the function on Azure

You can now invoke your orchestration endpoint in Azure by making an HTTP request to its URL. When your functions run in Azure, access key authorization is enforced, and you must provide a function access key with your request.

You can use the Core Tools to get the URL endpoint of the HTTP trigger that starts the orchestration in Azure.

  1. In your local terminal or command prompt, run these commands to get the URL endpoint values:

    APP_NAME=$(azd env get-value AZURE_FUNCTION_NAME)
    func azure functionapp list-functions $APP_NAME --show-keys
    

    The azd env get-value command gets your function app name from the local environment. When you use the --show-keys option with func azure functionapp list-functions, the returned Invoke URL: value for each endpoint includes any required function-level access keys.

  2. Use a browser or HTTP test tool to make a GET request to the HTTP start endpoint to start the orchestration in your function app running in Azure.

Redeploy your code

Run azd deploy as many times as you need to deploy code updates to your function app. If you need to update the Azure resources, run azd provision again.

Note

The latest deployment package always overwrites deployed code files.

Your initial responses to azd prompts and any environment variables generated by azd are stored locally in your named environment. Use the azd env get-values command to review all of the variables in your environment that you used when creating Azure resources.

Clean up resources

When you're done working with your function app and related resources, use this command to delete the function app and its related resources from Azure and avoid incurring any further costs:

azd down --no-prompt

Note

The --no-prompt option instructs azd to delete your resource group without a confirmation from you.

This command doesn't affect your local code project.