Skip to content

Commit 77855d6

Browse files
Merge pull request #1182 from marcduiker/workflow-tutorials
Workflow tutorials C#
2 parents f947399 + 4918bda commit 77855d6

File tree

134 files changed

+3187
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+3187
-3
lines changed

.github/workflows/validate_tutorials.yaml

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,64 @@ jobs:
184184
# pushd tutorials/observability
185185
# make validate
186186
# popd
187-
- name: Linkcheck README.md
188-
run: |
189-
make validate
187+
# Validation for workflows is intentionally commented out.
188+
# Mechanical markdown is not able to invoke the workflow services.
189+
# Once this feature has been added to Mechanical Markdown the
190+
# workflow validations can be enabled.
191+
#- name: Validate workflow/csharp/child-workflows
192+
# if: matrix.os == 'ubuntu-latest'
193+
# run: |
194+
# pushd tutorials/workflow/csharp/child-workflows
195+
# make validate
196+
# popd
197+
#- name: Validate workflow/csharp/combined-patterns
198+
# if: matrix.os == 'ubuntu-latest'
199+
# run: |
200+
# pushd tutorials/workflow/csharp/combined-patterns
201+
# make validate
202+
# popd
203+
#- name: Validate workflow/csharp/external-system-interaction
204+
# if: matrix.os == 'ubuntu-latest'
205+
# run: |
206+
# pushd tutorials/workflow/csharp/external-system-interaction
207+
# make validate
208+
# popd
209+
#- name: Validate workflow/csharp/fan-out-fan-in
210+
# if: matrix.os == 'ubuntu-latest'
211+
# run: |
212+
# pushd tutorials/workflow/csharp/fan-out-fan-in
213+
# make validate
214+
# popd
215+
#- name: Validate workflow/csharp/fundamentals
216+
# if: matrix.os == 'ubuntu-latest'
217+
# run: |
218+
# pushd tutorials/workflow/csharp/fundamentals
219+
# make validate
220+
# popd
221+
#- name: Validate workflow/csharp/monitor-pattern
222+
# if: matrix.os == 'ubuntu-latest'
223+
# run: |
224+
# pushd tutorials/workflow/csharp/monitor-pattern
225+
# make validate
226+
# popd
227+
#- name: Validate workflow/csharp/resiliency-and-compensation
228+
# if: matrix.os == 'ubuntu-latest'
229+
# run: |
230+
# pushd tutorials/workflow/csharp/resiliency-and-compensation
231+
# make validate
232+
# popd
233+
#- name: Validate workflow/csharp/task-chaining
234+
# if: matrix.os == 'ubuntu-latest'
235+
# run: |
236+
# pushd tutorials/workflow/csharp/task-chaining
237+
# make validate
238+
# popd
239+
#- name: Validate workflow/csharp/workflow-management
240+
# if: matrix.os == 'ubuntu-latest'
241+
# run: |
242+
# pushd tutorials/workflow/csharp/workflow-management
243+
# make validate
244+
# popd
245+
#- name: Linkcheck README.md
246+
# run: |
247+
# make validate
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Using the Dapr Workflow API with C#
2+
3+
This folder contains tutorials of using the Dapr Workflow API with C#. All examples can be run locally on your machine.
4+
5+
Before you start, it's recommended to read though the Dapr docs to get familiar with the many [Workflow features, concepts, and patterns](https://docs.dapr.io/developing-applications/building-blocks/workflow/).
6+
7+
## Prerequisites
8+
9+
- [Docker Desktop](https://www.docker.com/products/docker-desktop/)
10+
- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) & [Initialization](https://docs.dapr.io/getting-started/install-dapr-selfhost/)
11+
- [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0)
12+
- Optional: An IDE such as [VSCode](https://code.visualstudio.com/download) with a [REST client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client).
13+
14+
## Tutorials
15+
16+
- [Workflow Basics](./fundamentals/README.md)
17+
- [Task Chaining](./task-chaining/README.md)
18+
- [Fan-out/Fan-in](./fan-out-fan-in/README.md)
19+
- [Monitor](./monitor-pattern/README.md)
20+
- [External Events](./external-system-interaction/README.md)
21+
- [Child Workflows](./child-workflows/README.md)
22+
- [Resiliency & Compensation](./resiliency-and-compensation/README.md)
23+
- [Combined Patterns](./combined-patterns/README.md)
24+
- [WorkflowManagement](./workflow-management/README.md)
25+
- [Challenges & Tips](./challenges-tips/README.md)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using Dapr.Workflow;
3+
4+
namespace WorkflowApp;
5+
6+
internal sealed class NonDeterministicWorkflow : Workflow<string, string>
7+
{
8+
public override async Task<string> RunAsync(WorkflowContext context, string orderItem)
9+
{
10+
// Do not use non-deterministic operations in a workflow!
11+
// These operations will create a new value every time the
12+
// workflow is replayed.
13+
var orderId = Guid.NewGuid().ToString();
14+
var orderDate = DateTime.UtcNow;
15+
16+
var idResult = await context.CallActivityAsync<string>(
17+
"SubmitId",
18+
orderId);
19+
20+
await context.CallActivityAsync<string>(
21+
"SubmitDate",
22+
orderDate);
23+
24+
return idResult;
25+
}
26+
}
27+
28+
internal sealed class DeterministicWorkflow : Workflow<string, string>
29+
{
30+
public override async Task<string> RunAsync(WorkflowContext context, string orderItem)
31+
{
32+
// Do use these deterministic methods and properties on the WorkflowContext instead.
33+
// These operations create the same value when the workflow is replayed.
34+
var orderId = context.NewGuid().ToString();
35+
var orderDate = context.CurrentUtcDateTime;
36+
37+
var idResult = await context.CallActivityAsync<string>(
38+
"SubmitId",
39+
orderId);
40+
41+
await context.CallActivityAsync<string>(
42+
"SubmitDate",
43+
orderDate);
44+
45+
return idResult;
46+
}
47+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Dapr.Client;
2+
using Dapr.Workflow;
3+
4+
namespace WorkflowApp;
5+
6+
internal sealed class IdempotentActivity : WorkflowActivity<string, string>
7+
{
8+
public override async Task<InventoryResult> RunAsync(WorkflowActivityContext context, OrderItem orderItem)
9+
{
10+
// Beware of non-idempotent operations in an activity.
11+
// Dapr Workflow guarantees at-least-once execution of activities, so activities might be executed more than once
12+
// in case an activity is not ran to completion successfully.
13+
// For instance, can you insert a record to a database twice without side effects?
14+
// var insertSql = $"INSERT INTO Orders (Id, Description, UnitPrice, Quantity) VALUES ('{orderItem.Id}', '{orderItem.Description}', {orderItem.UnitPrice}, {orderItem.Quantity})";
15+
// It's best to perform a check if an record already exists before inserting it.
16+
}
17+
}
18+
19+
internal sealed record OrderItem(string Id, string Description, double UnitPrice, int Quantity);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Dapr.Workflow;
2+
3+
namespace WorkflowApp;
4+
5+
internal sealed class LargePayloadSizeWorkflow : Workflow<string, LargeDocument>
6+
{
7+
public override async Task<LargeDocument> RunAsync(WorkflowContext context, string id)
8+
{
9+
// Do not pass large payloads between activities.
10+
// They are stored in the Dapr state store twice, one as output argument
11+
// for GetDocument, and once as input argument for UpdateDocument.
12+
var document = await context.CallActivityAsync<LargeDocument>(
13+
"GetDocument",
14+
id);
15+
16+
var updatedDocument = await context.CallActivityAsync<LargeDocument>(
17+
"UpdateDocument",
18+
document);
19+
20+
// More activities to process the updated document...
21+
22+
return updatedDocument;
23+
}
24+
}
25+
26+
27+
public class SmallPayloadSizeWorkflow : Workflow<string, string>
28+
{
29+
public override async Task<string> RunAsync(WorkflowContext context, string id)
30+
{
31+
// Do pass small payloads between activities, preferably IDs only, or objects that are quick to (de)serialize in large volumes.
32+
// Combine multiple actions, such as document retrieval and update, into a single activity.
33+
var documentId = await context.CallActivityAsync<string>(
34+
"GetAndUpdateDocument",
35+
id);
36+
37+
// More activities to process the updated document...
38+
39+
return documentId;
40+
}
41+
}
42+
43+
internal sealed record LargeDocument(string Id, object Data);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Workflow Challenges & Tips
2+
3+
Workflow systems are very powerful tools but also have their challenges & limitations as described in the [Dapr docs](https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-features-concepts/#limitations).
4+
5+
This section provides some tips with code snippets to understand the limitations and get the most out of the Dapr Workflow API. Read through the following examples to learn best practices to develop Dapr workflows.
6+
7+
- [Deterministic workflows](DeterministicWorkflow.cs)
8+
- [Idempotent activities](IdempotentActivity.cs)
9+
- [Versioning workflows](VersioningWorkflow.cs)
10+
- [Workflow & activity payload size](PayloadSizeWorkflow.cs)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Dapr.Workflow;
2+
3+
namespace WorkflowApp;
4+
5+
/// <summary>
6+
/// This is the initial version of the workflow.
7+
/// Note that the input argument for both activities is the orderItem (string).
8+
/// </summary>
9+
internal sealed class VersioningWorkflow1 : Workflow<string, int>
10+
{
11+
public override async Task<int> RunAsync(WorkflowContext context, string orderItem)
12+
{
13+
var resultA = await context.CallActivityAsync<int>(
14+
"ActivityA",
15+
orderItem);
16+
17+
var resultB = await context.CallActivityAsync<int>(
18+
"ActivityB",
19+
orderItem);
20+
21+
return resultA + resultB;
22+
}
23+
}
24+
25+
/// <summary>
26+
/// This is the updated version of the workflow.
27+
/// The input for ActivityB has changed from orderItem (string) to resultA (int).
28+
/// If there are in-flight workflow instances that were started with the previous version
29+
/// of this workflow, these will fail when the new version of the workflow is deployed
30+
/// and the workflow name remains the same, since the runtime parameters do not match with the persisted state.
31+
/// It is recommended to version workflows by creating a new workflow class with a new name:
32+
/// {workflowname}1 -> {workflowname}2
33+
/// Try to avoid making breaking changes in perpetual workflows (that use the `ContinueAsNew` method)
34+
/// since these are difficult to replace with a new version.
35+
/// </summary>
36+
internal sealed class VersioningWorkflow2 : Workflow<string, int>
37+
{
38+
public override async Task<int> RunAsync(WorkflowContext context, string orderItem)
39+
{
40+
var resultA = await context.CallActivityAsync<int>(
41+
"ActivityA",
42+
orderItem);
43+
44+
var resultB = await context.CallActivityAsync<int>(
45+
"ActivityB",
46+
resultA);
47+
48+
return resultA + resultB;
49+
}
50+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Dapr.Workflow;
2+
3+
namespace ChildWorkflows.Activities;
4+
5+
internal sealed class Activity1 : WorkflowActivity<string, string>
6+
{
7+
public override Task<string> RunAsync(WorkflowActivityContext context, string input)
8+
{
9+
Console.WriteLine($"{nameof(Activity1)}: Received input: {input}.");
10+
return Task.FromResult($"{input} is processed" );
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Dapr.Workflow;
2+
3+
namespace ChildWorkflows.Activities;
4+
5+
internal sealed class Activity2 : WorkflowActivity<string, string>
6+
{
7+
public override Task<string> RunAsync(WorkflowActivityContext context, string input)
8+
{
9+
Console.WriteLine($"{nameof(Activity2)}: Received input: {input}.");
10+
return Task.FromResult($"{input} as a child workflow." );
11+
}
12+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using ChildWorkflows.Activities;
6+
using Dapr.Workflow;
7+
8+
namespace ChildWorkflows;
9+
internal sealed class ChildWorkflow : Workflow<string, string>
10+
{
11+
public override async Task<string> RunAsync(WorkflowContext context, string input)
12+
{
13+
var result1 = await context.CallActivityAsync<string>(
14+
nameof(Activity1),
15+
input);
16+
var childWorkflowResult = await context.CallActivityAsync<string>(
17+
nameof(Activity2),
18+
result1);
19+
20+
return childWorkflowResult;
21+
}
22+
}

0 commit comments

Comments
 (0)