Technologies | March 20, 2023

Serverless workflow orchestration – Azure Durable Function

Business process automation is a natural transformation that most companies have to get on board with. It is not an easy process, as it requires knowledge and experience. But the battle is not yet lost! In this article, I would like to present the Azure Durable Function solution, an Azure Function Extension, which will allow you to proceed smoothly throughout the development process. Read on to find out more about serverless workflow orchestration.

azure workflow orchestration

What is Azure Durable Function?

Usually, the system we develop isn’t just “a set of functionalities or modules” (Function-as-a-Service), but rather “a set of multiple functionalities and modules influencing one another, dependent and interacting”. Thanks to the Azure Durable Functions, we are able to master this process. Azure Durable Function is the orchestrator controlling the entire business process, meaning our workflow. Durable Function is an extension of Azure Functions which allows you to optimize processes in a serverless environment.

Azure Durable Function consists of 3 basic types of blocks:

azure workflow orchestration

Client Function

A kind of starter that gets the process up and running. It is most often triggered by an http request, adding a record to the database, uploading a file, etc.

Orchestrator Function

The process that controls our workflow – this is where we set up the sequence of steps in the process.

Activity Function

A specific functionality, e.g. retrieving a record from a database, sending an e-mail, issuing an invoice, etc.


nearshore 2023.02.23 cover

EXPERT KNOWLEDGE

Using AWS Lambda functions

Development of serverless applications Read the article!

Let’s explore Azure Durable Functions in practice

For the purposes of the article, let’s assume that we run a web fashion store, and after the payment has been credited, we want to present the customer with possible color versions and, once the decision has been made, close the order. The initial process would look like this:

azure workflow orchestration

Now let’s try to answer the following questions:

  • How can we monitor what stage of the process the client is at?
  • How can we spot any errors?
  • Can we retry the step in the event of an error?
  • Can we parallelize the steps?
  • How can we receive confirmation of the event, i.e. the customer’s choice?

As you can see, there are a number of problems. The process is not perfect and cannot be easily controlled. Let’s get started and make sure it’s done right!

We begin with the ‘starter’:

[FunctionName("OrderStarter")]
public static async Task<IActionResult> HttpStart(
	[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]
	  HttpRequest req,
	[DurableClient] IDurableOrchestrationClient starter, ILogger log)
	{
	    log.LogInformation("Received a payment");
	    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
	    var order = JsonConvert.DeserializeObject<Order>(requestBody);

	    // Function input comes from the request content.
		string instanceId =
		await starter.StartNewAsync("OrderOrchestrator", order);

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

	    return starter.CreateCheckStatusResponse(req, instanceId);
	}

We have created a function that is triggered by an http request and receives information about the order. Its key element is the type object IDurableOrchestrationClient, which starts the selected process.


nearshore 2023.02.09 cover

EXPERT KNOWLEDGE

Microservices architecture explained

Is it still a revolution or already standard in IT projects? Go to article!

Azure Durable Functions – now let’s use the orchestrator

In its initial, simplified form, it might just look like this:

[FunctionName("OrderOrchestrator")]
public static async Task OrderOrchestrator(
  [OrchestrationTrigger] IDurableOrchestrationContext context,ILogger log)
  {
    var order = context.GetInput<Order>();
    {
      await context.CallActivityAsync<string>("PostThePayment", order);
      await context.CallActivityAsync<string>("GenerateColors", order);
      await context.CallActivityAsync<string>("SendNotification", order);
      await context.CallActivityAsync<string>("CloseOrderProcess", order);

      log.LogInformation($"Order processed successfully.");
    }
  }

As you can see, it’s a place from which we can control the entire ordering process.

To put it very simply, without diving into too much detail, the individual steps of the process, i.e. the Activity Function, can look like this:

[FunctionName("PostThePayment")]
  public static Task PostThePayment([ActivityTrigger] Order order, ILogger log)
  {
      log.LogInformation($"Do some stuff with order.");
  }

Let’s make modifications to find clear answers to the questions previously asked:

[FunctionName("OrderOrchestrator")]
public static async Task OrderOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
{
    
    var color = new List<string>() { "white", "black", "pink", "green" };
    var order = context.GetInput<Order>();

    try
    {
	await context.CallActivityWithRetryAsync<string>(
	    "PostThePayment",
	    new RetryOptions(TimeSpan.FromSeconds(5),3),
	    order);


	var tasks = new list<Task<string>>();
	foreach (var color in colors)
	{
	    var task = context.CallActivityAsync<string>("GenerateColors", order);
	}
	var generatedColors = await Task.WhenAll(tasks);


	await context.CallActivityAsync<string>("SendNotification", order);
	var userDecisionResult = await
	context.WaitForExternalEvent<string>("Approval", TimeSpan.FromHours(24));

	if (userDecisionResult == null)
	{
	    //time out - user not approved his choice in 24 hours
	    //cancel order
	}
	if (userDecisionResult == "approved")
	{
	    //do some activity when user approves his choice
	}

	await context.CallActivityAsync<string>("CloseOrderProcess", order);

	log.LogiInformation($"Order processed successfully.");
    }
    catch (Exception e)
    {
        //log exception, do some necessary cleanup
    }
}

Now we can see the significant changes:

  • By using the method CallActivityWithRetryAsync we are able to repeat the step in case of an error.
  • Thanks to using the Fan-out Fan-in pattern, we made it possible to generate color versions much faster by parallelizing the processes.
azure workflow orchestration
  • As you can see, through orchestration, we can easily use the construction Task. WhenAll to achieve the above goal.
  • By using the pattern Human Interaction we have managed to simulate the system waiting for user confirmation within a given period of 24 hours.
azure workflow orchestration

The last question is: how do we monitor the state of the process? By default, after starting the orchestrator, we get a set of links to the list of workflow operations:

[FunctionName("ConfirmUserChoice")]
  public static async Task ConfirmUserChoice(
	[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]
	HttpRequest req,
	[DurableClient] IDurableOrchestrationClient client,
	ILogger log)
  {
	string result = req.GetQueryParameterDictionary()["result"];
	string orderId = ""; //get orderId from db or other storage
	await client.RaiseEventAsync(orderId, "Approval", result);

So to monitor the current status of the process, just call statusQueryGetUri and as a result, you will get, for example:

{
	• id: "35e9c2d4b7914d34a8c79e171587406f", 
	• statusQueryGetUri: "http://localhost:7071/runtime/webhooks/durabletask/instances/35e9c2d4b7914d34a8c79e171587406f?taskHub=TestHubName&connection=Storage&code=i0BkAM8H9xo64Lve9oYAJvBstz7vzobVSHCKsKziaYXdXe4YCEhz0g==",
	• sendEventPostUri: "http://localhost:7071/runtime/webhooks/durabletask/instances/35e9c2d4b7914d34a8c79e171587406f/raiseEvent/{eventName}?taskHub=TestHubName&connection=Storage&code=i0BkAM8H9xo64Lve9oYAJvBstz7vzobVSHCKsKziaYXdXe4YCEhz0g==",
	• terminatePostUri: "http://localhost:7071/runtime/webhooks/durabletask/instances/35e9c2d4b7914d34a8c79e171587406f/terminate?reason={text}&taskHub=TestHubName&connection=Storage&code=i0BkAM8H9xo64Lve9oYAJvBstz7vzobVSHCKsKziaYXdXe4YCEhz0g==",
	• purgeHistoryDeleteUri: "http://localhost:7071/runtime/webhooks/durabletask/instances/35e9c2d4b7914d34a8c79e171587406f?taskHub=TestHubName&connection=Storage&code=i0BkAM8H9xo64Lve9oYAJvBstz7vzobVSHCKsKziaYXdXe4YCEhz0g==",
	•
}

We see that the process is now over and it can be considered completed.

Benefits of Durable Functions

  • Durable Functions are based on Microsoft Azure – a platform that is used by hundreds of thousands of people on a daily basis.
  • Azure Durable Function can work in serverless mode, popularly known as pay-as-you-go – that is, you pay only for the time you use the service.
  • You can scale it freely depending on the load. There is no need to implement or set anything. The function will scale to such an extent as to speed up the task execution as much as possible.
  • Each functionality can be divided into separate, independently developed modules.
  • You can write each functionality (function) using a given language, e.g. C #, Java, JavaScript, Python etc.

Azure serverless functions – summary

To sum up, thanks to the use of Azure Durable Function, you can freely create complicated workflows and fully control them. Native functions can work with the entire cloud ecosystem, so the possibilities of expansion are endless. What’s more, there is an option to use containers of functions in the on-premise environment – but that’s a matter for a separate article.

Technical leader in .NET technology, every day actively involved in the project implementation for JCommerce clients. He has been working in the profession for over 10 years. Specializes in solutions for web applications. After hours karate man, swimmer, dad.

    Get notified about new articles

    Be a part of something more than just newsletter

    I hereby agree that JCommerce Sp. z o.o. shall process my personal data (hereinafter ‘personal data’), such as: my full name, e-mail address, telephone number and Skype ID/name for commercial purposes.
    I hereby agree that JCommerce Sp. z o.o. shall process my personal data (hereinafter ‘personal data’), such as: my full name, e-mail address and telephone number for marketing purposes.

    Read more

    Just one click away!

    We've sent you an email containing a confirmation link. Please open your inbox and finalize your subscription there to receive your e-book copy.


    Note: If you don't see that email in your inbox shortly, check your spam folder.