Before understanding function chaining, we need to know what Azure Durable Functions are and the benefits of this framework.
In a Serverless architecture, developers are able to create a short-lived, stateless function that has one responsibility.
The function’s responsibility could be processing a file upload, acting as an API endpoint for a specific task, etc.
While this is great, there are some drawbacks. For example what if there were a situation where you want to write functions that are not short-lived or stateless? This is where durable functions come in.
Durable Functions is an open-source framework for Azure Functions that allows developers to write long-running orchestrations as a single function while maintaining state for all function calls.
In function chaining, there is an ordered list of functions that execute in a particular order, where the output of one function is the input of another function.
Let’s look at the diagram below:
Here, there are three functions, UploadImage
, TransformToPdf
, and SavePdfToStorage
, that following each other. Let’s imagine we want to call these functions when a post request is made to our application.
There are three key components that make this possible:
An orchestrator function (called ImageToPdfOrchestrator
). This orchestrator calls UploadImage
, TransformToPdf
, and SavePdfToStorage
in the order we want, while maintaining state.
Our three activity functions, UploadImage
, TransformToPdf
, and SavePdfToStorage
, each perform their own individual task.
ImageToPdf
You can start an instance of an orchestrator function using a client function. In this example, our client function is an HTTP triggered function called ImageToPdf
. To start instances of ImageToPdfOrchestrator
, we will call the HTTP triggered function.
To interact with the orchestrators, the function must include a DurableClient
input binding.
public static class ImageToPdf{[FunctionName("ImageToPdf")]public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, methods: "post", Route = "imagetopdf")] HttpRequestMessage req,[DurableClient] IDurableClient starter,ILogger log){var instanceId = Guid.NewGuid().ToString();var request = await req.Content.ReadAsAsync<RequestObject>();await starter.StartNewAsync("ImageToPdfOrchestrator", instanceId, request.LocalImageLink);log.LogInformation($"Started orchestration with ID = '{instanceId}'.");return starter.CreateCheckStatusResponse(req, instanceId);}}public class RequestObject{public string LocalImageLink { get; set; }}
In orchestrator functions, there must be a DurableOrchestrationContext
parameter. The aim of the DurableOrchestrationContext
is to call multiple activity functions using its CallActivityAsync
method.
The ImageToPdfOrchestrator
orchestrator function below first calls the UploadImage
function, and then returns a link to the image on the cloud.
Next, the orchestrator calls the TransformToPdf
function. After execution, a temporary link to the recently created pdf file is returned.
Thirdly, the orchestrator calls the SavePdfToStorage
function. After execution, a link to the uploaded pdf document is returned.
[FunctionName("ImageToPdfOrchestrator")]public static async Task<List<string>> Run([OrchestrationTrigger] IDurableOrchestrationContext context){// Get the string inputvar localImageLink = context.GetInput<string>();// This is where the activity functions are chained// 1. Call UploadImage to upload a local image to the cloud and return the online linkvar cloudImageLink = await context.CallActivityAsync<string>("UploadImage", localImageLink);// 2. Call TransformToPdf to transform the uploaded image to a pdf documentvar temporaryPdfLink = await context.CallActivityAsync<string>("TransformToPdf", cloudImageLink);// 3. Call SavePdfToStorage to upload the pdf document to a storage accountvar cloudPdfLink = await context.CallActivityAsync<string>("SavePdfToStorage", temporaryPdfLink);return cloudPdfLink;}
Activities use the [ActivityTrigger]
attribute. If you’d like to perform actions like GetInput<T>
(similar to what we did in the orchestrator on line 6), use the IDurableActivityContext
.
In our example, we have three activities: UploadImage
, TransformToPdf
, and SavePdfToStorage
.
[FunctionName("UploadImage")]public static string UploadImage([ActivityTrigger] IDurableActivityContext context){string link = context.GetInput<string>();// code to upload image// ...return imageLink;}
[FunctionName("TransformToPdf")]public static string TransformToPdf([ActivityTrigger] IDurableActivityContext context){string link = context.GetInput<string>();// code to transform pdf// ...return pdfLink;}
[FunctionName("SavePdfToStorage")]public static string SavePdfToStorage([ActivityTrigger] IDurableActivityContext context){string link = context.GetInput<string>();// code to save pdf to storage// ...return pdfStorageLink;}
As we have seen in this shot, durable functions allow us to chain multiple functions while maintaining state. This can be useful in multiple scenarios, including eCommerce, where there may be separate functions for checkout, charging the card, and logging the order.