An in-depth guide to Dapr workflow patterns in .NET
After covering Dapr workflow basics in the previous article, let’s take a look at the different application patterns that can be used with Dapr workflow and .NET. The patterns covered in this post are: chaining, fan-out/fan-in, monitor, and external system interaction.
After covering Dapr workflow basics in the previous article, let’s take a look at the different application patterns that can be used with Dapr workflow and .NET. The patterns covered in this post are: chaining, fan-out/fan-in, monitor, and external system interaction.
Knowing which workflow patterns are available and when to use them is essential for using any workflow engine properly. Dapr workflows are stateful due to the underlying event-sourcing implementation. Workflows need to be written in a deterministic way because the workflow will replay several times during the execution. Some familiar programming constructs such as while loops, waiting/sleeping, and running parallel tasks are therefore required to be written in a workflow-friendly way, which this post covers in detail.
Prerequisites
All code samples shown in this post are taken from the dapr-workflow-demos GitHub repo. You can fork/clone the repository and run all the workflow samples locally. The following is required to run the samples:
- .NET 7 SDK
- Docker Desktop
- Dapr CLI (v1.11 or higher)
- A REST client, such as cURL, or the VSCode REST client extension.
For simplicity, all workflow samples are using the same workflow activity, named <inline-h>CreateGreetingActivity<inline-h>. This activity will return a random greeting from an array of greetings and combines it with the input.
Chaining pattern
The purpose of the chaining pattern is to enforce a specific order of activity executions. The output of an activity is usually required as an input for the next activity. Or there is a strict sequence of interactions with external systems that are called from the activities.
A real life use case where chaining is essential is an order process. First the inventory is checked, then the payment starts, and finally the shipping is initiated.
ChainingWorkflow sample
The <inline-h>ChainingWorkflow<inline-h> sample in the GitHub repo contains a sequence of three activity calls. In this workflow, a random greeting will be added to the input string. Each time the <inline-h>CreateGreetingActivity<inline-h> is called, the output of the activity is used as the input for the next activity.
Note that each <inline-h>CallActivityAsync<inline-h> method is awaited. This means the workflow will wait until that activity is completed before continuing with the next call. Every time an activity is scheduled for execution, the workflow becomes idle, and it restarts when the activity is done.
Running the ChainingWorkflow sample
1. Open the <inline-h>BasicWorkflowSamples<inline-h> folder in a terminal and build the project:
2. Start the workflow application via the Dapr CLI:
3. Start the <inline-h>ChainingWorkflow<inline-h> via the Workflow HTTP API using cURL, or use the basicworkflows.http file if you're using VSCode with the REST client:
Note that 1234b in the URL is the workflow instance ID. This can be any string you want.
5. Expected result:
6. Check the workflow status via Workflow HTTP API:
7. Expected result:
Fan-out/Fan-in
The purpose of the fan-out/fan-in pattern is to execute multiple activities in parallel (fan-out) and combine the result of these activities when all of them have completed (fan-in). The activities are independent of each other, and there’s no requirement of ordered execution.
A use case where fan-out/fan-in is useful is processing arrays of data, like order items. A workflow is started where the input argument is an array of order items, the workflow iterates over the array and processes each order item individually. The workflow ends with an aggregate, such as the summation of the total price across all order items.
FanOutFanInWorkflow sample
The <inline-h>FanOutFanInWorkflow<inline-h> sample in the GitHub repo contains a fan-out/fan-in pattern where for each name in the input, an activity call to execute a <inline-h>CreateGreetingActivity<inline-h> is created, but not immediately scheduled and executed. The result of the workflow is an array of three individual greetings.
Note that in this sample, the activity calls are not directly awaited, as is the case with the chaining pattern. In this case, a list of <inline-h>Task<string><inline-h> is created, where <inline-h>string<inline-h> is the output type of <inline-h>CreateGreetingActivity<inline-h>, and the <inline-h>CallActivityAsync<inline-h> tasks are added to this list. After the foreach loop, all tasks are scheduled and awaited with this line of code:
var messages = await Task.WhenAll(tasks);
Copy to clipboard
This means that the workflow will wait until all activity tasks have been completed. The result of all the individual tasks are combined and is assigned to the messages variable. Since the output type of the activity is <inline-h>string,<inline-h> the combined result (<inline-h>messages<inline-h>) is an array of type <inline-h>string<inline-h>.
Waiting for the first activity
A variation of the fan-out/fan-in pattern is to run the workflow until the first of the activity calls in the list is completed. An example of this is running competing tasks for the same input. A real-life use case is executing a text search and testing various search algorithms. The workflow schedules activities that use different search algorithms. The input for the activities are all the same; a search term and a body of text to search through. The activity that implements the most efficient search algorithm will complete first, and the workflow continues.
In this variation, <inline-h>Task.WhenAny<inline-h> is used where the result is a single value from the activity that completed first.
Running the FanOutFanInWorkflow sample
1. Ensure that the BasicWorkflowSamples app is still running, if not change to the BasicWorkflowSamples directory, build the app, and run the app using the Dapr CLI:
2. Start the <inline-h>FanOutFanInWorkflow<inline-h> via the Workflow HTTP API using cURL, or use the basicworkflows.http file if you're using VSCode with the REST client:
3. Note that 1234c in the URL is the workflow instance ID. This can be any string you want.
4. Check the workflow status via Workflow HTTP API:
5. Expected result:
Monitor
The purpose of the monitor pattern is to periodically execute one or more activities in a loop at a specified frequency, for example once every hour or daily at midnight. Occasionally, a condition is used that determines if the workflow is restarted or completed.
A real-life use case of the monitor pattern is cleaning up cloud resources at the end of every day.
MonitorWorkflow sample
The <inline-h>MonitorWorkflow<inline-h> sample in the GitHub repo contains a workflow with a numeric input (counter), it calls the <inline-h>CreateGreetingActivity<inline-h>, then checks if the counter is less than 10. If that is true, the counter is incremented, the workflow waits for one second and restarts (with an updated counter). The workflow is restarted until the counter reaches 10.
Note that restarting the workflow is done by calling the <inline-h>ContinueAsNew<inline-h> method. This instructs the workflow engine to restart the workflow as a new instance without the previous (replay) data. The updated <inline-h>counter<inline-h> value is passed in as the input argument of the workflow.
Running the MonitorWorkflow sample
1. Ensure that the BasicWorkflowSamples app is still running, if not change to the BasicWorkflowSamples directory, build the app, and run the app using the Dapr CLI:
2. Start the <inline-h>MonitorWorkflow<inline-h> via the Workflow HTTP API using cURL, or use the basicworkflows.http file if you're using VSCode with the REST client:
Note that 1234c in the URL is the workflow instance ID. This can be any string you want.
3. Expected result:
4. Check the workflow status via Workflow HTTP API:
5. Expected result:
External system interaction
The purpose of the external system interaction pattern is to wait on the execution of a part of the workflow until an external system raises an event that is received by the workflow.
A real-life use case of the external system interaction pattern is an approval workflow where a manager needs to approve a purchase by one of their employees. The employee initiates the workflow by making a purchase, a message is sent to the manager and the workflow will wait until it has received the event that the manager sends.
ExternalInteractionWorkflow sample
The <inline-h>ExternalInteractionWorkflow<inline-h> sample in the GitHub repo contains a workflow that will wait as soon as the workflow starts. The <inline-h>WaitForExternalEventAsync<T><inline-h> method is used to instruct the workflow engine to wait with further execution until an event named <inline-h>“approval-event”<inline-h> has been received or the timeout of 20 seconds has expired. The <inline-h>ApprovalEvent<inline-h> is used as the generic output type parameter. This is the type the workflow expects to receive. It can be any user-defined serializable object.
Once the event has been received or the timeout expired, the <inline-h>IsApproved<inline-h> property of the event is checked to determine if the <inline-h>CreateGreetingActivity<inline-h> will be scheduled.
Although the timeout parameter is optional, it is considered a best practice to provide one. Otherwise, the workflow will be waiting indefinitely. The timeout in the sample is short for demonstration purposes, but a timeout in a real-life use case can be hours, days or longer.
If a <inline-h>try/catch<inline-h> block is not used and the timeout expires, the workflow will terminate with a <inline-h>FAILED<inline-h> status because of the <inline-h>TaskCanceledException<inline-h>.
Running the ExternalInteractionWorkflow sample
1. Ensure that the <inline-h>BasicWorkflowSamples<inline-h> app is still running, if not change to the <inline-h>BasicWorkflowSamples<inline-h> directory, build the app, and run the app using the <inline-h>Dapr<inline-h> CLI:
2. Start the <inline-h>ExternalInteractionWorkflow<inline-h> via the Workflow HTTP API using cURL, or use the basicworkflows.http file if you're using VSCode with the REST client:
Note that 1234f in the URL is the workflow instance ID. This can be any string you want.
3. Expected result:
4. Check the workflow status via Workflow HTTP API before the timeout expires (make the request within 20 seconds):
5. The <inline-h>runtimeStatus<inline-h> should be <inline-h>RUNNING<inline-h> as the workflow is waiting for an event or the timeout:
6. Let the timeout expire (wait 20 seconds) and check the workflow status again via Workflow HTTP API:
7. The <inline-h>runtimeStatus<inline-h> will be <inline-h>COMPLETED<inline-h>, with a <inline-h>custom_status<inline-h> that the wait for external event is cancelled due to timeout:
8. Now start the workflow again, raise an event within the timeout duration (20 seconds), and retrieve the status of the workflow.
9. Raising an event is done by calling the <inline-h>raiseEvent<inline-h> endpoint of the workflow management API. Note that the workflow ID is also part of the URL. The payload is a JSON object with one property: <inline-h>"IsApproved":true<inline-h>.
9. Check the workflow status again via Workflow HTTP API:
10. The <inline-h>runtimeStatus<inline-h> should now be <inline-h>COMPLETED<inline-h> and the output of the workflow should be present:
Next steps
In this post, you’ve seen how to write Dapr workflows in .NET and use patterns such as chaining, fan-out/fan-in, monitor, and external system interaction. With these patterns, you can make just about anything with Dapr workflows. It’s very common to combine the patterns in your workflows to achieve the desired behavior. Take a look at the CheckoutService sample in the dapr-workflow-demos repository for a real-life example.
Do you have any questions or comments about this blog post or the code? Join the Dapr discord and post a message in the #workflow channel. Have you made something with Dapr, and created a blog post or video? Post a message in the #show-and-tell channel, we love to see what you’re doing with Dapr!
Resources
- Dapr Workflow Demos on GitHub: github.com/diagrid-labs/dapr-workflow-demos
- Dapr Workflow Pattern Docs: docs.dapr.io/developing-applications/building-blocks/workflow/workflow-patterns/
Diagrid Newsletter
Subscribe today for exclusive Dapr insights