Card image cap

Implementing CQRS using Mediator in ASP.NET Core - Explained

ASP.NET Core  • Posted one month ago

As an application grows over the time both in complexity and load, it becomes difficult to maintain. A variety of problems creep into the system each time it is modified or extended for a new change. Design Patterns aim at solving design issues beforehand, avoiding such unwanted problems 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 - called as CQRS. We shall also look at how we implement this pattern by taking help of another Pattern that advocates decoupling components from their dependencies by making use of a single "central controller" - the Mediator.

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.

data/Admin/2020/6/cqrs-block.png

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 shall look at how we can implement CQRS using a Mediator pattern.

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 advocates that:

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.

data/Admin/2020/6/mediator-block.png

Implementing Mediator - using 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 Readers API which works on two entities "Reader" and "User". 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 Reader and User models look like below:

namespace ReadersCqrsApp.Models.Entities
{
    public class Reader
    {
        [Key]
        public int Id { get; set; }
        public string Alias { get; set; }
        public string Bio { get; set; }
        public DateTime AddedOn { get; set; }
        public int UserId { get; set; }

        [ForeignKey("UserId")]
        public User User { get; set; }
    }
}
namespace ReadersCqrsApp.Models.Entities
{
    public class User
    {
        [Key]
        public int Id { get; set; }
        public string Username { get; set; }
        public string EmailAddress { get; set; }
        public DateTime AddedOn { get; set; }
    }
}

Observe that the Entity Reader is dependent on the Entity User and when a Reader is to be read from the database, we would also need to pick a few pieces of information from its determinant User entity as well. This is where we require a separation of data representation between the GET and POST operations and exactly where CQRS really shines.

The User and Reader entities are supported by their respective Repository classes which encapsulate the database logic.

namespace ReadersCqrsApp.Providers.Repositories
{
    public interface IReadersRepository
    {
        Task<int> CreateReader(CreateReaderModel model);
        IEnumerable<Reader> GetAll();
        Reader GetSingle(int readerId);
        Task<int> UpdateReader(int readerId, UpdateReaderModel model);
    }

    public interface IUsersRepository
    {
        Task<int> CreateUser(UserRequestModel model);
        IEnumerable<UserResponseModel> GetAll();
        UserResponseModel GetSingle(int readerId);
        Task<int> UpdateUser(int readerId, UserRequestModel model);
    }
}

Their controller methods resemble in a similar fashion as follows:

namespace ReadersCqrsApp.Controllers
{
    public interface IReadersController
    {
        Task<IEnumerable<ReaderResponseModel>> Get();
        Task<ReaderResponseModel> GetSingle(int id);
        Task<int> Post(CreateReaderModel model);
    }

    public interface IUsersController
    {
        Task<IEnumerable<UserResponseModel>> Get();
        Task<int> Post([FromBody] UserRequestModel model);
    }
}

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.

Prerequisite:

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="8.0.1" />
    <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="8.0.0" />
</ItemGroup>

Mediating a Query:

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

namespace ReadersCqrsApp.Providers.Requests.Queries
{
    public class GetAllReadersQueryRequest 
    : IRequest<IEnumerable<ReaderResponseModel>> { }
}

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

In this case, 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 ReadersCqrsApp.Providers.Handlers.Queries
{
    public class GetAllReadersQueryHandler 
        : IRequestHandler<GetAllReadersQueryRequest, IEnumerable<ReaderResponseModel>>
    {
        private readonly IReadersRepository repository;

        public GetAllReadersQueryHandler(IReadersRepository repository)
        {
            this.repository = repository;
        }

        public async Task<IEnumerable<ReaderResponseModel>> Handle(
                GetAllReadersQueryRequest request, CancellationToken cancellationToken)
        {
            var readers = this.repository.GetAll();
            return readers.Select(x => new ReaderResponseModel
            {
                Id = x.Id,
                Alias = x.Alias,
                EmailAddress = x.User.EmailAddress,
                Username = x.User.Username,
                Bio = x.Bio
            });
        }
    }
}

"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 IReadersRepository 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: CreateReaderModel or UserRequestModel and ReaderResponseModel and UserResponseModel. This is because we don't need to supply an Id attribute to the system to add a new Reader, while returning a Reader might involve adding data from related User entity as well.

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 ReadersController : ControllerBase, IReadersController
{
    private readonly IMediator mediator;

    public ReadersController(IMediator mediator)
    {
        this.mediator = mediator;
    }

    [HttpGet]
    public async Task<IEnumerable<ReaderResponseModel>> Get()
    {
        // create request
        var query = new GetAllReadersQueryRequest();

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

        // use it
        return 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.

Mediating a Command - Working with Parameterized Requests:

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 WRITE new Reader to the database looks like below.

namespace ReadersCqrsApp.Providers.Requests.Commands
{
    public class CreateReaderCommandRequest : IRequest<int>
    {
        public CreateReaderModel Model { get; }

        public CreateReaderCommandRequest(CreateReaderModel model)
        {
            this.Model = model;
        }
    }
}

Observe that we have added a Property Model of type CreateReaderModel that holds the Reader 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 ReaderId or Email).

The Handler for this Command is as follows:

namespace ReadersCqrsApp.Providers.Handlers.Commands
{
    public class CreateReaderCommandHandler
        : IRequestHandler<CreateReaderCommandRequest, int>
    {
        private readonly IReadersRepository repository;

        public CreateReaderCommandHandler(
            IReadersRepository repository)
        {
            this.repository = repository;
        }

        public async Task<int> Handle(
            CreateReaderCommandRequest request, CancellationToken cancellationToken)
        {
            var readerId = await this.repository.CreateReader(request.Model);
            return readerId;
        }
    }
}

The Handler WRITEs the reader passed to this via the Property Model inside the CommandRequest object and returns the Id of the created Reader. On the controller end, its a simple affair with only to create a CommandRequest request and Send on the Mediator.

[HttpPost]
public async Task<int> Post(CreateReaderModel model)
{
    var command = new CreateReaderCommandRequest(model);
    var response = await mediator.Send(command);
    return response;
}

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.AddScoped<IUsersRepository, UsersRepository>();
services.AddScoped<IReadersRepository, ReadersRepository>();

services.AddMediatR(typeof(Startup));

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.

In this way, we can implement CQRS pattern with a Mediator using MediatR. The complete source code used in this article is available at https://github.com/referbruv/cqrs-mediatr-aspnetcore-sample

asp.net core cqrs mediatr cqrs with mediatr and asp.net core cqrs mediatr .net core cqrs with mediatr mediatr asp.net core example cqrs mediatr example mediatr asp.net core web api mediatr in asp.net core mediator in asp.net core mediator in .net core mediator dotnet core mediator asp.net core asp.net core mediator pattern mediator asp net core mediator pattern asp.net core

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept