Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Mediator to Brighter #3370

Draft
wants to merge 49 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
f52c18d
chore: save work in progress
iancooper Oct 28, 2024
36890f5
feat: add ADR for a mediator and assembly
iancooper Oct 30, 2024
d92cb84
feat: required workflow classes to write setup of rist mediator test
iancooper Oct 30, 2024
556bb7a
chore: safety check whilst releasing V9 and V10; does not build
iancooper Oct 31, 2024
3ed0e7e
chore: safety checkin
iancooper Nov 1, 2024
e0c9c85
feat: add fire and forget action
iancooper Nov 1, 2024
f8609b8
feat: add requestreply outline
iancooper Nov 1, 2024
255d086
fix: folder name causing issues on MacOS which believes it is an appl…
iancooper Nov 2, 2024
a321e30
feat: modifications to step and workflow responsibility
iancooper Nov 4, 2024
7a879e6
feat: add workflow data, over just using the bag
iancooper Nov 5, 2024
b8eded1
feat: need correlation id on event and command to support workflow
iancooper Nov 6, 2024
5298d38
chore: check in to allow merging of master
iancooper Nov 6, 2024
ab16834
chore: merge branch 'master' into mediator
iancooper Nov 6, 2024
92e344d
feat: move completed workflows to the done state
iancooper Nov 9, 2024
6840f04
feat: add an ADR for adding the specification pattern
iancooper Nov 9, 2024
9e63244
feat: add the specification pattern
iancooper Nov 9, 2024
f7cf152
fix: typo in filename
iancooper Nov 9, 2024
b09c3a6
chore: safety dance
iancooper Nov 9, 2024
5313018
chore: safety dance
iancooper Nov 9, 2024
c41547a
feat: add a choice workflow action
iancooper Nov 10, 2024
c42b6b2
fix: shared fixture problems
iancooper Nov 10, 2024
40bf2b4
feat: add first version of robust flow
iancooper Nov 10, 2024
2b11f6a
chore: ~Merge branch 'master' into mediator
iancooper Nov 10, 2024
56f577e
fix: remove IAmTheWorkflowData as unnecessary abstraction.
iancooper Nov 11, 2024
3cb3c4b
fix: make choice about choosing the next step from the workflow data
iancooper Nov 11, 2024
bd6d2e1
fix: tests not checking all paths
iancooper Nov 11, 2024
12a7ecd
fix: add workflow patterns to ADR
iancooper Nov 12, 2024
f24fac2
feat: move to workflow patterns style, step and task; some behaviours…
iancooper Nov 13, 2024
991e4f2
feat: first pass at Parallel; requires Scheduler-Runner split to Medi…
iancooper Nov 17, 2024
1373583
chore: safety check-in during scheduler/runner split work
iancooper Nov 18, 2024
6067bd4
fix: refactor relationship between job and step to be more explicit. …
iancooper Nov 19, 2024
8b4e5e4
fix: step advancement manages job state
iancooper Nov 19, 2024
e5a6639
fix: add cancellation token interrupt of runner to all tests
iancooper Nov 19, 2024
acef39d
chore: safety check in; fixing failing tests
iancooper Nov 19, 2024
aa8ca95
fix: get the steps to save state, when they modify the job, not the r…
iancooper Nov 20, 2024
1cea4ea
fix: add fault version of robust request-reply
iancooper Nov 20, 2024
08b2e18
fix: add multi-threading support to Job
iancooper Nov 20, 2024
67f3376
Update ASB Samples to use the Emulator (#3391)
preardon Nov 21, 2024
e118650
feat: adding a Wait step.
iancooper Nov 23, 2024
ca229f0
fix: don't try to await a thread; ensure we leave time for scheduler …
iancooper Nov 23, 2024
059fff0
chore: merge from master
iancooper Nov 25, 2024
a7019a2
chore: merge with master
iancooper Dec 2, 2024
f35d999
chore: safety check in; failing test on parallel split still
iancooper Dec 2, 2024
1ef0839
fix: parallel split was not terminating
iancooper Dec 4, 2024
392dd4d
fix: we should pass data to the callbacks; was capturing variable in …
iancooper Dec 4, 2024
5dd7cf2
fix: issue with dequeuejobasync hard crashing
iancooper Dec 5, 2024
f08fa7a
chore: merge master in
iancooper Dec 29, 2024
f13aa59
feat: move the implementation to FBP
iancooper Jan 17, 2025
95e43ef
Merge remote-tracking branch 'origin/mediator' into mediator
iancooper Jan 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Brighter.sln
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Salutation_Sweeper", "sampl
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.Locking.MsSql", "src\Paramore.Brighter.Locking.MsSql\Paramore.Brighter.Locking.MsSql.csproj", "{758EE237-C722-4A0A-908C-2D08C1E59025}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.MediatorWorkflow", "src\Paramore.Brighter.MediatorWorkflow\Paramore.Brighter.MediatorWorkflow.csproj", "{F00B137A-C187-4C33-A37B-22AD40B71600}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1765,6 +1767,18 @@ Global
{758EE237-C722-4A0A-908C-2D08C1E59025}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{758EE237-C722-4A0A-908C-2D08C1E59025}.Release|x86.ActiveCfg = Release|Any CPU
{758EE237-C722-4A0A-908C-2D08C1E59025}.Release|x86.Build.0 = Release|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Debug|x86.ActiveCfg = Debug|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Debug|x86.Build.0 = Debug|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Release|Any CPU.Build.0 = Release|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Release|x86.ActiveCfg = Release|Any CPU
{F00B137A-C187-4C33-A37B-22AD40B71600}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0020-reduce-esb-complexity.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 20. Reduce External Service Bus Complexity

Date: 2019-08-01
Date: 2024-08-01

## Status

Expand Down
57 changes: 57 additions & 0 deletions docs/adr/0022-add-a-mediator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# 22. Add a Mediator to Brighter

Date: 2024-10-22

## Status

Proposed

## Context
We have two approaches to a workflow: orchestration and choreography. In choreography the workflow emerges from the interaction of the participants. In orchestration, one participant executes the workflow, calling other participants as needed. Whilst choreography has low-coupling, it also has low-cohesion. At scale this can lead to the Pinball anti-pattern, where it is difficult to maintain the workflow.

The [Mediator](https://www.oodesign.com/mediator-pattern) pattern provides an orchestrator that manages a workflow that involves multiple objects. In its simplest form, instead of talking to each other, objects talk to the mediator, which then calls other objects as required to execute the workflow.

Brighter provides `IHandleRequests<>` to provide a handler for an individual request, either a command or an event. It is possible to have an emergent workflow, within Brighter, through the choreography of these handlers. However, Brighter provides no model for an orchestrator that manages a workflow that involves multiple handlers. In particular, Brighter does not support a class that can listen to multiple requests and then call other handlers as required to execute the workflow.

In principle, nothing stops an end user from implementing a `Mediator` class that listens to multiple requests and then calls other handlers as required to execute the workflow. So orchestration has always been viable, but left as an exercise to the user. However, competing OSS projects provide popular workflow functionality, suggesting there is demand for an off-the-shelf solution.

Other dotnet messaging platforms erroneously conflate the Saga and Mediator patterns. A Saga is a long-running transaction that spans multiple services. A Mediator is an orchestrator that manages a workflow that involves multiple objects. One aspect of those implementations is typically the ability to store workflow state.

A particular reference for the requirements for this work is [AWS step functions](https://states-language.net/spec.html). AWS Step functions provide a state machine that mediates calls to AWS Lambda functions. When thinking about Brighter's `IHandleRequests` it is attractive to compare them to Lambda functions in the Step functions model :

1. The AWS Step funcions state machine does not hold the business logic, that is located in the functions called; the Step function handles calling the Lambda functions and state transitions (as well as error paths)
2. We want to use the Mediator to orchestrate both internal bus and external bus hosted workflows. Step functions provide a useful model of requirements for the latter.

This approach is intended to enable flexible, event-driven workflows that can handle various business processes and requirements, including asynchronous event handling and conditional branching.

We are also influenced by the [Arazzo Specification](https://github.com/OAI/Arazzo-Specification/blob/main/versions/1.0.0.md) for defining workflows from AsyncAPI.


## Decision

We will add a `Mediator` class to Brighter that will:

1. Manages and tracks a WorkflowState object representing the current step in the workflow.
2. Supports multiple process states, including:
• StartState: Initiates the workflow.
• FireAndForgetProcessState: Dispatches a `Command` and immediately advances to the next state.
• RequestReactionProcessState: Dispatches a `Command` and waits for an event response before advancing.
• ChoiceProcessState: Evaluates conditions using the `Specification` Pattern and chooses the next `Command` to Dispatch based on the evaluation.
• WaitState: Suspends execution for a specified TimeSpan before advancing.
3. Uses a CommandProcessor for routing commands and events to appropriate handlers.
4. Can be passed events, and uses the correlation IDs to match events to specific workflow instances and advance the workflow accordingly.

The Specification Pattern in ChoiceProcessState allows flexible conditional logic by combining specifications with And and Or conditions, enabling complex branching decisions within the workflow.

We assume that the initial V10 of Brighter will contain a minimum viable product version of the `Mediator`. Additional functionality, such as process states, UIs for workflows will be a feature of later releases.

## Consequences

Positive Consequences

1. Simplicity: Providing orchestration for a workflow, which is easier to understand
2. Modularity: It is possible to extend the `Mediator' relativey easy by adding new process states.

Negative Consequences

1. Increased Brighter scope: Previously we had assumed that developers would use an off-the-shelf workflow solution like [Stateless](https://github.com/nblumhardt/stateless) or [Workflow Core]. The decision to provide our own workflow, to orchestrate via CommandProcessor means that we increase our scope to include the complexity of workflow management.
46 changes: 46 additions & 0 deletions src/Paramore.Brighter.MediatorWorkflow/IStateStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#region Licence
/* The MIT License (MIT)
Copyright © 2024 Ian Cooper <ian_hammond_cooper@yahoo.co.uk>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. */

#endregion

using System;

namespace Paramore.Brighter.MediatorWorkflow;

/// <summary>
/// Used to store the state of a workflow
/// </summary>
public interface IStateStore
{
/// <summary>
/// Saves the workflow state
/// </summary>
/// <param name="state">The workflow state</param>
void SaveState(Workflow state);

/// <summary>
/// Retrieves a workflow via its Id
/// </summary>
/// <param name="id">The id of the workflow</param>
/// <returns>if found, the workflow, otherwise null</returns>
Workflow? GetState(Guid id);
}
43 changes: 43 additions & 0 deletions src/Paramore.Brighter.MediatorWorkflow/InMemoryStateStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#region Licence
/* The MIT License (MIT)
Copyright © 2024 Ian Cooper <ian_hammond_cooper@yahoo.co.uk>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. */

#endregion

using System;
using System.Collections.Generic;

namespace Paramore.Brighter.MediatorWorkflow;

public class InMemoryStateStore : IStateStore
{
private readonly Dictionary<Guid, Workflow> _states = new();

public void SaveState(Workflow state)
{
_states[state.Id] = state;
}

public Workflow? GetState(Guid id)
{
return _states.TryGetValue(id, out var state) ? state : null;
}
}
71 changes: 71 additions & 0 deletions src/Paramore.Brighter.MediatorWorkflow/Mediator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#region Licence
/* The MIT License (MIT)
Copyright © 2024 Ian Cooper <ian_hammond_cooper@yahoo.co.uk>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. */

#endregion

using System;
using System.Collections.Generic;

namespace Paramore.Brighter.MediatorWorkflow;

/// <summary>
/// The mediator orchestrates a workflow, executing each step in the sequence.
/// </summary>
/// <param name="steps"></param>
/// <param name="commandProcessor"></param>
/// <param name="stateStore"></param>
public class Mediator(IAmACommandProcessor commandProcessor)
{
/// <summary>
///
/// Runs the workflow by executing each step in the sequence.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when the workflow has not been initialized.</exception>
public void RunWorkFlow(Step? firstStep)
{
var step = firstStep;
while (step is not null)
{
step.Action.Handle(step.Flow, commandProcessor);
step.OnCompletion();
step = step.Next;
}
}

/// <summary>
/// Receives an event and processes it if there is a pending response for the event type.
/// </summary>
/// <param name="event">The event to process.</param>
/// <exception cref="InvalidOperationException">Thrown when the workflow has not been initialized.</exception>
public void ReceiveWorklowEvent(Event @event)
{
// var state = stateStore.GetState(@event.CorrelationId);
//
// var eventType = @event.GetType();
//
// if (!state.PendingResponses.TryGetValue(eventType, out Action<Event, Workflow> replyFactory))
// return;
//
// replyFactory(@event, state);
// state.PendingResponses.Remove(eventType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>The Command Dispatcher pattern is an addition to the Command design pattern that decouples the dispatcher for a service from its execution. A Command Dispatcher component maps commands to handlers. A Command Processor pattern provides a framework for handling orthogonal concerns such as logging, timeouts, or circuit breakers</Description>
<Authors>Ian Cooper</Authors>
<TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
<PackageTags>Command;Event;Command Dispatcher;Command Processor;Request;Service;Task Queue;Work Queue;Retry;Circuit Breaker;Availability</PackageTags>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Text.Json" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Paramore.Brighter\Paramore.Brighter.csproj" />
</ItemGroup>

</Project>
67 changes: 67 additions & 0 deletions src/Paramore.Brighter.MediatorWorkflow/Workflow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#region Licence
/* The MIT License (MIT)
Copyright © 2024 Ian Cooper <ian_hammond_cooper@yahoo.co.uk>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. */

#endregion

using System;
using System.Collections.Generic;

namespace Paramore.Brighter.MediatorWorkflow;

public enum WorkflowState
{
Ready,
Waiting,
Done
}

/// <summary>
/// Workflow represents the current state of the workflow and tracks if it’s awaiting a response.
/// </summary>
public class Workflow
{
/// <summary>
/// Used to store data that is passed between steps in the workflow
/// </summary>
public Dictionary<string, object> Bag { get; set; } = new();

/// <summary>
/// The id of the workflow, used to save-retrieve it from storage
/// </summary>
public Guid Id { get; private set; } = Guid.NewGuid();

/// <summary>
/// If we are awaiting a response, we store the type of the response and the action to take when it arrives
/// </summary>
public Dictionary<Type, Action<Event, Workflow>> PendingResponses { get; private set; } = new();

/// <summary>
/// Is the workflow currently awaiting an event response
/// </summary>
public WorkflowState State { get; set; } = WorkflowState.Ready;

/// <summary>
/// Constructs a new Workflow
/// </summary>
public Workflow() { }
}

36 changes: 36 additions & 0 deletions src/Paramore.Brighter.MediatorWorkflow/Workflows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;

namespace Paramore.Brighter.MediatorWorkflow;

/// <summary>
/// A step in the worfklow. Steps form a singly linked list.
/// </summary>
/// <param name="Name">The name of the step</param>
/// <param name="Action">The type of action we take with the step</param>
/// <param name="Flow">The workflow that we belong to</param>
/// <param name="Next">What is the next step in sequence</param>
public record Step(string Name, IWorkflowAction Action, Action OnCompletion, Workflow Flow, Step? Next);

public interface IWorkflowAction
{
void Handle(Workflow state, IAmACommandProcessor commandProcessor);
}

public class FireAndForgetAction<TRequest>(Func<TRequest> requestFactory) : IWorkflowAction where TRequest : class, IRequest
{
public void Handle(Workflow state, IAmACommandProcessor commandProcessor)
{
commandProcessor.Send(requestFactory());
}
}

public class RequestAndReplyAction<TRequest, TReply>(Func<TRequest> requestFactory, Action<Event> replyFactory)
: IWorkflowAction where TRequest : class, IRequest where TReply : class, IRequest
{
public void Handle(Workflow state, IAmACommandProcessor commandProcessor)
{
commandProcessor.Send(requestFactory());

state.PendingResponses.Add(typeof(TReply), (reply, state) => replyFactory(reply));
}
}
6 changes: 6 additions & 0 deletions src/Paramore.Brighter/Event.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ namespace Paramore.Brighter
/// </summary>
public class Event : IEvent
{
/// <summary>
/// An event may be the response to a command, in order to find the command that caused the event, we need to know the correlation id
/// In many cases correlation id is the command id
/// </summary>
public Guid CorrelationId { get; set; }

/// <summary>
/// Gets or sets the identifier.
/// </summary>
Expand Down
Loading
Loading