Understanding Azure Durable Functions - Part 4: Passing Input To Orchestrations and Activities

This is the fourth part in a series of articles.

In the first part of the series we learned that Durable Functions consist of three types of function: client, orchestrator, and activity functions.Up until this point, the only data we’ve made use of has been hardcoded in the orchestrator function:

await context.CallActivityAsync<string>("ReplayExample_ActivityFunction", "Tokyo");

In the preceding code, the ReplayExample_ActivityFunction is being called and the hardcoded data "Tokyo” is being passed to the activity. It’s more likely in real use that data won’t be hardcoded but will instead be either passed to the client function for use in the activity, or read in using one of the standard input bindings.

How To Pass Data to a Durable Functions Orchestrator Function

The first step in this scenario is to allow the initiator of the orchestration to provide some data. One way to do this is with an HTTP-triggered Azure Function that allows the caller to provide some JSON data, to represent this data we can create a class:

class SayHelloRequest
{
    public List<string> CityNames { get; set; }
}

This class can now be deserialized into from the incoming JSON data:

var data = await req.Content.ReadAsAsync<SayHelloRequest>();

To pass input data to an orchestration, it can be supplied to the StartNewAsync method of the DurableOrchestrationClient:

string instanceId = await starter.StartNewAsync("DataExample", data);

The full listing of the client function is now:

[FunctionName("DataExample_HttpStart")]
public static async Task<HttpResponseMessage> HttpStart(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequestMessage req,
    [OrchestrationClient]DurableOrchestrationClient starter,
    ILogger log)
{
    var data = await req.Content.ReadAsAsync<SayHelloRequest>();

    string instanceId = await starter.StartNewAsync("DataExample", data);

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

    return starter.CreateCheckStatusResponse(req, instanceId);
}

Now we are sending the data, we can make use of it in the orchestrator.

How To Read Data Passed Into an Orchestrator Function

To read data that was passed into the orchestrator, the GetInput method of the DurableOrchestrationContext can be used:

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

One thing to note here is that the input data must be JSON serializable .

Now the data is available in the orchestrator function, it can be passed down to the activity function(s) instead of the hardcoded data we used earlier. In this example the activity function does not need to change.

The full listing for the new version of the orchestrator:

[FunctionName("DataExample")]
public static async Task RunOrchestrator(
    [OrchestrationTrigger] DurableOrchestrationContext context, ILogger log)
{
    log.LogInformation($"************** RunOrchestrator method executing ********************");

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

    foreach (var city in data.CityNames)
    {
        await context.CallActivityAsync<string>("DataExample_ActivityFunction", city);
    }            
}

Passing Data to an Azure Functions Durable Function Activity

The second parameter in the call to the CallActivityAsync method of the DurableOrchestrationContext allows an object to be passed to an activity function when it is called.

For example if the activity function is defined as: public static string SayHello([ActivityTrigger] string name, ILogger log) then the orchestrator can pass a single string to this activity when it is called.

To pass more data you could change this from a simple string to a more complex type as the following modified versions shows:

using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;

namespace DurableDemos
{
    public static class DataExample2
    {
        [FunctionName("DataExample2")]
        public static async Task RunOrchestrator(
            [OrchestrationTrigger] DurableOrchestrationContext context, ILogger log)
        {
            log.LogInformation($"************** RunOrchestrator method executing ********************");

            GreetingsRequest data = context.GetInput<GreetingsRequest>();

            foreach (var greeting in data.Greetings)
            {
                await context.CallActivityAsync<string>("DataExample2_ActivityFunction", greeting);
            }            
        }

        [FunctionName("DataExample2_ActivityFunction")]
        public static string SayHello([ActivityTrigger] Greeting greeting, ILogger log)
        {            
            log.LogInformation($"Saying '{greeting.Message}' to {greeting.CityName}.");
            return $"{greeting.Message} {greeting.CityName}";
        }

        public class Greeting
        {
            public string CityName { get; set; }
            public string Message { get; set; }
        }

        public class GreetingsRequest
        {
            public List<Greeting> Greetings { get; set; }
        }

        [FunctionName("DataExample2_HttpStart")]
        public static async Task<HttpResponseMessage> HttpStart(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequestMessage req,
            [OrchestrationClient]DurableOrchestrationClient starter,
            ILogger log)
        {
            var data = await req.Content.ReadAsAsync<GreetingsRequest>();

            string instanceId = await starter.StartNewAsync("DataExample2", data);

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

            return starter.CreateCheckStatusResponse(req, instanceId);
        }
    }
}

Notice in the preceding code that the activity now accepts a more complex type, a Greeting.

SHARE:

Add comment

Loading