-
Notifications
You must be signed in to change notification settings - Fork 9
Middleware
To enable the interception of commands, events and queries, the mediator supports specialized middleware classes, that will behave like a pipeline, passing the entity as a parameter to the next delegate, until it reaches the handler(s). At any time the middleware can change the mediator flow, by making a short-circuit, throw or ignore exceptions or even change the handler result, if applied.
Explanation per type:
Remarks: The same behavior could be achieved using the decorator pattern over each handler, but that would require the support by the dependency injection provider or each handler extending from some base class. Having the direct support from the library just gives more control and removes this limitations.
The following interfaces and abstract classes are used to implement middleware instances:
| Interface/Class | Description |
|---|---|
ICommandMiddleware |
interface that enables the interception of commands before or after the send to the handler |
IEventMiddleware |
interface that enables the interception of events before or after the broadcast to the handlers |
IQueryMiddleware |
interface that enables the interception of queries before or after the data being fetched from the handler |
IMiddleware |
interface used to aggregate all interception possibilities by implementing all the other interfaces |
Middleware |
abstract class implementing IMiddleware and exposing everything as virtual methods |
The interception of commands is made using the interface ICommandMiddleware and the order by which they are run is the same as the collection returned by the method IMediatorFactory.BuildCommandMiddlewares().
The interface ICommandMiddleware has the following methods:
| Method | Description |
|---|---|
OnCommandAsync<TCommand> |
invoked when a command without result is sent into the mediator |
OnCommandAsync<TCommand,TResult> |
invoked when a command that has a result is sent into the mediator. It must return the command result |
Each method has the following parameters:
| Parameter | Description |
|---|---|
CommandMiddlewareDelegate |
delegate with the next pipeline action, that will either be the next ICommandMiddleware or invoking the ICommandHandler. It must be invoked by the middleware, otherwise a flow short-circuit will happen |
TCommand |
the command that was sent into the mediator and will eventually be received by the handler |
CancellationToken |
the cancellation token that should be used to cancel the async execution |
Lets imagine there was a requirement to log the JSON of every sent command and result, if applied. A possible implementation could be the following:
public class LoggingMiddleware : ICommandMiddleware {
private readonly ILogger<LoggingMiddleware> _logger;
public LoggingMiddleware(ILogger<LoggingMiddleware> logger) {
_logger = logger;
}
public async Task OnCommandAsync<TCommand>(CommandMiddlewareDelegate<TCommand> next, TCommand cmd, CancellationToken ct) {
if(_logger.IsEnabled(LogLevel.Debug)) {
_logger.LogDebug("Command data: {commandData}", JsonConvert.SerializeObject(cmd));
}
await next(cmd, ct).ConfigureAwait(false);
}
public async Task<TResult> OnCommandAsync<TCommand, TResult>(CommandMiddlewareDelegate<TCommand, TResult> next, TCommand cmd, CancellationToken ct) {
if(_logger.IsEnabled(LogLevel.Debug)) {
_logger.LogDebug("Command data: {commandData}", JsonConvert.SerializeObject(cmd));
}
var result = await next(cmd, ct).ConfigureAwait(false);
if(_logger.IsEnabled(LogLevel.Debug)) {
_logger.LogDebug("Command result: {commandResult}", JsonConvert.SerializeObject(result));
}
return result;
}
}The interception of events is made using the interface IEventMiddleware and the order by which they are run is the same as the collection returned by the method IMediatorFactory.BuildEventMiddlewares().
The interface IEventMiddleware has the following method:
| Method | Description |
|---|---|
OnEventAsync<TEvent> |
invoked when an event should be broadcast by the mediator |
The method has the following parameters:
| Parameter | Description |
|---|---|
EventMiddlewareDelegate |
delegate with the next pipeline action, that will either be the next IEventMiddleware or invoking the collection of all known IEventHandler for the event type. It must be invoked by the middleware, otherwise a flow short-circuit will happen |
TEvent |
the event that must be broadcast by the mediator and will eventually be received by the handlers |
CancellationToken |
the cancellation token that should be used to cancel the async execution |
Lets imagine there was a requirement to log the JSON of every event being broadcast. A possible implementation could be the following:
public class LoggingMiddleware : IEventMiddleware {
private readonly ILogger<LoggingMiddleware> _logger;
public LoggingMiddleware(ILogger<LoggingMiddleware> logger) {
_logger = logger;
}
public async Task OnEventAsync<TEvent>(EventMiddlewareDelegate<TEvent> next, TEvent evt, CancellationToken ct) {
if(_logger.IsEnabled(LogLevel.Debug)) {
_logger.LogDebug("Event data: {eventData}", JsonConvert.SerializeObject(evt));
}
await next(evt, ct).ConfigureAwait(false);
}
}The interception of queries is made using the interface IQueryMiddleware and the order by which they are run is the same as the collection returned by the method IMediatorFactory.BuildQueryMiddlewares().
The interface IQueryMiddleware has the following method:
| Method | Description |
|---|---|
OnQueryAsync<TQuery,TResult> |
invoked when the mediator wants to fetch some query data. It must return the query result |
Each method has the following parameters:
| Parameter | Description |
|---|---|
QueryMiddlewareDelegate |
delegate with the next pipeline action, that will either be the next IQueryMiddleware or invoking the IQueryHandler. It must be invoked by the middleware, otherwise a flow short-circuit will happen |
TQuery |
the query which data must be fetched by the mediator and will eventually be received by the handler |
CancellationToken |
the cancellation token that should be used to cancel the async execution |
Lets imagine there was a requirement to log the JSON of every fetched query and its result. A possible implementation could be the following:
public class LoggingMiddleware : IQueryMiddleware {
private readonly ILogger<LoggingMiddleware> _logger;
public LoggingMiddleware(ILogger<LoggingMiddleware> logger) {
_logger = logger;
}
public async Task<TResult> OnQueryAsync<TQuery, TResult>(QueryMiddlewareDelegate<TQuery, TResult> next, TQuery query, CancellationToken ct) {
if(_logger.IsEnabled(LogLevel.Debug)) {
_logger.LogDebug("Query data: {queryData}", JsonConvert.SerializeObject(query));
}
var result = await next(query, ct).ConfigureAwait(false);
if(_logger.IsEnabled(LogLevel.Debug)) {
_logger.LogDebug("Query result: {queryResult}", JsonConvert.SerializeObject(result));
}
return result;
}
}