Dynamically select orchestrators in Azure Durable Functions

Dynamically select orchestrators in Azure Durable Functions

This is one of the awesome things that I think can be done in functions, which also enables us to build a lot of very interesting mechanics, it’s not that complicated to pull off, but the results of using this technique might lead to very impressive outcomes.

Before we begin though, there are some disclaimers:

Disclaimer #1: The code example that I prepared is quite ridiculous, but I couldn’t find anything more simple to show this, since any other ideas I had pulled the focus to the business logic.

Disclaimer #2: It’s really ridiculous, please don’t do things like this in real life ( string building via durable functions workflows :D)

Disclaimer #3: In the code examples there aren’t many logging statements nor many exception handling, please for production code abuse logging and use exception handling, as with #1, the code example is designed to show this functionality in a theoretical concept and make it as clear as possible.

Good, now that we got this out of the way, let’s dig in.

Requirements:

  1. As usual, we need a function app with the durable function nuggets installed ( if you want to follow along). If you need any guidance check this link.

  2. A basic understanding of azure functions and durable functions

What are we building?

In this example we will have the following scenarios: Alt Text

So, we will have the default HTTP trigger that asks for a name, and instead of returning the name, we will forward the name to our “Dynamic Orchestrator”. This orchestrator will decide if it is a short or long name, and based on this will call the LongName or the ShortName orchestrator. Each of these sub-orchestrators will have an activity that will actually build a string with a greeting and then return it. I know, brilliant example! Anyway, enough with the talk and drawing, let’s get into the code.

As I said earlier the trigger is basically the one that you get from the template with a few small tweaks:

public static class DynamicOrchestratorTrigger
{
    [FunctionName("DynamicOrchestratorTrigger")]
    public static async Task<IActionResult> RunAsync(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
        HttpRequest req, ILogger log,
        ExecutionContext executionContext,
        [DurableClient] IDurableOrchestrationClient starter)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");

        string name = req.Query["name"];

        var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        dynamic data = JsonConvert.DeserializeObject(requestBody);
        name ??= data?.name;
        if (name == null)
        {
            return new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }

        var orchestratorInput = new OrchestratorInput
        {
            CorrelationId =  new Guid(),
            Name =  name.ToString()
        };

        var instanceId = await starter.StartNewAsync(nameof(DynamicOrchestrator), orchestratorInput);

        DurableOrchestrationStatus durableOrchestrationStatus = await starter.GetStatusAsync(instanceId);
        while (durableOrchestrationStatus.RuntimeStatus != OrchestrationRuntimeStatus.Completed)
        {
            await Task.Delay(200);
            durableOrchestrationStatus = await starter.GetStatusAsync(instanceId);
        }

        return (ActionResult) new OkObjectResult(durableOrchestrationStatus.Output);
    }
}

Please note, that line 30 – 35 are more or less a hack for this example, what this does is basically polling the orchestrator until it’s completed to and than returns the output, the only reason this is done is for “you” to easily test this out if you get the project from GitHub (link will be at the end of the post). I don’t advise using something like this in production.

So, next, we will look at the DynamicOrchestrator, here is where the magic happens:

public class DynamicOrchestrator
{

    [FunctionName(nameof(DynamicOrchestrator))]
    public async Task<string> Run([OrchestrationTrigger] IDurableOrchestrationContext context,
        ExecutionContext executionContext)
    {
        var input = context.GetInput<OrchestratorInput>();

        string greeting = "";
        switch (input.Name.Length)
        {
           case <= 4 :
               greeting = await context.CallSubOrchestratorAsync<string>(nameof(ShortNameOrchestrator), input);
               break;
           default:
               greeting = await context.CallSubOrchestratorAsync<string>(nameof(LongNameOrchestrator), input);
               break;
        }

        return greeting;
    }
}

So, at this point, I think you are either thinking “now this was underwhelming… “, or “this is amazing!”. As I said in the beginning, this might not look like much, and of course, this example doesn’t help either, but this simple construct enables you to do some really cool stuff. If this is not obvious let me refactor this code into this:

public class DynamicOrchestrator
{

    [FunctionName(nameof(DynamicOrchestrator))]
    public async Task<string> Run([OrchestrationTrigger] IDurableOrchestrationContext context,
        ExecutionContext executionContext)
    {
        var input = context.GetInput<OrchestratorInput>();
        return await context.CallSubOrchestratorAsync<string>(
            await context.CallActivityAsync<string>(
                nameof(DetermineOrchestratorToRunActivity), input.Name)
            ,input);
    }
}

Basically, what has happened here, we turned this orchestrator more or less in a HOF (higher-order function). By doing this we can even swap the “DetermineOrchestratorToRunActivity” if we wanted to.

Speaking of “DetermineOrchestratorToRunActivity”, in our case, this is just a simple activity that should perform the business logic that will result in our next step:

public class DetermineOrchestratorToRunActivity
{

    [FunctionName(nameof(DetermineOrchestratorToRunActivity))]
    public async Task<string> Run([ActivityTrigger] IDurableActivityContext  context)
    {
        var input= context.GetInput<string>();
        string nameOfOrchestrator;

        switch (input.Length)
        {
           case <= 4 :
               return nameof(ShortNameOrchestrator);
               break;
           default:
               return nameof(LongNameOrchestrator);
               break;
        }
    }
}

Maybe this doesn’t look like much, but instead of the length switch here, you could do latterly anything here, and I think this is quite interesting.

The rest of the orchestrators and activities are quite dumb and simple and don’t think it has any point in showing them here, but you can see a working example in the GitHub repository.