mediator-pattern-in-asp-netcore

Mediator Pattern in ASP.NET Core

Mediator pattern is used to reduce communication complexity between multiple objects or classes. This pattern provides a mediator class which normally handles all the communications between different classes and supports easy maintenance of the code by loose coupling

For the last few years, Command Query Responsibility Segregation (CQRS) and Event Sourcing (ES) emerged as patterns that can help implement large scale systems, with some risky complexity, by having different models to read or mutate data while also using events as a single source of truth.

Example of mediator pattern in real life. Think about a big airport like  with many of arriving and departing planes. They all need to be coordinated to avoid crashes. It would be impossible for a plan to talk to all other planes. Instead, they call the tower as a mediator, and the tower talks to all planes and organizes who goes where.

In this post  I’m going to focus and show how you can decouple your application layers by  mediator

Commands, Queries, Events, Handlers and Mediator

Before showing some code, lets make a simple review of some core concepts about these patterns and the library we are going to use:

  • Commands — each action intended to change the system state, by creating, updating or deleting information, should be represented by a class implementing either ICommand or ICommand<TResult>, if you are using the mediator just for in-process decoupling and want to return a result synchronously. Examples: CreateProductCommand or DeleteUserCommand
  • Queries — classes implementing IQuery<TResult> represent data reads that shouldn’t change the system state or else they must be considered commands. Examples: GetProductByIdQuery or SearchUsersQuery
  • Events — representing system changes over time, these classes implement IEvent. Examples: ProductCreatedEvent or UpdatedUserEmailEvent
  • Handlers — responsible for receiving the commands (ICommandHandler), queries (IQueryHandler) or events (IEventHandler), they implement the business behavior to be run. Examples: CreateProductCommandHandler or GetProductByIdQueryHandler
  • Mediator — decouples the caller from the executor exposing generic methods to send commands, fetch queries or broadcast events, being responsible for finding the correct handler (or handlers, in case of an event) and is represented by the interface IMediator.

Implementation of the Mediator Pattern

As I described in my previous post, in In my Productmicroservice, the controllers call all needed services (CQRS mediator) and therefore work as the mediator. Additionally, I installed the MediatR , AutoMapper and  MediatR.Extension.Microsoft.DependencyInjection NuGet package, which helps to call the services.

Installing MediatR

I have installed the MediatR , AutoMapper and  MediatR.Extension.Microsoft.DependencyInjection packages in my  project : ProductMicroservice.

In the Startup class, I registered Mediators using:

services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddAutoMapper(typeof(Startup));

In this  project both Controller and CQRS folder (Services) are in the same project (ProductMicroservice)  (The other solution shall be creating two other projects one  for CQRS and one for Data access  and moving data access (in this case Repository) to Data access project and adding references for both new projects to the ProductMicroservice project).

I use the IMediator object with dependency injection in my controllers.

public class ProductController : ControllerBase
{
private readonly IMediator _mediator;
private readonly IMapper _mapper;
public ProductController(IMediator mediator, IMapper mapper)
{
_mediator = mediator;
_mapper = mapper;
}

Using the Mediator pattern

Every call from Controller consists of a request and a handler. The request is sent to the handler which processes this request. A request could be a adding a new object (post action as Insert) which should be saved in the database  object (Get action )  which should be retrieved. Here we are  using CQRS, which the  requests are either a query for read operations or a command for a write operation.

In the ProductController, I have the Post method which will create a  new product object.  I create a CreateProductCommand and map the Product from the post request to the Product of the CreateProductCommandObject. Then I use the Send method of the mediator.

[HttpPost]
public async Task<ActionResult<Product>> Post([FromBody] Product product)
{
try
{
var prod = new CreateProductCommand
{
product = _mapper.Map<Product>(product)
};
return await _mediator.Send(prod);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}Copied!

The command (CreateProductCommand) inherit from IRequest interface which where (T =Product)  indicates the return value. If you don’t have a return value, then inherit from IRequest.

public class CreateProductCommand : IRequest<Product>
{
public Product Product { get; set; }
}

The send method (mediator.Send(prod)) sends the object to the CreateProductCommmandHandler (in the bellow code). The handler inherits from IRequestHandler<TRequest, TResponse> and implements a Handle method. This Handle method processes the CreateProductCommand . In this case, it calls the InsertProduct(request.Product) method of the repository and passes the Product.

using MediatR;
using ProductMicroservice.Models;
using ProductMicroservice.Repository;
using System.Threading;
using System.Threading.Tasks;
namespace ProductMicroservice.CQRS.Commands
{
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Product>
{
private readonly IProductRepository _ProductRepository;
public CreateProductCommandHandler(IProductRepository productRepository)
{
_ProductRepository = productRepository;
}
public async Task<Product> Handle(CreateProductCommand request, CancellationToken cancellationToken)
{
return await Task.FromResult(_ProductRepository.InsertProduct(request.product));
}
}
}

I have implemented Mediator to the a method in the ProductController.

you can can find implementation of all methods in the  GitHub.

Advantages

  • Less coupling: Since the classes don’t have dependencies on each other, they are less coupled.
  • Easier reuse: Fewer dependencies also helps to reuse classes.
  • Single Responsibility Principle: The services don’t have any logic to call other services, therefore they only do one thing.
  • Open/closed principle: Adding new mediators can be done without changing the existing code.

Disadvantages

  • he Mediator often needs to be very intimate with all the different classes, And it makes it really complex.
  • Can make it difficult to maintain.

Conclusion

The Mediator pattern is used to reduce communication complexity between multiple objects or classes. This pattern provides a mediator class which normally handles all the communications between different classes and supports easy maintenance of the code by loose coupling. This also helps you to reuse your components in your application and also to keep the Single Responsible Principle, by using MediatR NuGet package

In my Next post  I will going to implement RabbitMQ, which enables Microservices to exchange data (communicate with each others) in a decoupled asynchronous way.

The complete code can be find on GitHub.

This post is part of Microservices-Step by step”.

Back to home page