Async HTTP APIs Pattern - Part 4 - Azure Durable Functions

2022, Jun 17

This is a built-in pattern available with durable functions, that removes the need for custom code to interact with long-running function executions.

This is a series where I am explaining the components of durable functions1 and the different application patterns of implementation.

Check the PlayGoKids repository for this article demo.

Understanding the Flow

Async-Http-APIs

The Async HTTP APIs pattern addresses the coordination of states in long-running operations that are available to client consumers. On the graph, from an HTTP Start trigger some DoWork is executed, and a client can query an API to GetStatus, to identify whether the operation is running, or it has finished.

Running the flow

Using the Visual Studio quickstart sample this can be easily demoed. Create a new solution using the template:

Azure Functions

Durable Function

The project is created in a single file Function1.cs:

using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;

namespace DurableFunctionsAsyncHTTPAPIsPattern
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task<List<string>> RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context)
        {
            var outputs = new List<string>();

            // Replace "hello" with the name of your Durable Activity Function.
            outputs.Add(await context.CallActivityAsync<string>("Function1_Hello", "Tokyo"));
            outputs.Add(await context.CallActivityAsync<string>("Function1_Hello", "Seattle"));
            outputs.Add(await context.CallActivityAsync<string>("Function1_Hello", "London"));

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

        [FunctionName("Function1_Hello")]
        public static string SayHello([ActivityTrigger] string name, ILogger log)
        {
            log.LogInformation($"Saying hello to {name}.");
            return $"Hello {name}!";
        }

        [FunctionName("Function1_HttpStart")]
        public static async Task<HttpResponseMessage> HttpStart(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestMessage req,
            [DurableClient] IDurableOrchestrationClient starter,
            ILogger log)
        {
            // Function input comes from the request content.
            string instanceId = await starter.StartNewAsync("Function1", null);

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

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

Start the function, and then hit the endpoint:

Function1_HttpStart: [GET,POST] http://localhost:7071/api/Function1_HttpStart

If you add some breakpoints in each function, you will be able to simulate this.

First, you get the response by triggering the Durable Client Function1_HttpStart:

{
    "id":"7fc98a1fec9142f8a0fbe6ce550b13b8",
    "statusQueryGetUri":"http://localhost:7071/runtime/webhooks/durabletask/instances/7fc98a1fec9142f8a0fbe6ce550b13b8?taskHub=TestHubName&connection=Storage&code=bw4_4IMJVXm1VHDOXquvBt7SX9LE3_2mAg2l6SJSkNPTAzFuXAQwgw==",
    "sendEventPostUri":"http://localhost:7071/runtime/webhooks/durabletask/instances/7fc98a1fec9142f8a0fbe6ce550b13b8/raiseEvent/{eventName}?taskHub=TestHubName&connection=Storage&code=bw4_4IMJVXm1VHDOXquvBt7SX9LE3_2mAg2l6SJSkNPTAzFuXAQwgw==",
    "terminatePostUri":"http://localhost:7071/runtime/webhooks/durabletask/instances/7fc98a1fec9142f8a0fbe6ce550b13b8/terminate?reason={text}&taskHub=TestHubName&connection=Storage&code=bw4_4IMJVXm1VHDOXquvBt7SX9LE3_2mAg2l6SJSkNPTAzFuXAQwgw==",
    "purgeHistoryDeleteUri":"http://localhost:7071/runtime/webhooks/durabletask/instances/7fc98a1fec9142f8a0fbe6ce550b13b8?taskHub=TestHubName&connection=Storage&code=bw4_4IMJVXm1VHDOXquvBt7SX9LE3_2mAg2l6SJSkNPTAzFuXAQwgw==",
    "restartPostUri":"http://localhost:7071/runtime/webhooks/durabletask/instances/7fc98a1fec9142f8a0fbe6ce550b13b8/restart?taskHub=TestHubName&connection=Storage&code=bw4_4IMJVXm1VHDOXquvBt7SX9LE3_2mAg2l6SJSkNPTAzFuXAQwgw=="
}

The endpoint from the Async HTTP API pattern that is a built-in feature in Durable functions is statusQueryGetUri

Then if you query this endpoint, depending on the state of execution you get "runtimeStatus":"Running":

{
    "name":"Function1",
    "instanceId":"7fc98a1fec9142f8a0fbe6ce550b13b8",
    "runtimeStatus":"Running",
    "input":null,
    "customStatus":null,
    "output":null,
    "createdTime":"2022-06-16T14:29:49Z",
    "lastUpdatedTime":"2022-06-16T14:30:23Z"
}

And then you get "runtimeStatus":"Completed":

{
    "name":"Function1",
    "instanceId":"7fc98a1fec9142f8a0fbe6ce550b13b8",
    "runtimeStatus":"Completed",
    "input":null,
    "customStatus":null,
    "output":["Hello Tokyo!","Hello Seattle!","Hello London!"],
    "createdTime":"2022-06-16T14:29:49Z",
    "lastUpdatedTime":"2022-06-16T14:31:02Z"
}

As you can verify, this pattern is already available in durable functions, and you don't need to reinvent the wheel.

In the next article of the series let's check out how to use the Monitor pattern.

Full Series

Major components

Function chaining

Fan out/fan in

Async HTTP APIs

Monitor

Human interaction

Aggregator (stateful entities)


  1. Azure Durable Functions link