Posted in:

In this post I'll run through the steps involved with creating and trying out Durable Functions in the Azure Portal. This is a great way to experiment if you don't want to (or can't) install the development environment locally.

1. Create a Function App

First you need to create a new function app in the usual way. The consumption plan is fine. In function app settings, set the runtime version to beta (I'm not sure that's required for durable functions, but I needed it in order to be offered the durable functions templates):

image

2. Install the Durable Functions Extension

To use durable functions we need the Durable Functions Extension, and the easiest way to do that is to create a function using one of the Durable Functions templates. Start off by clicking the + icon to create a new function:

image

If it takes you to the quickstart, choose "create your own custom function":

image

Choose "Durable Functions" in the "scenario" dropdown

image

Now you will be prompted Install the Durable Functions Extension:

image

It takes a few minutes to install, but once it's done we can continue to create a function using one of the Durable Functions templates.

3. Create an orchestration starter function.

Let's start off by using the HTTP Starter template, which is just a regular Azure Function that is able to kick off a new Durable Functions orchestration. I'll call mine HttpStarterFunction

image

The sample code it generates is very generic. It lets us pass in the name of the orchestrator function as a parameter. And we can post any data that we want to send to the orchestrator in the request body. Obviously in a real-world application you'd likely have functions that specifically trigger a single orchestration.

The DurableOrchestrationClient binding allows us to start a new orchestration with StartNewAsync, and we use the helpful CreateCheckStatusResponse function to return a JSON object containing the URIs we can use to query the status of the orchestration we've started.

image

4. Create an orchestrator function

We don't have any orchestrations to actually start with our starter, so let's create an orchestrator function. We'll use the orchestrator template:

image

I'll call it OrchestratorFunction:

image

This sample orchestration is going to call the same activity function three times, with different inputs ("Tokyo", "Seattle", "London"). The activity is called "Hello" and takes a string input.

image

5. Create an activity function

Next we need to create our activity function. Again there's a template for that:

image

But this time we must take care to name it with the name the name the orchestrator function is expecting ("Hello"):

image

The code for our sample activity is trivial. It takes a string and returns a string, which is fine for demo purposes. Obviously a real-world activity function would be doing something a bit more complex.

image

6. Start an orchestration

To test our orchestrator in the portal, we'll need to go back to our starter function We can click the "Test" tab, and put "OrchestratorFunction" in as the function name we want to call:

image

Now it's just a matter of clicking "Run". We'll get a 202 Accepted that gives us some JSON with information about the orchestration that was started.

image

In the JSON we get the id of the orchestration, plus a bunch of URIs we can use to check on the status. Something like this:

{"id":"6b749f72e73443d0bc84d7bb53a6a79f",
  "statusQueryGetUri": "https://durable-functions-test.azurewebsites.net/runtime/webhooks/DurableTaskExtension/instances/6b749f72e73443d0bc84d7bb53a6a79f?taskHub=DurableFunctionsHub&connection=Storage&code=3vB2Qm8yQbWosqreTspvaSfUerCpcRaMUXiCq8rfzabcJN22R88jTQ==",
  "sendEventPostUri": "https://durable-functions-test.azurewebsites.net/runtime/webhooks/DurableTaskExtension/instances/6b749f72e73443d0bc84d7bb53a6a79f/raiseEvent/{eventName}?taskHub=DurableFunctionsHub&connection=Storage&code=3vB2Qm8yQbWosqreTspvaSfUerCpcRaMUXiCq8rfzabcJN22R88jTQ==",
  "terminatePostUri": "https://durable-functions-test.azurewebsites.net/runtime/webhooks/DurableTaskExtension/instances/6b749f72e73443d0bc84d7bb53a6a79f/terminate?reason={text}&taskHub=DurableFunctionsHub&connection=Storage&code=3vB2Qm8yQbWosqreTspvaSfUerCpcRaMUXiCq8rfzabcJN22R88jTQ=="
}

7. Query orchestration status

I just need the statusQueryGetUri one to find out if the orchestration has completed yet and get its output. I can easily call this from powershell:

Invoke-WebRequest "https://durable-functions-test.azurewebsites.net/runtime/webhooks/DurableTaskExtension/instances/6b749f72e73443d0bc84d7bb53a6a79f?taskHub=DurableFunctionsHub&connection=Storage&code=3vB2Qm8yQbWosqreTspvaSfUerCpcRaMUXiCq8rfzabcJN22R88jTQ=="

Unfortunately, I got a 404 not found! It seems like at the moment there may be a bug that causes orchestrations to intermittently fail to start. Hopefully that will be resolved soon (I've reported an issue at GitHub).

However, for the ones that did work, I was able to use the statusQueryGetUri to retrieve details of the orchestration. Since this one runs pretty quickly, it will likely already be finished by the time you query it:

{
    runtimeStatus: "Completed",
    input: null,
    output: [
        "Hello Tokyo!",
        "Hello Seattle!",
        "Hello London!"
    ],
    createdTime: "2018-01-06T11:46:23Z",
    lastUpdatedTime: "2018-01-06T11:46:28Z"
}

8. Looking behind the scenes

The Durable Functions extension uses an Azure Storage account behind the scenes to track the state of your orchestrations. So if you're troubleshooting issues it can be useful to know where to look to see what's been stored. You can use the excellent Azure Storage Explorer. Here's a view of the various tables, queues and blob containers that get created for a function app with the durable tasks extension. The most useful one is the DurableFunctionsHubHistory table, which contains an "event sourced" history of all the actions that have happened in this orchestration.

image

For the bug I was encountering there were no records at all in the DurableFunctionsHubHistory table the orchestration client had claimed to start. The orchestration ids are used as partition keys in the table, so it's easy to find rows relating to the orchestration instance you are interested in:

image

Conclusion

It is quite easy to try Durable Functions in the portal without having to set up any local development environment (although I do recommend that if you plan to experiment more with Durable Functions). Seems like there are still a few bugs to iron out at the moment, but I think Durable Functions is a very promising new feature that can greatly simplify the implementation of workflows in serverless environments.

Want to learn more about how easy it is to get up and running with Durable Functions? Be sure to check out my Pluralsight course Azure Durable Functions Fundamentals.

Comments

Comment by Marc Roussy

Thanks for that Mark - I wasn't aware there was such a thing as Durable Functions! Could be very useful for some scenarios where you need to persist some kind of state in a serverless function.

Marc Roussy
Comment by Mark Heath

yes, Durable Functions is still in preview but it is very powerful and a great addition to Azure Functions

Mark Heath
Comment by Ryan

Hey Mark - it looks like to kick off an orchestration on a timer you'd have to:
A) Create a timer function that calls a
B) Http triggered starter function that then calls a
C) Durable Context Orchestrator Function that then calls 1-*
D) Durable Context Activity Functions
Is this accurate? I was kind of hoping that the Durable Orchestrator functions could have their own triggers.

Ryan
Comment by Mark Heath

I don't think you need step B. The timer function should be able to bind directly to a DurableOrchestrationClient it can use to start the orchestration. Haven't tried that exact situation myself but see no reason why it wouldn't work.

Mark Heath
Comment by Ryan

*Slaps Head* Yeah that should work. Now all I have to do is figure out how to get the sweet timer functionality from the main context/orchestrator methods into the activity functions.
For anyone else doing this in VS rather than through the browser, make sure you're adding the correct attributes for the parameters. Namely [OrchestrationClient] and [OrchestrationTrigger].
Also huge thanks Mark the documentation on Durable Orchestrations is in a weird state and this was incredibly straighforward!

Ryan
Comment by dksw

How can I manually add an element to DurableFunctionsHubHistory ? I'm trying to keep track of the state of a method call that wraps the azure durable functions. I know I can retrieve it using GetStatusAsync(). I just don't know how to manually add data to it that was created by code that occurs before a IDurableOrchestrationContext.CallActivityAsync() call

dksw
Comment by Mark Heath

I'm not sure exactly what you're trying to achieve, but possibly the "custom status" feature would help: https://docs.microsoft.com/...

Mark Heath
Comment by dksw

Thanks for the suggestion Mark. SetCustomStatus() only allows for one value to be stored per orchestration. I guess the new value could be a list of values so maybe I could GetCustomStatus() of a list of data and then add to it and create json for the updated list, but at some point it would reach a limit of data if the data I'm storing in SetCustomStatus() gets too large since it says
"The custom status payload is limited to 16 KB of UTF-16 JSON text because it needs to be able to fit in an Azure Table Storage column. You can use external storage if you need larger payload."
So maybe I would instead just give a link to some azure storage blob that has that content for the might be large payload of a list. The history seemed to allow 16KB of json per each history element so that would have fit my need perfectly but I was unable to figure out how to call it - this is how far I got but did not know how to instantiate AzureStorageOrchestrationServiceSettings with all of its values
var dp = new DurabilityProvider();
DurableTask.Core.IOrchestrationService isrv = new DurableTask.AzureStorage.AzureStorageOrchestrationService(new DurableTask.AzureStorage.AzureStorageOrchestrationServiceSettings { StorageConnectionString = "UseDevelopmentStorage=true" });
var tm = new DurableTask.Core.TaskMessage() { Event = new DurableTask.Core.History.GenericEvent(1, ""), SequenceNumber = 0, OrchestrationInstance = new DurableTask.Core.OrchestrationInstance { } };
isrv.CompleteTaskActivityWorkItemAsync(new DurableTask.Core.TaskActivityWorkItem { Id = "", TaskMessage = tm }, new DurableTask.Core.TaskMessage { Event = new DurableTask.Core.History.GenericEvent(1, ""), ExtensionData = null, OrchestrationInstance = null, SequenceNumber = 0 });

dksw