Azure Durable Functions – Part 1

Category: Blogs | Cloud/Azure
By: Sunita Kawale
Featured image for a blog about Azure Durable Functions

What is Azure functions?

Azure Functions is Azure’s Function-as-a-Service offering.

A serverless cloud environment for your application to run with less code, less infrastructure maintenance, and save your cost. This helps to reduce the deployment time and worries of maintenance. Each Azure Function has a trigger, that’s what causes it to execute. For example, running your code after a record is deleted from the database or new user is created or user permissions are changed. Moreover, the initial executions of your function/applications are free of cost.

 

What is a Durable function?

Executing stateful functions in a serverless cloud environment is a durable function. It is an extension of the azure function allowing you to write stateful workflows. Durable functions are divided into two parts one is Orchestrator functions which help us to write the stateful workflows and the second is Entity functions to handle the stateful objects of the workflow/programs. In durable functions, we can manage the restart of the function, the status of the function, and add the checkpoints too, allowing us to focus on the business development.

 

Supporting languages of durable functions?

Mostly all languages are supported by a durable function which are work with the normal azure functions but there is a minimum requirement for all languages which is shown in the below table.

Language Stack Azure Functions Runtime versions Language worker role version Minimum bundle version
.NET / C# / F# Functions 1.0+ In-process (GA) Out-of-process N/a.
Javascript/Typescript Functions 2.0+ Node 8+ 2.x bundles
Python Functions 2.0+ Python 3.7+ 2.x bundles
PowerShell Functions 3.0+ PowerShell 7+ 2.x bundles
Java (preview) Functions 3.0+ Java 8+ 4.x bundles

Types of durable functions?

  1. Function Chaining (Covered in this article)
  2. Fan-Out/Fan-In (Covered in this article)
  3. Async HTTP APIs
  4. Monitoring
  5. Human interaction
  6. Aggregator (Stateful entities)

 

What is Function Chaining?

A series of functions get executed after every function and the next function depends on the previous function output called function linking or chaining.

In the below diagram, we can see function F2 depends on the output of function F1 and so on.

A durable function environment monitors the status of the function execution, While the execution of the function system got failed then the durable function will start the function instance from the previous point.

Example of function Chaining

Consider we need to create a workflow for the user registration, on successful registration we will assign the permissions and then send a confirmation email.

In the example below, we created a simple workflow function ChainingFunction with three activities. The first activity function is RegisterUser. Once we get the first activity function’s result, we send the result to the next activity function AssignUserPermissions and then the third activity function SendConfirmationEmail email.

[FunctionName("ChainingFunction")]
public static async Task<string> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
{
    try
    {
       var user = await context.CallActivityAsync<string>("RegisterUser", "Sunita");
       var assignedPermissions =  context.CallActivityAsync<bool>("AssignUserPermissions", user);
       var mailSend =  context.CallActivityAsync<bool>("SendConfirmationEmail", user);
       return user;
    }
    catch (Exception ex)
    {
        log.LogError($"Unhandled exception Message {ex.Message}. {ex.StackTrace}");
    }

    return string.Empty;
}

[FunctionName("RegisterUser")]
public static async Task<string> CreateUser([ActivityTrigger] string name, ILogger log)
{
    var createUser = await Task.Factory.StartNew<string>(() => $"{name}@test.com");
    log.LogInformation($"Saying hello to {name}.");
    return createUser;
}

[FunctionName("AssignUserPermissions")]
public static async Task<bool> AssignUserPermissions([ActivityTrigger] string userName, ILogger log)
{
    var assignPermissions = await Task.Factory.StartNew<bool>(() =>
    {
        Thread.Sleep(30);
        return true;
    });
    return assignPermissions;
}

[FunctionName("SendConfirmationEmail")]
public static async Task<bool> SendConfirmationEmail([ActivityTrigger] string userName, ILogger log)
{
    var sendEmail = await Task.Factory.StartNew<bool>(() =>
    {
        Thread.Sleep(30);
        return true;
    });
    return sendEmail;
}

What is Fan-Out/Fan-in?

Processing the multiple functions in parallel and then doing the aggregation on the results is referred to as the Fan-out/Fan-in.

In the below diagram function F1 starts multiple functions F2 to execute in parallel and function F3 will do the aggregations on the results of F2.

Example of Fan-Out/Fan-In

Consider, in a university, we need to find the top three students by the total number of marks they got in each subject.

To achieve the above objective, we will create a Fan-Out/Fan-In durable function.

GetTotalMarks is the activity function we are making parallel execution for each student’s total marks. We will wait until all students get the total marks and then find the top N students by calling the activity function GetTopNStudent.

[FunctionName("FanOutFanInFunction")]
public static async Task<List<Student>> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var parallelTasks = new List<Task<Student>>();
    RequestTopNStudents inputdata = con-text.GetInput<RequestTopNStudents>();

    // Get a list of N work items to process in parallel.
    try
    {
        if (inputdata != null)
        {
            foreach (var item in inputdata.Students)
            {
                Task<Student> task = con-text.CallActivityAsync<Student>("GetTotalMarks", item);
                parallelTasks.Add(task);
            }
            var all = await Task.WhenAll(parallelTasks);
            int topN  = inputdata.TopNStudents;
            (List<Student>, int) inputs = new(all.ToList(), topN); //value tuple to send multiple parameters to activity function
            
            List<Student> topNStudents = await con-text.CallActivityAsync<List<Student>>("GetTopNStudent", inputs);
            return topNStudents;
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("error {0}", ex);
    }
    return null;
}

[FunctionName("GetTotalMarks")]
public static Student GetTotalMarks([ActivityTrigger] Student student)
{
    student.Total = student.MathsMarks + student.PhysicsMarks + stu-dent.ChemistryMarks;
    return student;
} 

[FunctionName("GetTopNStudent")]
public static List<Student> GetTopNStudent([ActivityTrigger] IDurableActiv-ityContext inputs)
{
    var (students, topN) = inputs.GetInput<(List<Student>, int)>();

    students.Sort((a, b) => b.Total.CompareTo(a.Total));
    return students.Take(topN).ToList();
} 
 

Reference document: Azure Functions | overview Microsoft Learn

In the next part, we will cover the different types of Durable functions and ways to deploy on Azure.