Azure Azure Functions

Durable Functions and when to use them

Dominic Sinclair-Moore
Azure Function
Azure Function

Background of Azure Functions

Azure Functions are serverless compute solutions that allows you to write less code, maintain less infrastructure and save costs. The main focus of Azure Functions is to prioritise the business logic above any application lifecycle logic.

Typically Azure Functions are reusable logic elements that can be triggered from different events. A good use case of Azure Functions could be within an organisations code repository. During any pull requests an Azure Function can trigger to run some validation logic. For example we could validate that if the target branch is the master branch, the source branch must be a release branch from the same repository. If this usage example interests you please check out the Azure DevOps Azure Function integration.

Scenarios

There are many different ways to utilise Azure Functions for many different scenarios utilising all sorts of different technologies. From HTTP Triggering web APIs to real-time data processing utilising SignalR to responding to database changes in a Cosmos Database.

Taking our previously defined usage of Azure Functions to be reusable logic elements we are more than likely to need multiple logical elements to process data. This is where Durable Functions can be useful, although this is by no means the only usage of Durable Functions. Durable functions allow you to chain multiple Functions together. This allows each logic element to be self sufficient in some scenarios, but also usable in larger more complex data processing applications.

What are Durable Functions?

As we've touched upon, Durable Functions can be used to chain multiple Functions together however this is just one of the many use cases of Durable Functions. But what are they? Simply put, Durable functions are Azure Functions that can have a stateful workflow defined within Orchestrator Functions and stateful entities defined within Entity Functions.

The primary use case for Durable Functions is to simplify complex logic and coordinate stateful requirements in serverless applications.

Durable Function Patterns

Microsoft defined the following patterns that can be used within Durable Functions:

  • Function chaining
  • Fan-out/fan-in
  • Async HTTP APIs
  • Monitoring
  • Human interaction
  • Aggregator (stateful entities)

My personal favourite has to be the fan-out/fan-in pattern as it allows for extremely scalable applications without needing to worry about the infrastructure being there.

The fan-out/fan-in pattern itself naturally uses the Function Chaining pattern due to the orchestration function (F1) which needs to call the activity function (F2). As Functions scales aren't determined by the Function App itself in Azure, the number of activity functions is only limited by resource limits. A good use case of this pattern is to handle batch requests and process each of the requests coming in individually then pushing the final results to a database without degrading any performance.

Constraints

Happily for all these great features there are hardly any constraints. From a code perspective only the Orchestrator function code constraints need to be followed if using Durable Functions. Otherwise the main areas of concerns are infrastructure and syntax at scale.

If you're planning to use the Azure Function Consumption plan for Durable Functions, there are some billing behaviours which are less than ideal (double billing and paying for waiting) which may sway you to use some other event offering such as Event Grid.

Gotchas

If you're planning to use Functions at scale with at least 2 or more deployment slots (eg. production + staging) then you may run into some stranger behaviours especially if you have multiple Durable Functions on the same Function App. The Orchestrator in the Durable Function uses a DurableClientAttribute which has an optional property called TaskHub which gets or sets the name of the task hub in which the orchestration data lives. A task hub is simply a logical container for durable storage resources that are used for the orchestrations and entities. When multiple Durable Functions share a single storage account (this includes production and staging slots) you must define the task hub name.

To define the Task Hub simply add the following into the host.json.

{
  "version": "2.0",
  "extensions": {
    "durableTask": {
      "hubName": "MyTaskHub"
    }
  }
}

When applying a Task Hub in the host.json we can use App Settings to pull through Task Hub names by encapsulating the name with a %.

{
  "version": "2.0",
  "extensions": {
    "durableTask": {
      "hubName": "%MyTaskHub%"
    }
  }
}

In addition to the host.json, Task Hub names can be configured in the orchestration client binding metadata. In C# Functions we apply the DurableClientAttribute to the IDurableOrchestrationClient property in the Orchestrating Function which defines the Task Hub. This allows us to access orchestrations or entities that are within a separate function app.

[FunctionName("HttpStart")]
public static async Task<HttpResponseMessage> Run(
    [HttpTrigger(AuthorizationLevel.Function, methods: "post", Route = "orchestrators/{functionName}")] HttpRequestMessage req,
    [DurableClient(TaskHub = "%MyTaskHub%")] IDurableOrchestrationClient 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);
}

These settings only apply to Azure Functions version 2 and above. Version 1 Azure Functions have slightly different syntax.

Closing thoughts

Durable Functions are a welcomed addition to the Azure Functions and the serverless ecosystem available within Azure. Durable Functions may not always be the best choice, however being aware of the capability that they bring can significant. Whether it's an alternative solution to your problem or a way to reuse logic without duplication or just a complex set of Web API requests this additional processing mechanism has proved invaluable to me. Hopefully you will find Durable Functions just as valuable.

Cookie Policy© 2021 — DOTNET Ltd