Adventures with Durable Functions: Error Handling and Retries

Azure Function

Normally I use, and recommend, Logic Apps for orchestration, but I recently had an ask for sample code of a Durable Function that demonstrated error handling and retry logic.

The code for this article can be found at https://github.com/mattruma/MJR047.

Using the Azure Storage Emulator I created two queues, one called sayhello and another called sayhello-poison, the latter will be where we put messages that could not be processed.

In Visual Studio, I created a new C# Azure Function project.

I created a SayHelloRequest class to model an incoming requests.

public class SayHelloRequest
{
      [JsonProperty("names")]
      public IEnumerable<string> Names { get; set; }
}

I created a SayHelloOrchestrationTrigger class which contains my Durable Function using a OrchestrationTrigger and a single step that by Durable Function will call using an ActivityTrigger.

public static class SayHelloOrchestrationTrigger
{
    [FunctionName(nameof(SayHelloOrchestrationTrigger))]
    public static async Task<List<string>> RunOrchestrator(
        [OrchestrationTrigger] IDurableOrchestrationContext context,
        ILogger log,
        [Queue("sayhello-poison")] ICollector<string> errors)
    {
        log.LogInformation(
        	$"{nameof(SayHelloOrchestrationTrigger)} trigger function processed a request.");

        var data = context.GetInput<SayHelloRequest>();

        var outputs = new List<string>();

        foreach (var name in data.Names)
        {
            try
            {
                var retryOptions =
                    new RetryOptions(TimeSpan.FromSeconds(5), 3);

                outputs.Add(
                    await context.CallActivityWithRetryAsync<string>(
                        "Function1_Hello",
                        retryOptions,
                        name));
            }
            catch
            {
                errors.Add(
                	JsonConvert.SerializeObject(
                		new SayHelloRequest { Names = new[] { name } }));
            }
        }

        return outputs;
    }

    [FunctionName("Function1_Hello")]
    public static string SayHello([ActivityTrigger] string name, ILogger log)
    {
        log.LogInformation(
        	"Function1_Hello trigger function processed a request.");

        if (name == "Matt")
        {
            throw new ArgumentException(nameof(name));
        }

        log.LogInformation($"Saying hello to {name}.");

        return $"Hello {name}!";
    }
}

The RunOrchestrator method is looking for a SayHelloRequest class in the context paramter. The method then loops through all the names and calls the SayHello, which will output Hello {name}!.

The retryOptions variable defines the retry policy should an exception be encountered, in my example, I am defining the retry policy to try 3 times every 5 seconds.

If an exception is still encountered it then sends a new SayHelloRequest with the value of name as the name that generated the error.

If the name Matt is included in the list of names and exception will be generated.

I then created two more functions, SayHelloHttpTrigger, which calls the Durable Function from an Http Request, and the other, SayHelloQueueTrigger, which looks for messages in the sayhello Azure queue.

Run the Azure Function.

Open Postman, and execute the request.

Click on the statusQueryGetUri to see the executed request.

Execute the request again and include Matt.

Open Storage Explorer and look in the sayhello-poison queue, you should see a new message with a names property consisting of one element called Matt.

You can also trigger the Durable Function by dropping a message in the sayhello queue.

That’s it! Pretty straight forward, though took some digging!

Related Articles

Leave a Reply

Your email address will not be published.