What is Azure Durable Function 17th July 2021

heading

Stateful Workflows on top of Stateless Serverless Cloud Functions i.e Durable Functions are an extension of Azure Functions that lets you write stateful functions in a serverless environment. I did write up on a bit of history and why we need Azure Durable Functions. You can read that article here. Link below Why do we need Azure Durable Functions and its history

When there’s a workflow involved we require some stored state. Like I mentioned in the previous article, Azure Functions are stateless, so Microsoft Azure Durable Functions came into play to cover such situations. Since Durable functions allow to orchestrate other functions, they can help to create workflows in which status is saved automatically using storage. Durable functions manage state, checkpoints, and restarts for you. Durable Function is an open-source library that brings workflow orchestration abstractions to Azure functions. Being open-source, MS accepts external contributions and community is very active

From the point of view of pricing, the durable functions extension follows the same pricing model as Azure functions (more info here).

In the previous article, we also saw few challenges in our hypothetical example. Now let's try to see how Azure Durable functions can solve these. But before we go ahead,there are few terminologies we should be aware of. The best I thought is to depict it as a diagram

durablefunctiontypes

Now that we know we require Activity Functions and Orchestrator functions to build workflow functionality, let's dwell a little further into each one of them to solve our challenge and adapt it to our scenario

durablefunctiontypes

Above we see that a client function starts an instance of an orchestration. Client function may be triggered as an HTTP endpoint we hit from an application. Client functions start an instance of orchestration and we get an instance id, our unique reference to that specific flow. Next, we try to carry out everything inside of the orchestration. Orchestrations' most basic form is where it chains multiple independent activities into a single sequential workflow and manages the flow of execution and data among several activity functions.

In our example, book tech symposium tickets, reserve a seat on the bus, and book accommodation have their own activity functions held together by an orchestrator function. Your orchestrator function code may look like something below



              [FunctionName("Myworkflow")]
              public static async Task Sequential([OrchestrationTrigger] DurableOrchestrationContext context)
              {
                  var symposium = await context.CallActivityAsync("BookTechSymposium", "Trip");
                  var travel = await context.CallActivityAsync("ReserveBus", symposium.Response.Dates);
                  await context.CallActivityAsync("BookAccomodation", travel.Response.Dates);
              }
              
              

CallActivityAsync is key here. When Orchestrator starts running and comes across this method, it sends a queue message to the corresponding Activity function. In this case, the first BookTechSymposium activity function runs which performs it's task i.e Booking the tech symposium tickets and returns the result. It sends a queue message back to the orchestrator. When a message arrives, the orchestrator gets triggered again and moves on to the next activity. This way both remaining activity functions get triggered too one by one i.e Reserve bus and book accommodation runs one by one

Now the interesting part is Billing. Azure Functions on the serverless consumption-based plan are billed per execution + per duration of execution. One thing to note with orchestration is that what it orchestrates usually is asynchronous which means we don't know exactly when something finishes. So that you don't pay the running costs for it, durable functions power down and save the state.

When an orchestration function is given more work to do, for eg say a response message is received, the orchestrator wakes up and re-executes the entire function. It always starts with the first line and means that the same line is executed multiple times. Of course, it's not dumb to restart everything from scratch. It stores the history of the past executions in Azure Storage, so the effect of a second pass or every subsequent pass of the first line is different. During the replay, if the code tries to call a function, then Durable Task frameworks internally check the execution history of the current orchestration.If it finds that the activity function has already expected and yielded a result, it replays the function's result and the orchestrator code continues to run.

Because of these “replays”, the orchestrator’s implementation has to be deterministic: don’t use DateTime.Now, random numbers or multi-thread operations etc. The whole behavior of orchestrator is like stop-resume behavior

Error handling
errorhandling

Now in the previous article, we also discussed the possibility of errors, like what if the third party bus reserving application is down. Then our application needs to handle the error gracefully or even run some compensating logic, for instance, roll back the whole workflow or book a cab may be. Instead of silently failing, the activity function, in this case booking bus function sends a message contention information about the error back to the Orchestrator. Orchestrator deserializes the error detail, and at the time of replays throws a .NET exception. The developer can handle the exception using a try..catch block around the call and decide what to do. For eg.



              [FunctionName("MyworkflowWithCompensatingLogic")]
              public static async Task Sequential([OrchestrationTrigger] DurableOrchestrationContext context)
              {
                  var symposium = await context.CallActivityAsync("BookTechSymposium", "Trip");
                  try{
                  var travelViaBus = await context.CallActivityAsync("ReserveBus", symposium.Response.Dates);
                  }catch(Exception){
                    var travelViaCab= await context.CallActivityAsync("BookCab", symposium.Response.Dates);
                  }
                  await context.CallActivityAsync("BookAccomodation", travel.Response.Dates);
              }
              
              

You can even apply an automatic retry policy. All you need to use is RetryOptions.Below code asks the library to:

  • Retry up to 3 times
  • Wait for 1 minute before the first retry
  • If an error is not intermittent, there is no point retying immediately. We can configure the client to retry the request periodically with increased delays between requests. Here we have the Backoff coefficient as 2 which means delays increase y factor of 2. i.e first one happens in 1 min, sec one after 2 .third after 4 mins and so on, etc.)


              [FunctionName("MyworkflowWithRetry")]
              public static async Task Sequential([OrchestrationTrigger] DurableOrchestrationContext context)
              {
                  var symposium = await context.CallActivityAsync("BookTechSymposium", "Trip");

                  var myRetryOptions= new RetryOptions(
                    firstRetryInterval: TimeSpan.FromMinutes(1),
                    maxNumberOfAttempts: 3);
                   myRetryOptions.BackoffCoefficient = 2.0;

                    var travelViaBus = await context.CallActivityAsync("ReserveBus",myRetryOptions, symposium.Response.Dates);                   
                    await context.CallActivityAsync("BookAccomodation", travel.Response.Dates);
              }
              
              
Conclusion.

To conclude,I hope you would have got better hold on Azure Durable functions. If you are interested to learn more about Azure Durable Functions then follow below articles:
Why do we need Azure Durable Functions and its history
What is difference between Azure Durable Functions and Logic Apps
Patterns And Best Practises- Azure Durable Functions
Simple Azure Durable function implementation


Email me at "techspacedeck@gmail.com" incase you have queries. Alternatively, you can fill the "CONTACT" form or drop a comment below

Did you like the blog or have questions, please add your comments