Skip to content

Middleware

João Simões edited this page Sep 7, 2017 · 11 revisions

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.

Components

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

Commands

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().

Methods

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

Parameters

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

Example

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;
  }
}

Events

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().

Methods

The interface IEventMiddleware has the following method:

Method Description
OnEventAsync<TEvent> invoked when an event should be broadcast by the mediator

Parameters

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

Example

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);
  }
}

Queries

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().

Methods

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

Parameters

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

Example

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;
  }
}

Clone this wiki locally