Azure Durable Functions
Tags: azure, serverless, csharp
Published: 2020-01-31 10:56:27

Durable Functions is an extension of Azure Functions that enables you to perform long-lasting, stateful operations in Azure. Azure provides the infrastructure for maintaining state information. You can use Durable Functions to orchestrate a long-running workflow. Using this approach, you get all the benefits of a serverless hosting model, while letting the Durable Functions framework take care of activity monitoring, synchronization, and runtime concerns.

Suppose your e-commerce company has a warehouse and there is a staff to ship products. We want to automate the process, but still involve humans. We can implement [human interaction](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-phone-verification) pattern by using an orchestrator function. The orchestrator uses a [durable timer](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-timers?tabs=csharp) to request approval. The orchestrator escalates if timeout occurs. The orchestrator waits for an [external event](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-external-events?tabs=csharp), such as a notification that's generated by a human interaction.

<!--more-->

![Azure Durable Functions Diagram](https://dev-to-uploads.s3.amazonaws.com/i/3o8xgyehgzfxirn62696.png)


## Create a function app project

If you did not create a function app on Visual Studio before, please follow the steps on [Create your first durable function in C#](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-create-first-csharp).

Copy the code below to your _function.cs_ file.

```csharp
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;

namespace OlcayFunctionAppSample
{
    public static class DurableFunction
    {
        [FunctionName("DurableFunction_Orchestrator")]
        public static async Task<List<string>> RunOrchestrator(
            [OrchestrationTrigger] DurableOrchestrationContext context)
        {
            var outputs = new List<string>();

            using (var timeoutCts = new CancellationTokenSource())
            {
                DateTime expiration = context.CurrentUtcDateTime.AddSeconds(20);
                Task timeoutTask = context.CreateTimer(expiration, timeoutCts.Token);

                Task shipTask = context.WaitForExternalEvent("Event_Ship");

                // Wait until one of the tasks is completed
                Task winner = await Task.WhenAny(shipTask, timeoutTask);
                if (winner == shipTask)
                {
                    // Event raised
                    outputs.Add(await context.CallActivityAsync<string>("DurableFunction_Ship", "shipped"));
                }
                else
                {
                    // Timeout expired
                    outputs.Add(await context.CallActivityAsync<string>("DurableFunction_Escalate", "Head of department"));
                }

                if (!timeoutTask.IsCompleted)
                {
                    // All pending timers must be complete or canceled before the function exits.
                    timeoutCts.Cancel();
                }
            }

            return outputs;
        }

        [FunctionName("DurableFunction_Ship")]
        public static string Ship([ActivityTrigger] string activityInput, ILogger log)
        {
            log.LogInformation($"The order is {activityInput}.");
            return $"The order is {activityInput}!";
        }

        [FunctionName("DurableFunction_Escalate")]
        public static string Escalate([ActivityTrigger] string activityInput, ILogger log)
        {
            log.LogInformation($"The order is escalated to {activityInput}.");
            return $"The order is escalated to {activityInput}!";
        }

        [FunctionName("DurableFunction_HttpStart")]
        public static async Task<HttpResponseMessage> HttpStart(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]
            HttpRequestMessage req,
            [OrchestrationClient] DurableOrchestrationClient starter,
            ILogger log)
        {
            string instanceId = await starter.StartNewAsync("DurableFunction_Orchestrator", null);

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

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

## Test the function locally

1. Press __F5__ to start debugging.
1. Copy the URL of your function from the Azure Functions runtime output.
    ![Azure Functions Debugging](https://dev-to-uploads.s3.amazonaws.com/i/4r4p9hlarbqjh52zrp98.png)
1. Paste the URL `http://localhost:7071/api/DurableFunction_HttpStart` into your browser's address bar and execute the request.
1. Copy the URL value for `statusQueryGetUri` and paste it in the browser's address bar and execute the request. If you do it on time, you will see _Pending_ or _Running_ as `runtimeStatus`.
1. If you wait enough to expire timeout task and refresh, the `runtimeStatus` would be _Completed_ and an output message would be displayed as _The order is escalated to Head of department!_.
1. If you raise _ship_ event before the timeout expires, the `runtimeStatus` would be _Completed_ and an output message would be displayed as _The order is shipped!_.

### Raising an event
Copy the URL value for `sendEventPostUri`, replace `{eventName}` with `Event_Ship` and run the command below with the the URL on command prompt.

`curl --request POST 'http://localhost:7071/runtime/webhooks/durabletask/instances/{instanceId}/raiseEvent/Event_Ship' \
--header 'Content-Type: application/json' --data-raw ''`