(UPDATED to .NET 6) Implementing CQRS with MediatR in ASP.NET Core

In this article, let's talk about implementing CQRS - Command Query Responsibility Segregation in ASP.NET Core via Mediator pattern by using MediatR library.

An application becomes difficult to maintain as the solution grows in size with new code. A variety of problems creep into the system each time it is modified or extended for a new change.

Design Patterns aim at preventing such design issues as the application grows making it scalable and extensible.

In this article, let’s talk about one such Design Pattern which talks about enforcing Single Responsibility on the entities by segregating actions based on their impact on the data. It is called as CQRS.

What is CQRS?

CQRS stands for Command Query Responsibility Segregation. It is an Architectural Design Pattern that advocates segregating or grouping the methods based on how they impact the data and design them separately according to their requirements. It is a general tendency to use a single representation of data at various levels of an application such as a Model, a DTO or even a Domain Entity for both GET and POST operations. This approach fails for applications that involve complex Entities or requirements.

For example, a GET request in a complex application might require projecting data from multiple Domain Entities as it reaches the User, while a POST request model might require validation and transformation before it is saved onto the database. Instead of stuffing a single Model with features which is not a part of its Responsibility, we segregate the components and treat them with entities and processing that match their Responsibility.

wp-content/uploads/2022/05/cqrs-block.png

What are Commands and Queries?

CQRS classifies functionalities into two different models – Commands and Queries.

Commands

  • The methods which MODIFY the dataset; which result in a data CHANGE.
  • Examples are POST, PUT or PATCH methods.
  • These methods might require data validation or transformation before writing onto the database.
  • These are mostly focused on a TASK rather than data. For example; "PLACE a new ORDER" rather than "INSERT ID INTO USER_ORDERS"
  • The application can vary how Commands are processed –
    • they can be placed on a queue and processed or
    • even written to a different datastore for performance.

Queries

  • The methods which DO NOT modify the dataset; which result in a data READ.
  • Examples are GET or a HEAD method.
  • These methods might require projecting data from multiple entities before returning.
  • The application can decide on what needs to be returned to the user and what shouldn’t be; encapsulating irrelevant domain data such as KEYS.

"A Command doesn’t worry about how data would be returned; its models focus only on the WRITE and its requirements. The Queries don’t bother about how data would be written; their models focus only on the response and what needs to be pushed in the response. This creates a clear separation of responsibilities into distinctive objects and each work in their own perspective of the dataset."

When to use CQRS?

Like with many other Design Patterns, CQRS should be used only when necessary and is not suitable for all applications. Applying CQRS on applications without a need would unnecessarily complicate the architecture which might result in an anti-pattern situations.

CQRS should only be used when the application has characteristics like:

  • Complex application model with multiple relationships and dependencies
  • An application that offers something more than a basic CRUD operation over the dataset – which achieves something
  • READs and WRITEs which are not at all balanced in terms of loads
  • Where data is not immediately pushed into the database and instead should go through a series of processing pipeline

In general, CQRS Pattern can be efficiently implemented in applications using other patterns such as Event Sourcing.

In this article, we’ll implement this pattern by taking help of another Pattern that advocates decoupling components from their dependencies by making use of a single "central controller" – a Mediator.

What is a Mediator?

Mediator pattern is another Design Pattern which advocates encapsulating and decoupling interacting components in a system.

It is a Behavioral Design Pattern which recommends:

  • Coupling of two components interacting with each other should be avoided and
  • It should be possible to change the interaction between the components dynamically without having to disturb the system.

A typical example of a Mediator is an Air Traffic Controller (ATC) which guides all the airplanes flying in the airspace – any communication between two airplanes are delegated to the ATC which forwards it to the other airplane so that the airplanes are relieved off this overload. Similar is the case with a Rail Traffic Controller (RTC) that is responsible for signaling and safe movement of trains on the Rail Tracks.

Why to use Mediator for CQRS?

Mediator pattern handles the decoupling by placing an intermediate layer between the components called as a "Mediator" which will be their single source of dependencies. The components delegate their function calls to the Mediator and the Mediator decides which component needs to be invoked for what. This ensures that components are "loosely coupled" with each other and they don’t call each other directly. This also creates the opportunity for a component to be replaced by another implementation if required and the system won’t be affected.

In CQRS, the responsibilities for Querying and Commands are taken up by their respective Handlers and it becomes quite difficult for us to manually wire up a Command with its Handler and maintain the mappings. This is where Mediator pattern comes into picture: it encapsulates these interactions and takes care of the mapping for us.

wp-content/uploads/2022/05/mediator-block.png

Desiging a CQRS solution in ASP.NET Core API with MediatR

To implement CQRS using this pattern, we define a "Request" and a "Handler". The "Request" is created and sent by the front-end method to the Mediator which contains a mapping of the "Requests" and their "Handlers". The Mediator recognizes the "Request" and delegates it to the respective "Handler" that returns the data accordingly. It makes sure that the front-end methods are always clean and focused, while the necessary processing is handled by the "Handler".

"In ASP.NET Core, we can implement the Mediator pattern by using a library called "MediatR", an open source library which provides the template for a simple Mediator."

For our demonstration, lets take the example of a Ninjas API which works on an entity "Ninja". A couple of methods encapsulate processing these Entities for READ and WRITE operations. We shall now implement CQRS pattern on this system and decouple the methods from their back-end domain layers by means of a Mediator.

The Ninja entity look like below:

namespace CqrsNinja.Contracts.Data.Entities
{
    public class Ninja : BaseEntity
    {
        public string Name { get; set; }
        public string Moniker { get; set; }
        public string Bio { get; set; }
        public string Clan { get; set; }
        public string Weapon { get; set; }
    }
}

The entity is supported by its respective Repository implementation which encapsulates the database logic.

using CqrsNinja.Contracts.Data.Entities;

namespace CqrsNinja.Contracts.Data.Repositories
{
    public interface IRepository<T>
    {
        IEnumerable<T> GetAll();
        T Get(object id);
        void Add(T entity);
        void Update(T entity);
        void Delete(object id);
        int Count();
    }

    public interface INinjaRepository : IRepository<Ninja> { }
}

This setup clearly satisfies the notion of CQRS: separate representations and processing for Commands and Queries. We shall add a layer of Mediator to this setup which delegates the requests from the Controller to a Mediator layer that forwards it to respective Handlers for processing. Since we have Commands and Queries separated, we’ll have separate Requests and Handlers for Commands and Queries respectively. Let’s look at implementing a single Command and Query to support our argument.

Installing the necessary Packages

To get started with MediatR, we require to add two packages into our solution which introduces the Mediator container and libraries for injection.

<ItemGroup>
    <PackageReference Include="MediatR" Version="9.0.0" />
    <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
</ItemGroup>

To wire up the Mediator DependencyInjection capabilities which we’re using in to resolve dependencies at the Handler, we add the MediatR service to the container during the Startup as below:

services.AddMediatR(Assembly.GetExecutingAssembly());

The type attribute passed is the location where the MediatR needs to check for resolving dependency related information. In general cases, it’d be the Startup class where the definitions reside and are wired up.

Usecase 1 – Implementing a Query for a GET All Ninjas API

1. Creating a Query

Let’s apply Mediator to Get() function of Ninja Entity that returns all the Ninjas registered in the application. We begin by creating a "QueryRequest" that represents this request in the Mediator layer.

namespace CqrsNinja.Providers.Handlers.Queries
{
    public class GetAllNinjasQuery : IRequest<IEnumerable<NinjaDTO>>
    {
    }
}

"The IRequest interface accepts the type which the Handler should return to the calling component."

2. Implementing a Query Handler

The Handler must return an IEnumerable to the calling client which is our controller. We next add a Handler which receives this request via the Mediator and processes it.

namespace CqrsNinja.Providers.Handlers.Queries
{
    public class GetAllNinjasQuery : IRequest<IEnumerable<NinjaDTO>>
    {
    }

    public class GetAllNinjasQueryHandler : 
        IRequestHandler<GetAllNinjasQuery, IEnumerable<NinjaDTO>>
    {
        private readonly IUnitOfWork _repository;
        private readonly IMapper _mapper;

        public GetAllNinjasQueryHandler(
                IUnitOfWork repository, IMapper mapper)
        {
            _repository = repository;
            _mapper = mapper;
        }

        public async Task<IEnumerable<NinjaDTO>> Handle(
                GetAllNinjasQuery request, CancellationToken cancellationToken)
        {
            var entities = await Task.FromResult(_repository.Ninjas.GetAll());
            return _mapper.Map<IEnumerable<NinjaDTO>>(entities);
        }
    }
}

"The IRequestHandler accepts two type parameters: the Request to which is must respond and the type it must return."

The IRequestHandler comes with Dependency Injection and hence any dependency that needs to be used in the handler can be injected via the constructor. In this case, we inject and invoke the INinjaRepository which has the implementation for fetching and returning records from the database. The Handler transforms the data into required projection so that the domain data is encapsulated from unwanted access.

Observe that the methods which WRITE to the database and which READ from the database use separate data representations: CreateNinjaDTO and NinjaDTO. This is because we don’t need to supply an Id attribute to the system to add a new Ninja, while returning a Ninja might involve adding data from other entities as well.

3. Calling a Query from the API Endpoint

Finally in the Controller, we call the Mediator and Send our "QueryRequest" which is delegated to the QueryRequestHandler and return data from the database.

[Route("api/[controller]")]
[ApiController]
public class NinjasController : ControllerBase
{
    private readonly IMediator _mediator;

    public NinjasController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpGet]
    [ProducesResponseType(
        typeof(IEnumerable<NinjaDTO>), (int)HttpStatusCode.OK)]
    [ProducesErrorResponseType(typeof(BaseResponseDTO))]
    public async Task<IActionResult> Get()
    {
        // create request
        var query = new GetAllNinjasQuery();

        // get response
        var response = await mediator.Send(query);

        // use it
        return Ok(response);
    }
}

Observe that the Controller is now totally free from any dependency since it only needs to communicate to the Mediator that handles the processing and dependencies for us.

Usecase 2 – Implementing a Command for Create Ninja API with Request Parameters

1. Creating a Command

Mediating a Command works on the similar lines to Mediating a Query, except that the focus is on WRITE rather than READ. A CommandRequest to add a new Ninja to the database looks like below.

namespace CqrsNinja.Providers.Handlers.Commands
{
    public class CreateNinjaCommand : IRequest<int>
    {
        public CreateNinjaDTO Model { get; }
        public CreateNinjaCommand(CreateNinjaDTO model)
        {
            this.Model = model;
        }
    }
}

Observe that we have added a Property Model of type CreateNinjaDTO that holds the Ninja information to be added to the database. We’re passing the data to be used by the Handler on the other side of the Mediator as Properties, assigning them values via constructor. When the Request object is created, we add data to the Request via the constructor which assigns it to the respective public Properties. The Handler makes use of these public Properties to get the data. This is the same approach we use for Querying data from the database via the Mediator for a given filter criteria (say a specific Id or Email).

2. Implementing a Command Handler

The Handler for this Command is as follows:

namespace CqrsNinja.Providers.Handlers.Commands
{
    public class CreateNinjaCommand : IRequest<int>
    {
        public CreateNinjaDTO Model { get; }
        public CreateNinjaCommand(CreateNinjaDTO model)
        {
            this.Model = model;
        }
    }

    public class CreateNinjaCommandHandler 
        : IRequestHandler<CreateNinjaCommand, int>
    {
        private readonly IUnitOfWork _repository;
        private readonly IValidator<CreateNinjaDTO> _validator;

        public CreateNinjaCommandHandler(
                IUnitOfWork repository, IValidator<CreateNinjaDTO> validator)
        {
            _repository = repository;
            _validator = validator;
        }

        public async Task<int> Handle(
            CreateNinjaCommand request, CancellationToken cancellationToken)
        {
            CreateNinjaDTO model = request.Model;

            var result = _validator.Validate(model);

            if (!result.IsValid)
            {
                var errors = result.Errors.Select(x => x.ErrorMessage).ToArray();
                throw new InvalidRequestBodyException
                {
                    Errors = errors
                };
            }

            var entity = new Ninja
            {
                Name = model.Name,
                Moniker = model.Moniker,
                Bio = model.Bio,
                Clan = model.Clan,
                Weapon = model.Weapon
            };

            _repository.Ninjas.Add(entity);
            await _repository.CommitAsync();

            return entity.Id;
        }
    }
}

The Handler WRITEs the Ninja passed to this via the Property Model inside the CommandRequest object and returns the Id of the created Ninja.

The Handler pushes the record into the backend (persistent store) via a UnitOfWork instance which encapsulates the dbContext object.

namespace CqrsNinja.Core.Data
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly DatabaseContext _context;

        public UnitOfWork(DatabaseContext context)
        {
            _context = context;
        }
        public INinjaRepository Ninjas => new NinjaRepository(_context);

        public async Task CommitAsync()
        {
            await _context.SaveChangesAsync();
        }
    }
}

3. Calling the Command from the API Endpoint

On the controller end, its a simple affair with only to create a CommandRequest request and Send on the Mediator.

[HttpPost]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
[ProducesErrorResponseType(typeof(BaseResponseDTO))]
public async Task<IActionResult> Post([FromBody] CreateNinjaDTO model)
{
    try
    {
        var command = new CreateNinjaCommand(model);
        var response = await _mediator.Send(command);
        return StatusCode((int)HttpStatusCode.Created, response);
    }
    catch (InvalidRequestBodyException ex)
    {
        return BadRequest(new BaseResponseDTO
        {
            IsSuccess = false,
            Errors = ex.Errors
        });
    }
}

Final Thoughts and Source Code

CQRS helps us in separating the functions that WRITE data to the persistent store from the functions that READ from the same. This create a modular and loosely coupled solution, where each functionality can be scaled or extended without having to disturb the other.

The code snippets used in this article are a part of CqrsNinja – An ASP.NET Core boilerplate for implementing CQRS with MediatR. The solution offers a perfect starting point for developers looking to get started in the CQRS/MediatR space. The solution also offers couple other interesting implementations such as Fluent Validation, Swagger UI, and so on, all packed in a Clean Architecture.

Do check out the repository and give a star if you find the solution helpful.

Default image
Sriram Mannava

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity.

Leave a Reply