Thursday, December 2, 2021

Azure Durable Functions to implement function chaining with custom data

In my previous post I explained how to post to Azure Function with .NET 6. Let's  complicate the scenario a little bit.

Let's say that we need to save a customer, but we will store customer id and name in our own SQL database and need to send the email address to a CRM system. 
Following are the steps
  • Insert customer name to Azure SQL instance
  • Generate auto increment id in Azure SQL
  • Insert customer contact in external CRM with auto increment customer id
  •  











Can we develop this logic in a single Function?

Yes it is possible. But it is not the best practice. Following are some of the issues and risks I can see
  • First of all it is violating the Single Responsibility concept. 
  • We should not start CRM insertion part until the SQL insertion is completed. So some sort of statefulness is required.
  • How can we ensure the durability and consistency. Let's say External CRM fails, what can we do for the entire transaction.
What would be the best solution then?

We can use Azure Durable Functions to achieve the functionality. 

What is Azure Durable Function?

Azure Durable Functions allow you to author stateful functions in a serverless compute environment. We need to break our logic into several functions.
  • Client Function (trigger)
  • Orchestrator Function
  • Activity Function
Let's implement our logic

Step 1: Create Function. We need to specify storage account during the creation process. This is to maintain the state. Scaffolding generates a sample base which is very helpful.


























Step 2: Let's add our customer class




















Step 3: Let's create our activity functions

[FunctionName("DurableCustomer_AddToSQL")]
[FunctionName("DurableCustomer_AddToSQL")]
public static int AddToSQL([ActivityTrigger] Customer customer, ILogger log)
{
    //insert to SQL logic
    int customerId = customer.id; //should be populated with the reusult
    return customerId;
}

[FunctionName("DurableCustomer_AddCRM")]
public static int AddToCRM([ActivityTrigger] int customerId, ILogger log)
{
    //insert to CRM
    int returnId = customerId * 10; //return code from CRM insert
    return returnId;
}

Step 4: Let's modify our orchestrator

[FunctionName("DurableCustomer")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
    Customer customer =  context.GetInput();

    int customerId = await context.CallActivityAsync("DurableCustomer_AddToSQL", customer);
    int returnId = await context.CallActivityAsync("DurableCustomer_AddCRM", customerId);

    return returnId;
}
 

Step 5: Let's do necessary amendments to our http trigger function to accept Customer object

public static async Task HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestMessage req,
[DurableClient] IDurableOrchestrationClient starter,
ILogger log)
{
    // Function input comes from the request content.

    var customer = await req.Content.ReadAsAsync();

    string instanceId = await starter.StartNewAsync("DurableCustomer", customer);

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

    return starter.CreateCheckStatusResponse(req, instanceId);
}

Step 6: Let's deploy our function













Step 7: Let's try this with Postman


















Step 8: Get the StatusQueryGetUri to check the status






















Great !!. We will discuss how to handle retry operation in my next post

No comments: