Monitor Pattern - Part 5 - Azure Durable Functions
Monitor Pattern - Part 5 - Azure Durable Functions
• Azure Functions, Serverless, .NET, Azure
• 4 min read
The Monitor pattern is implemented as a long-running operation that consumes an external endpoint, checking for a state change, different from the async HTTP API pattern, which reversely exposes an endpoint for an external client to monitor a long-running operation.
This is a series where I am explaining the components of durable functions1 and the different application patterns of implementation.
In this flow, the orchestration initiates a long-running asynchronous process and then periodically polls to see if the operation is complete. This is orchestrated by using a timer and loop.
This example was a slight modification of Microsoft’s example, to reflect a Real World implementation. It is a durable function created to monitor an Image Processing task, that can take some time for processing.
The Monitor_HttpStart is the HTTP Trigger.
Starter Function (aka DurableClient)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[FunctionName($"{nameof(Monitor)}_HttpStart")]publicasyncTask<HttpResponseMessage>HttpStart([HttpTrigger(AuthorizationLevel.Anonymous,"get","post")]HttpRequestMessagereq,[DurableClient]IDurableOrchestrationClientstarter,ILoggerlog){varimageDto=awaitreq.Content.ReadAsAsync<ImageDto>();varinstanceId=awaitstarter.StartNewAsync(nameof(Constants.RunOrchestrator),imageDto);log.LogInformation($"Started orchestration with ID = '{instanceId}'.");returnstarter.CreateCheckStatusResponse(req,instanceId);}
The Orchestrator is responsible for calling all the Activities. On this Monitor pattern note that we are dealing with a while loop, checking for a time represented by isProcessWithinTime.
Depending on the jobStatus, the Orchestrator sleeps during the pollingInterval, getting retriggered automatically because a Timer is created with context.CreateTimer:
publicclassOrchestrator{[FunctionName(nameof(Constants.RunOrchestrator))]publicasyncTaskRunOrchestrator([OrchestrationTrigger]IDurableOrchestrationContextcontext,ILoggerlog){varimageDto=context.GetInput<ImageDto>();varpollingInterval=GetPollingInterval();varexpiryTime=GetExpiryTime(context);awaitcontext.CallActivityAsync(nameof(Constants.RunProcessImageActivity),imageDto);varisProcessWithinTime=context.CurrentUtcDateTime<expiryTime;while(isProcessWithinTime){varjobStatus=awaitcontext.CallActivityAsync<ImageStatus>(nameof(Constants.RunGetStatusImageActivity),imageDto.FileName);if(jobStatus==ImageStatus.Processed){// Perform an action when a condition is met.
awaitcontext.CallActivityAsync(nameof(Constants.RunSendAlertActivity),imageDto.FileName);break;}// Orchestration sleeps until this time.
varnextCheck=context.CurrentUtcDateTime.AddSeconds(pollingInterval);awaitcontext.CreateTimer(nextCheck,CancellationToken.None);}if(!isProcessWithinTime){// Operation has timed out
log.LogWarning($"The image process operation for {imageDto.FileName} has timed out.");}}privateDateTimeGetExpiryTime(IDurableOrchestrationContextcontext){returncontext.CurrentUtcDateTime.AddMinutes(5);//Define the expiry time
}privateintGetPollingInterval(){return15;//Define the polling interval
}}
In case it times out, Logging is recorded.
Activity Functions
The activities in this demo are basically placeholders for Real World actions:
publicclassActivity{privatereadonlyIImageProcessor_imageProcessor;privatereadonlyILogger<Activity>_logger;publicActivity(IImageProcessorimageProcessor,ILogger<Activity>logger){_imageProcessor=imageProcessor;_logger=logger;}[FunctionName(nameof(Constants.RunProcessImageActivity))]publicvoidRunProcessImageActivity([ActivityTrigger]ImageDtoimageDto){_logger.LogInformation($"{nameof(RunProcessImageActivity)} for {imageDto.FileName}");_imageProcessor.Process(imageDto.FileName,imageDto.File);}[FunctionName(nameof(Constants.RunGetStatusImageActivity))]publicImageStatusRunGetStatusImageActivity([ActivityTrigger]stringfileName){_logger.LogInformation($"{nameof(RunGetStatusImageActivity)} for {fileName}");return_imageProcessor.GetStatus(fileName);}[FunctionName(nameof(Constants.RunSendAlertActivity))]publicvoidRunSendAlertActivity([ActivityTrigger]stringfileName){_logger.LogInformation($"{nameof(RunSendAlertActivity)} for {fileName}");//TODO: send alert
}}
The ImageProcessor is just a dummy that represents an interface that would make a call to an external service:
1
2
3
4
5
6
7
8
9
10
11
12
13
publicclassImageProcessor:IImageProcessor{publicvoidProcess(stringfileName,byte[]file){//TODO: process image
}publicImageStatusGetStatus(stringfileName){//TODO: check status dynamically
returnImageStatus.Processed;}}
This is an example of how to use the Monitor pattern, applied to monitor the processing of an image.
In the next article of the series let’s check out how to use the Human interaction pattern.
Join the conversation! Share your thoughts and connect with other readers.