Mediatr

CQRS with MediatR in .NET 6

In this article, I will show a brief introduction about CQRS and Mediator. We’ll also look at a practical example of how to use these patterns in .NET 6 using MediatR.

Introduction to CQRS patterns and Mediator

What is CQRS?

The command and query segregation of responsibility ( CQRS ) pattern separates the query and update operations of a data store.

This maximizes performance, scalability, and security of an application.

In traditional architectures, the same data model is used to query and update a database. It’s straightforward and works well for basic CRUD operations. However, in more complex applications, this approach can be unwieldy.

CQRS separates reads and writes into different models, uses  “command”  to update the data and  “query”  to read the data.

Queries

They are queries that return a result without changing the state of the system and have no side effects.

  • Queries never modify the database. A query returns a DTO that does not encapsulate any domain knowledge.

Commands

They change the state of a system.

  • Commands should be task-based, rather than data-centric.
  • Commands can be queued for asynchronous processing, instead of being processed synchronously.

CQRS can be carried out in three different ways

1. Work with a single database. Here the commands and queries work on the same database.

2. Separate command and query databases. Here the “write” database works for create, update and delete tasks. All query payloads work on the “read” database. In this way, we purify the write database from the query load. We could have faster queries by using a materialized view which is a database object that contains the results of a query.

3. CQRS-Event Source. The “write” database is now represented by the event queue (event store). The event handler is the component that consumes events from the event store and using these events updates the data in the “read” database. Therefore, the current states of the entities are stored only in the “read” database. All entity transformation history can be extracted using a sequence of events that is stored in the “write” database.

Why use CQRS?

Here are some advantages of CQRS:

  • Independent scaling. CQRS allows read and write workloads to scale independently, which can result in fewer lock contentions.
  • Optimized data schemas.  The read side can use a schema that is optimized for queries, while the write side uses a schema that is optimized for updates.
  • security. It’s easier to ensure that only the correct domain entities are doing writes to the data.
  • Separation of issues. The separation of read and write can lead to models that are more flexible and easier to maintain. Most of the complex business logic goes into the script model. The reading model can be relatively simple.
  • Easier queries. By storing a materialized view in the read database, the application can avoid complex joins when making queries.

What is Mediator?

It is a behavior pattern that allows to reduce chaotic dependencies between objects. The pattern restricts direct communications between objects and forces them to collaborate only through a mediator object. The mediator pattern defines an object that encapsulates how a set of objects interact with each other.

The objects do not communicate directly with each other, instead they communicate through the mediator. This reduces the dependencies between the communicating objects, thus reducing the code dependency.

MediatR  is an implementation of the Mediator pattern in .NET. Supports synchronous and asynchronous requests/responses, commands, queries, notifications, and events with intelligent dispatch via C# generic variance.

Let’s go to code?

We will build an application that illustrates how to use CQRS patterns and Mediator. The implementation will be simple so that we can focus more on building the CQRS pattern using MediatR. If your coffee☕ is ready let’s get started.

CQRS

First of all, let’s start with the implementation of the CQRS pattern.

  • First, we add the Requests models:
public class AddPostRequestModel
{
    public Guid UserId { get; set; }
    public Guid Id { get; set; }
    public string? Title { get; set; }
    public string? Body { get; set; }
}
public class GetPostRequestModel
{
    public Guid PostId { get; set; }
}
  • Second, we add the response models ( Responses ):
public class AddPostResponseModel
{
    public Guid PostId { get; set; }
}
public class GetPostResponseModel
{
    public Guid Id { get; set; }
    public Guid UserId { get; set; }
    public string? Title { get; set; }
    public string? Body { get; set; }
}
  • Next, we add two interfaces, which will contain methods that we will implement later:
public interface IAddPostCommandHandler
{
    AddPostResponseModel AddPost(AddPostRequestModel request);
}
public interface IGetPostQueryHandler
{
    GetPostResponseModel GetPost(GetPostRequestModel request);
}
  • Now yes, we implement classes that inherit from these interfaces and their methods ( Handlers ):
public class AddPostCommandHandler : IAddPostCommandHandler
{
    public AddPostResponseModel AddPost(AddPostRequestModel request)
    {
        // your logic add post...

        return new AddPostResponseModel()
        {
            PostId = Guid.NewGuid()
        };
    }
}
public class GetPostQueryHandler : IGetPostQueryHandler
{
    public GetPostResponseModel GetPost(GetPostRequestModel request)
    {
        // your logic get post...

        return new GetPostResponseModel()
        {
            Body = "It is a long established fact that a reader will...",
            Id = Guid.Parse("556b8afa-5617-425c-87af-381f278ccf90"),
            Title = "What is Lorem Ipsum?",
            UserId = Guid.Parse("40cea88f-9cdb-4509-a8bd-a307d149daf0")
        };
    }
}
  • Let’s go with the controller. We add dependencies to the application startup code in the “program.cs” file:
builder.Services.AddScoped<IAddPostCommandHandler, AddPostCommandHandler>();
builder.Services.AddScoped<IGetPostQueryHandler, GetPostQueryHandler>();
  • Lastly, we create the controller and inject dependencies IAddPostCommandHandlerand IGetPostCommandHandler. Finally we already have the CQRS pattern ready to use?
[ApiController]
[Route("[controller]")]
public class PostController : ControllerBase
{
    private readonly IAddPostCommandHandler _addPostCommandHandler;
    private readonly IGetPostQueryHandler _getPostQueryHandler;

    public PostController(IAddPostCommandHandler addPostCommandHandler, IGetPostQueryHandler getPostQueryHandler)
    {
        _addPostCommandHandler = addPostCommandHandler;
        _getPostQueryHandler = getPostQueryHandler;
    }

    [HttpGet(Name = "postDetails")]
    public IActionResult Get([FromQuery] GetPostRequestModel request)
    {
        var response = _getByIdQueryHandler.GetPost(request);
        return Ok(response);
    }

    [HttpPost(Name = "addPost")]
    public IActionResult Post([FromBody] AddPostRequestModel request)
    {
        var response = _addPostCommandHandler.AddPost(request);
        return Ok(response);
    }
}

MediatR

Next, we’ll continue with the Mediator pattern , which helps us solve the following problems:

  • Reduce the number of connections between classes.
  • Object encapsulation using the mediator interface.
  • Provide a unified interface to manage dependencies between classes.

Let’s apply MediatR to the previous example.

At the start, we install the “MediatR” library:

Install-Package MediatR.Extensions.Microsoft.DependencyInjection

We refactored the source code as follows:

  • First of all, when you start using the MediatR library, the first thing you need to define is “request”. Requests describe the behavior of your commands and queries. I Request<T> is the request or message indicating the task to be performed, requested by some service and addressed to   Handlers. That is, the mediator will take the IRequest<T> and send it to the registered handlers. These handlers know the message they can receive and they know how the task will be carried out.
public class AddPostRequestModel : IRequest<AddPostResponseModel>
{
    public Guid UserId { get; set; }
    public Guid Id { get; set; }
    public string? Title { get; set; }
    public string? Body { get; set; }
}
public class GetPostRequestModel : IRequest<GetPostResponseModel>
{
    public Guid PostId { get; set; }
}
  • Second, when a request is created, we will need a controller to resolve the request, for this we update the controllers with IRequestHandler<T>. All handlers must implement the IRequestHandler interface. This interface depends on two parameters. First — request, second — response.
public class AddPostCommandHandler : IRequestHandler<AddPostRequestModel, AddPostResponseModel>
{
    public async Task<AddPostResponseModel> Handle(AddPostRequestModel request, CancellationToken cancellationToken)
    {
        // your logic add post...

        return new AddPostResponseModel()
        {
            PostId = Guid.NewGuid()
        };
    }
}
public class GetPostQueryHandler : IRequestHandler<GetPostRequestModel, GetPostResponseModel>
{
    public async Task<GetPostResponseModel> Handle(GetPostRequestModel request, CancellationToken cancellationToken)
    {
        // your logic get post...

        return new GetPostResponseModel()
        {
            Body = "It is a long established fact that a reader will...",
            Id = Guid.Parse("556b8afa-5617-425c-87af-381f278ccf90"),
            Title = "What is Lorem Ipsum?",
            UserId = Guid.Parse("40cea88f-9cdb-4509-a8bd-a307d149daf0")
        };
    }
}
  • Third, we register MediatR in “program.cs”:
//builder.Services.AddScoped<IAddPostCommandHandler, AddPostCommandHandler>();
//builder.Services.AddScoped<IGetPostQueryHandler, GetPostQueryHandler>();
builder.Services.AddMediatR(Assembly.GetExecutingAssembly());
  • So, we have our request and our handler, but how to use them? All we need is to define the controller, inject the mediator, and send the query. That’s all.
[ApiController]
[Route("[controller]")]
public class PostController : ControllerBase
{
    private readonly IMediator _mediator;

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

    [HttpGet(Name = "postDetails")]
    public IActionResult Get([FromQuery] GetPostRequestModel request)
    {
        var response = _mediator.Send(request);
        return Ok(response);
    }

    [HttpPost(Name = "addPost")]
    public IActionResult Post([FromBody] AddPostRequestModel request)
    {
        var response = _mediator.Send(request);
        return Ok(response);
    }
}
  • Finally, we refactored by removing the IAddPostCommandHandler and IGetPostQueryHandler interfaces , because MediatR provides a unified interface for managing dependencies between classes.

Validations with FluentValidation

We can improve the above by adding validations using FluentValidation , it’s a free to use .NET validation library that helps us make validations clean, easy to create and maintain. Let’s see the following code:

  • We install FluentValidation from the package console:
Install-Package FluentValidation.DependencyInjectionExtensions
Install-Package FluentValidation.AspNetCore
  • Now, let’s go ahead and add a new validator with our rule directly in the AddPostRequestModel and GetPostRequestModel classes.

We create a class called AddPostCommandValidator that inherits from the AbstractValidator <T> class, specifying the AddPostRequestModel type. This lets FluentValidation know that this validation is for the AddPostRequestModel class.

public class AddPostCommandValidator : AbstractValidator<AddPostRequestModel>
{
    public AddPostCommandValidator()
    {
        RuleFor(v => v.Title)
            .MaximumLength(200)
            .NotEmpty()
            .WithMessage("Title is empty.");

        /*...*/
    }
}
public class GetPostQueryValidator : AbstractValidator<GetPostRequestModel>
{
    public GetPostQueryValidator()
    {
        /*...*/

        RuleFor(x => x.PostId)
            .Must(ValidateGuid)
            .WithErrorCode("Not a guid");
    }

    private bool ValidateGuid(Guid arg)
    {
        return Guid.TryParse(arg.ToString(), out var result);
    }
}

We can add as many rules as we want, chain validators, and even use custom validators.

  • Finally, we add FluentValidation to the application startup code:
builder.Services.AddFluentValidation(options =>
{
    options.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly());
});

All implementations of these examples are available on GitHub code , which you can download.

In this post I presented the basic architecture of CQRS and how to implement it with MediatR. But in this link, Clean Architecture .NET 6 , I’ll show you how to implement a real world CQRS example.

That’s it, for now, I’ll keep updating and adding content in this post. I hope you found it interesting😉

Download

The source code for this article can be found on  GitHub

Links of interest

MediatR

Clean Architecture .NET 6

1