Skip to content

Commit 19993ee

Browse files
committed
Add Mediator samples
1 parent 67bb402 commit 19993ee

File tree

13 files changed

+473
-26
lines changed

13 files changed

+473
-26
lines changed

Microsoft.DurableTask.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grpc", "src\Grpc\Grpc.cspro
6565
EndProject
6666
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "test\Benchmarks\Benchmarks.csproj", "{82C0CD7D-2764-421A-8256-7E2304D5A6E7}"
6767
EndProject
68+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Preview", "samples\Preview\Preview.csproj", "{EA7F706E-9738-4DDB-9089-F17F927E1247}"
69+
EndProject
6870
Global
6971
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7072
Debug|Any CPU = Debug|Any CPU
@@ -167,6 +169,10 @@ Global
167169
{82C0CD7D-2764-421A-8256-7E2304D5A6E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
168170
{82C0CD7D-2764-421A-8256-7E2304D5A6E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
169171
{82C0CD7D-2764-421A-8256-7E2304D5A6E7}.Release|Any CPU.Build.0 = Release|Any CPU
172+
{EA7F706E-9738-4DDB-9089-F17F927E1247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
173+
{EA7F706E-9738-4DDB-9089-F17F927E1247}.Debug|Any CPU.Build.0 = Debug|Any CPU
174+
{EA7F706E-9738-4DDB-9089-F17F927E1247}.Release|Any CPU.ActiveCfg = Release|Any CPU
175+
{EA7F706E-9738-4DDB-9089-F17F927E1247}.Release|Any CPU.Build.0 = Release|Any CPU
170176
EndGlobalSection
171177
GlobalSection(SolutionProperties) = preSolution
172178
HideSolutionNode = FALSE
@@ -199,6 +205,7 @@ Global
199205
{93E3B973-0FC4-4241-B7BB-064FB538FB50} = {5AD837BC-78F3-4543-8AA3-DF74D0DF94C0}
200206
{44AD321D-96D4-481E-BD41-D0B12A619833} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
201207
{82C0CD7D-2764-421A-8256-7E2304D5A6E7} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
208+
{EA7F706E-9738-4DDB-9089-F17F927E1247} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
202209
EndGlobalSection
203210
GlobalSection(ExtensibilityGlobals) = postSolution
204211
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using McMaster.Extensions.CommandLineUtils;
5+
using Microsoft.DurableTask;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Preview.MediatorPattern.ExistingTypes;
9+
10+
/**
11+
* This sample shows mediator-pattern orchestrations and activities using existing types as their inputs. In this mode,
12+
* the request object provides a distinct separate object as the input to the task. The below code has no real purpose
13+
* nor demonstrates good ways to organize orchestrations or activities. The purpose is to demonstrate how the static
14+
* 'CreateRequest' method way of using the mediator pattern.
15+
*
16+
* This is just one such way to leverage the mediator-pattern. Ultimately all the request object is all that is needed,
17+
* how it is created is flexible.
18+
*/
19+
20+
public class MediatorOrchestrator1 : TaskOrchestrator<MyInput> // Single generic means it has no output. Only input.
21+
{
22+
public static IOrchestrationRequest CreateRequest(string propA, string propB)
23+
=> OrchestrationRequest.Create(nameof(MediatorOrchestrator1), new MyInput(propA, propB));
24+
25+
public override async Task RunAsync(TaskOrchestrationContext context, MyInput input)
26+
{
27+
string output = await context.RunAsync(MediatorSubOrchestrator1.CreateRequest(input.PropA));
28+
await context.RunAsync(WriteConsoleActivity1.CreateRequest(output));
29+
30+
output = await context.RunAsync(ExpandActivity1.CreateRequest(input.PropB));
31+
await context.RunAsync(WriteConsoleActivity1.CreateRequest(output));
32+
}
33+
}
34+
35+
public class MediatorSubOrchestrator1 : TaskOrchestrator<string, string>
36+
{
37+
public static IOrchestrationRequest<string> CreateRequest(string input)
38+
=> OrchestrationRequest.Create<string>(nameof(MediatorSubOrchestrator1), input);
39+
40+
public override Task<string> RunAsync(TaskOrchestrationContext context, string input)
41+
{
42+
// Orchestrations create replay-safe loggers off the
43+
ILogger logger = context.CreateReplaySafeLogger<MediatorSubOrchestrator1>();
44+
logger.LogDebug("In MySubOrchestrator");
45+
return context.RunAsync(ExpandActivity1.CreateRequest($"{nameof(MediatorSubOrchestrator1)}: {input}"));
46+
}
47+
}
48+
49+
public class WriteConsoleActivity1 : TaskActivity<string> // Single generic means it has no output. Only input.
50+
{
51+
readonly IConsole console;
52+
53+
public WriteConsoleActivity1(IConsole console) // Dependency injection example.
54+
{
55+
this.console = console;
56+
}
57+
58+
public static IActivityRequest CreateRequest(string input)
59+
=> ActivityRequest.Create(nameof(WriteConsoleActivity1), input);
60+
61+
public override Task RunAsync(TaskActivityContext context, string input)
62+
{
63+
this.console.WriteLine(input);
64+
return Task.CompletedTask;
65+
}
66+
}
67+
68+
public class ExpandActivity1 : TaskActivity<string, string>
69+
{
70+
readonly ILogger logger;
71+
72+
public ExpandActivity1(ILogger<ExpandActivity1> logger) // Activities get logger from DI.
73+
{
74+
this.logger = logger;
75+
}
76+
77+
public static IActivityRequest<string> CreateRequest(string input)
78+
=> ActivityRequest.Create<string>(nameof(ExpandActivity1), input);
79+
80+
public override Task<string> RunAsync(TaskActivityContext context, string input)
81+
{
82+
this.logger.LogDebug("In ExpandActivity");
83+
return Task.FromResult($"Input received: {input}");
84+
}
85+
}
86+
87+
public record MyInput(string PropA, string PropB);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using McMaster.Extensions.CommandLineUtils;
5+
using Microsoft.DurableTask;
6+
using Preview.MediatorPattern.ExistingTypes;
7+
8+
namespace Preview.MediatorPattern;
9+
10+
[Command(Description = "Runs the first mediator sample")]
11+
public class Mediator1Command : SampleCommandBase
12+
{
13+
public static void Register(DurableTaskRegistry tasks)
14+
{
15+
tasks.AddActivity<ExpandActivity1>();
16+
tasks.AddActivity<WriteConsoleActivity1>();
17+
tasks.AddOrchestrator<MediatorOrchestrator1>();
18+
tasks.AddOrchestrator<MediatorSubOrchestrator1>();
19+
}
20+
21+
protected override IBaseOrchestrationRequest GetRequest()
22+
{
23+
return MediatorOrchestrator1.CreateRequest("PropInputA", "PropInputB");
24+
}
25+
}
26+
27+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using McMaster.Extensions.CommandLineUtils;
5+
using Microsoft.DurableTask;
6+
using Preview.MediatorPattern.NewTypes;
7+
8+
namespace Preview.MediatorPattern;
9+
10+
[Command(Description = "Runs the second mediator sample")]
11+
public class Mediator2Command : SampleCommandBase
12+
{
13+
public static void Register(DurableTaskRegistry tasks)
14+
{
15+
tasks.AddActivity<ExpandActivity2>();
16+
tasks.AddActivity<WriteConsoleActivity2>();
17+
tasks.AddOrchestrator<MediatorOrchestrator2>();
18+
tasks.AddOrchestrator<MediatorSubOrchestrator2>();
19+
}
20+
21+
protected override IBaseOrchestrationRequest GetRequest()
22+
{
23+
return new MediatorOrchestratorRequest("PropA", "PropB");
24+
}
25+
}
26+
27+
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using McMaster.Extensions.CommandLineUtils;
5+
using Microsoft.DurableTask;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Preview.MediatorPattern.NewTypes;
9+
10+
/**
11+
* This sample shows mediator-pattern orchestrations and activities using newly defined request types as their input. In
12+
* this mode, the request object is the input to the task itself. The below code has no real purpose nor demonstrates
13+
* good ways to organize orchestrations or activities. The purpose is to demonstrate how request objects can be defined
14+
* manually and provided directly to RunAsync method.
15+
*
16+
* This is just one such way to leverage the mediator-pattern. Ultimately all the request object is all that is needed,
17+
* how it is created is flexible.
18+
*/
19+
20+
public record MediatorOrchestratorRequest(string PropA, string PropB) : IOrchestrationRequest
21+
{
22+
public TaskName GetTaskName() => nameof(MediatorOrchestrator2);
23+
}
24+
25+
public class MediatorOrchestrator2 : TaskOrchestrator<MediatorOrchestratorRequest> // Single generic means it has no output. Only input.
26+
{
27+
public override async Task RunAsync(TaskOrchestrationContext context, MediatorOrchestratorRequest input)
28+
{
29+
string output = await context.RunAsync(new MediatorSubOrchestratorRequest(input.PropA));
30+
await context.RunAsync(new WriteConsoleActivityRequest(output));
31+
}
32+
}
33+
34+
public record MediatorSubOrchestratorRequest(string Value) : IOrchestrationRequest<string>
35+
{
36+
public TaskName GetTaskName() => nameof(MediatorSubOrchestrator2);
37+
}
38+
39+
public class MediatorSubOrchestrator2 : TaskOrchestrator<MediatorSubOrchestratorRequest, string>
40+
{
41+
public override Task<string> RunAsync(TaskOrchestrationContext context, MediatorSubOrchestratorRequest input)
42+
{
43+
// Orchestrations create replay-safe loggers off the
44+
ILogger logger = context.CreateReplaySafeLogger<MediatorSubOrchestrator2>();
45+
logger.LogDebug("In MySubOrchestrator");
46+
return context.RunAsync(new ExpandActivityRequest($"{nameof(MediatorSubOrchestrator2)}: {input.Value}"));
47+
}
48+
}
49+
50+
public record WriteConsoleActivityRequest(string Value) : IActivityRequest<string>
51+
{
52+
public TaskName GetTaskName() => nameof(WriteConsoleActivity2);
53+
}
54+
55+
public class WriteConsoleActivity2 : TaskActivity<WriteConsoleActivityRequest> // Single generic means it has no output. Only input.
56+
{
57+
readonly IConsole console;
58+
59+
public WriteConsoleActivity2(IConsole console) // Dependency injection example.
60+
{
61+
this.console = console;
62+
}
63+
64+
public override Task RunAsync(TaskActivityContext context, WriteConsoleActivityRequest input)
65+
{
66+
this.console.WriteLine(input.Value);
67+
return Task.CompletedTask;
68+
}
69+
}
70+
71+
public record ExpandActivityRequest(string Value) : IActivityRequest<string>
72+
{
73+
public TaskName GetTaskName() => nameof(ExpandActivity2);
74+
}
75+
76+
public class ExpandActivity2 : TaskActivity<ExpandActivityRequest, string>
77+
{
78+
readonly ILogger logger;
79+
80+
public ExpandActivity2(ILogger<ExpandActivity2> logger) // Activities get logger from DI.
81+
{
82+
this.logger = logger;
83+
}
84+
85+
public override Task<string> RunAsync(TaskActivityContext context, ExpandActivityRequest input)
86+
{
87+
this.logger.LogDebug("In ExpandActivity");
88+
return Task.FromResult($"Input received: {input.Value}");
89+
}
90+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Mediator Pattern
2+
3+
## Running this sample
4+
5+
First sample:
6+
``` cli
7+
dotnet run Preview.csproj -- mediator1
8+
```
9+
10+
Second sample:
11+
``` cli
12+
dotnet run Preview.csproj -- mediator2
13+
```
14+
15+
**NOTE**: see [dotnet run](https://learn.microsoft.com/dotnet/core/tools/dotnet-run). The `--` with a space following it is important.
16+
17+
## What is the mediator pattern?
18+
19+
> In software engineering, the mediator pattern defines an object that encapsulates how a set of objects interact. This pattern is considered to be a behavioral pattern due to the way it can alter the program's running behavior.
20+
>
21+
> -- [wikipedia](https://en.wikipedia.org/wiki/Mediator_pattern)
22+
23+
Specifically to Durable Task, this means using objects to assist with enqueueing of orchestrations, sub-orchestrations, and activities. These objects handle all of the following:
24+
25+
1. Defining which `TaskOrchestrator` or `TaskActivity` to run.
26+
2. Providing the input for the task to be ran.
27+
3. Defining the output type of the task.
28+
29+
The end result is the ability to invoke orchestrations and activities in a type-safe manner.
30+
31+
## What does it look like?
32+
33+
Instead of supplying the name, input, and return type of an orchestration or activity separately, instead a 'request' object is used to do all of these at once.
34+
35+
Example: enqueueing an activity.
36+
37+
Raw API:
38+
``` CSharp
39+
string result = await context.RunActivityAsync<string>(nameof(MyActivity), input);
40+
```
41+
42+
Explicit extension method [1]:
43+
``` csharp
44+
string result = await context.RunMyActivityAsync(input);
45+
```
46+
47+
Mediator
48+
``` csharp
49+
string result = await context.RunAsync(MyActivity.CreateRequest(input));
50+
51+
// OR - it is up to individual developers which style they prefer. Can also be mixed and matched as seen fit.
52+
53+
string result = await context.RunAsync(new MyActivityRequest(input));
54+
```
55+
56+
[1] - while the extension method is concise, having many extension methods off the same type can make intellisense a bit unwieldy.

samples/Preview/Preview.csproj

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net6.0</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.DurableTask.Sidecar" Version="0.3.0" />
11+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="$(SrcRoot)Client/Grpc/Client.Grpc.csproj" />
16+
<ProjectReference Include="$(SrcRoot)Worker/Grpc/Worker.Grpc.csproj" />
17+
</ItemGroup>
18+
19+
</Project>

0 commit comments

Comments
 (0)