Skip to content

Commit e945f9e

Browse files
committed
Add initial mediator pattern
1 parent abd41bd commit e945f9e

File tree

4 files changed

+274
-0
lines changed

4 files changed

+274
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace Microsoft.DurableTask;
5+
6+
/// <summary>
7+
/// Represents the base request to run a <see cref="ITaskOrchestrator" />.
8+
/// </summary>
9+
public interface IBaseOrchestrationRequest
10+
{
11+
/// <summary>
12+
/// Gets the <see cref="TaskName" /> representing the <see cref="ITaskOrchestrator" /> to run.
13+
/// </summary>
14+
/// <returns>A <see cref="TaskName" />.</returns>
15+
/// <remarks>
16+
/// This is a function instead of a property so it is excluded in serialization without needing to use a
17+
/// serialization library specific attribute to exclude it.
18+
/// </remarks>
19+
TaskName GetTaskName();
20+
}
21+
22+
/// <summary>
23+
/// Represents a request to run a <see cref="ITaskOrchestrator" /> which returns <typeparamref name="TResult" />.
24+
/// </summary>
25+
/// <typeparam name="TResult">The result of the orchestrator that is to be ran.</typeparam>
26+
public interface IOrchestrationRequest<out TResult> : IBaseOrchestrationRequest
27+
{
28+
}
29+
30+
/// <summary>
31+
/// Represents a request to run a <see cref="ITaskOrchestrator" /> which has no return.
32+
/// </summary>
33+
public interface IOrchestrationRequest : IOrchestrationRequest<Unit>
34+
{
35+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace Microsoft.DurableTask;
5+
6+
/// <summary>
7+
/// Base class for an orchestration.
8+
/// </summary>
9+
/// <typeparam name="TInput">The orchestration input type.</typeparam>
10+
/// <typeparam name="TOutput">The orchestration output type.</typeparam>
11+
public abstract class Orchestrator<TInput, TOutput> : ITaskOrchestrator
12+
where TInput : IOrchestrationRequest<TOutput>
13+
{
14+
/// <inheritdoc/>
15+
Type ITaskOrchestrator.InputType => typeof(TInput);
16+
17+
/// <inheritdoc/>
18+
Type ITaskOrchestrator.OutputType => typeof(TOutput);
19+
20+
/// <inheritdoc/>
21+
async Task<object?> ITaskOrchestrator.RunAsync(TaskOrchestrationContext context, object? input)
22+
{
23+
Check.NotNull(context, nameof(context));
24+
OrchestratorHelper.ValidateInput(input, out TInput typedInput);
25+
26+
return await this.RunAsync(context, typedInput);
27+
}
28+
29+
/// <summary>
30+
/// Override to implement task orchestrator logic.
31+
/// </summary>
32+
/// <param name="context">The task orchestrator's context.</param>
33+
/// <param name="input">The deserialized orchestration input.</param>
34+
/// <returns>The output of the orchestration as a task.</returns>
35+
public abstract Task<TOutput> RunAsync(TaskOrchestrationContext context, TInput input);
36+
}
37+
38+
/// <summary>
39+
/// Base class for an orchestration.
40+
/// </summary>
41+
/// <typeparam name="TInput">The orchestration input type.</typeparam>
42+
public abstract class Orchestrator<TInput> : ITaskOrchestrator
43+
where TInput : IOrchestrationRequest<Unit>
44+
{
45+
/// <inheritdoc/>
46+
Type ITaskOrchestrator.InputType => typeof(TInput);
47+
48+
/// <inheritdoc/>
49+
Type ITaskOrchestrator.OutputType => typeof(Unit);
50+
51+
/// <inheritdoc/>
52+
async Task<object?> ITaskOrchestrator.RunAsync(TaskOrchestrationContext context, object? input)
53+
{
54+
Check.NotNull(context, nameof(context));
55+
OrchestratorHelper.ValidateInput(input, out TInput typedInput);
56+
57+
await this.RunAsync(context, typedInput);
58+
return Unit.Value;
59+
}
60+
61+
/// <summary>
62+
/// Override to implement task orchestrator logic.
63+
/// </summary>
64+
/// <param name="context">The task orchestrator's context.</param>
65+
/// <param name="input">The deserialized orchestration input.</param>
66+
/// <returns>The output of the orchestration as a task.</returns>
67+
public abstract Task RunAsync(TaskOrchestrationContext context, TInput input);
68+
}
69+
70+
/// <summary>
71+
/// Orchestration implementation helpers.
72+
/// </summary>
73+
static class OrchestratorHelper
74+
{
75+
/// <summary>
76+
/// Due to nullable reference types being static analysis only, we need to do our best efforts for validating the
77+
/// input type, but also give control of nullability to the implementation. It is not ideal, but we do not want to
78+
/// force 'TInput?' on the RunAsync implementation.
79+
/// </summary>
80+
/// <typeparam name="TInput">The input type of the orchestration.</typeparam>
81+
/// <param name="input">The input object.</param>
82+
/// <param name="typedInput">The input converted to the desired type.</param>
83+
public static void ValidateInput<TInput>(object? input, out TInput typedInput)
84+
{
85+
if (input is TInput typed)
86+
{
87+
// Quick pattern check.
88+
typedInput = typed;
89+
return;
90+
}
91+
else if (input is not null && typeof(TInput) != input.GetType())
92+
{
93+
throw new ArgumentException($"Input type '{input?.GetType()}' does not match expected type '{typeof(TInput)}'.");
94+
}
95+
96+
// Input is null and did not match a nullable value type. We do not have enough information to tell if it is
97+
// valid or not. We will have to defer this decision to the implementation. Additionally, we will coerce a null
98+
// input to a default value type here. This is to keep the two RunAsync(context, default) overloads to have
99+
// identical behavior.
100+
typedInput = default!;
101+
return;
102+
}
103+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace Microsoft.DurableTask;
5+
6+
/// <summary>
7+
/// Extensions for <see cref="TaskOrchestrationContext" /> for strongly-typed requests.
8+
/// </summary>
9+
public static class TaskOrchestrationContextRequestExtensions
10+
{
11+
/// <summary>
12+
/// Runs the sub-orchestration described by <paramref name="request" /> with <paramref name="request" /> as the
13+
/// input to the orchestration itself.
14+
/// </summary>
15+
/// <typeparam name="TResult">The result type of the orchestration.</typeparam>
16+
/// <param name="context">The context used to run the orchestration.</param>
17+
/// <param name="request">The orchestration request.</param>
18+
/// <param name="options">The task options.</param>
19+
/// <returns>The result of the orchestration.</returns>
20+
public static Task<TResult> RunAsync<TResult>(
21+
this TaskOrchestrationContext context, IOrchestrationRequest<TResult> request, TaskOptions? options = null)
22+
{
23+
Check.NotNull(context);
24+
Check.NotNull(request);
25+
TaskName name = request.GetTaskName();
26+
return context.CallSubOrchestratorAsync<TResult>(name, request, options);
27+
}
28+
29+
/// <summary>
30+
/// Runs the sub-orchestration described by <paramref name="request" /> with <paramref name="request" /> as the
31+
/// input to the orchestration itself.
32+
/// </summary>
33+
/// <param name="context">The context used to run the orchestration.</param>
34+
/// <param name="request">The orchestration request.</param>
35+
/// <param name="options">The task options.</param>
36+
/// <returns>A task that completes when the orchestration completes.</returns>
37+
public static Task RunAsync(
38+
this TaskOrchestrationContext context, IOrchestrationRequest<Unit> request, TaskOptions? options = null)
39+
{
40+
Check.NotNull(context);
41+
Check.NotNull(request);
42+
TaskName name = request.GetTaskName();
43+
return context.CallSubOrchestratorAsync(name, request, options);
44+
}
45+
}

src/Abstractions/Mediator/Unit.cs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace Microsoft.DurableTask;
5+
6+
/// <summary>
7+
/// Represents a <see cref="Void" /> result.
8+
/// </summary>
9+
public readonly struct Unit : IEquatable<Unit>, IComparable<Unit>
10+
{
11+
#pragma warning disable CA1801 // unused parameters
12+
#pragma warning disable IDE0060 // unused parameters
13+
14+
static readonly Unit RefValue;
15+
16+
/// <summary>
17+
/// Gets the default value for <see cref="Unit" />.
18+
/// </summary>
19+
public static ref readonly Unit Value => ref RefValue;
20+
21+
/// <summary>
22+
/// Gets the task result for a <see cref="Unit" />.
23+
/// </summary>
24+
public static Task<Unit> Task { get; } = System.Threading.Tasks.Task.FromResult(RefValue);
25+
26+
/// <summary>
27+
/// Compares two empties for equality. Always true.
28+
/// </summary>
29+
/// <param name="left">The left empty.</param>
30+
/// <param name="right">The right empty.</param>
31+
/// <returns>Always true.</returns>
32+
public static bool operator ==(Unit left, Unit right) => true;
33+
34+
/// <summary>
35+
/// Compares two empties for inequality. Always false.
36+
/// </summary>
37+
/// <param name="left">The left empty.</param>
38+
/// <param name="right">The right empty.</param>
39+
/// <returns>Always false.</returns>
40+
public static bool operator !=(Unit left, Unit right) => !true;
41+
42+
/// <summary>
43+
/// Compares two empties. Always false.
44+
/// </summary>
45+
/// <param name="left">The left empty.</param>
46+
/// <param name="right">The right empty.</param>
47+
/// <returns>Always false.</returns>
48+
public static bool operator <(Unit left, Unit right) => false;
49+
50+
/// <summary>
51+
/// Compares two empties. Always true.
52+
/// </summary>
53+
/// <param name="left">The left empty.</param>
54+
/// <param name="right">The right empty.</param>
55+
/// <returns>Always true.</returns>
56+
public static bool operator <=(Unit left, Unit right) => true;
57+
58+
/// <summary>
59+
/// Compares two empties. Always false.
60+
/// </summary>
61+
/// <param name="left">The left empty.</param>
62+
/// <param name="right">The right empty.</param>
63+
/// <returns>Always false.</returns>
64+
public static bool operator >(Unit left, Unit right) => false;
65+
66+
/// <summary>
67+
/// Compares two empties. Always true.
68+
/// </summary>
69+
/// <param name="left">The left empty.</param>
70+
/// <param name="right">The right empty.</param>
71+
/// <returns>Always true.</returns>
72+
public static bool operator >=(Unit left, Unit right) => true;
73+
74+
/// <inheritdoc />
75+
public int CompareTo(Unit other) => 0;
76+
77+
/// <inheritdoc />
78+
public bool Equals(Unit other) => true;
79+
80+
/// <inheritdoc />
81+
public override bool Equals(object obj) => obj is Unit;
82+
83+
/// <inheritdoc />
84+
public override int GetHashCode() => 0;
85+
86+
/// <inheritdoc />
87+
public override string ToString() => "{}";
88+
89+
#pragma warning restore CA1801 // unused parameters
90+
#pragma warning restore IDE0060 // unused parameters
91+
}

0 commit comments

Comments
 (0)