Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is MediatR library overused in CQRS examples on the web?

I'm struggling to understand why so many examples on the web are using MediatR when explaining CQRS patterns, when dealing with commands and queries.

Almost everywhere I see examples where Commands and Queries are handled by MediatR but I do not see any benefit from it other than not needing to register every Command or Query in Dependency Injection Container. But then you need to implement Query objects (inheriting IRequest), Query Handlers, and Query Responses objects so that in your API controller method you can then call _mediatr.Send(queryObject).

Why not just use dependency injection to inject query object into API controller which you can call "get" methods directly on? Like:

[HttpGet]
[Route("getall")]
public async Task<IncidentQueryResult> GetAll(int page = 0, int pageSize = 25)
{
    var result = await _incidentQueries.GetIncidents(page, pageSize);
    return result;
}

instead of:

[HttpGet]
[Route("getall")]
public async Task<IncidentQueryResult> GetAll(int page = 0, int pageSize = 25)
{
    var query = new IncidentQuery(page, pageSize);
    var result = await _mediatr.Send(query);
    return result;
}

Then, inside the GetIncidents method, there's a direct sql call to database and mapping results to C# objects. Plain and simple.

For me, the perfect and only reasonable use of MediatR library is to handle Domain Events. When implementing DDD, I'm trying to set up a project in the way presented below. Each rectangle is a different project in the solution. The arrows represents references:

DDD with CQRS schema

Let's imagine a scenario: Creating a domain object needs to increment a counter stored in another domain object (a different aggregate)

  1. A request is made to API endpoint to add some new domain object to database (layer 6: Presentation)
  2. The controller method uses a command injected in it's constructor to create a domain object (layer 4: Commands)
  3. Inside the command, a new domain object is created along with "domain object created" event stored in this object, ready to be broadcasted just before saving to database
  4. Then the Command uses Repository from Infrastructure layer to add this newly created object to database.
  5. Then just before database save is performed: the "domain object created" event is sent through MediatR (layer 2: Infrastructure)
  6. The event is then caught in layer 3: Application in one of the Domain Events Handler.
  7. The domain event handler (layer 3: Application) uses Repository from Infrastructure layer to get another domain Aggregate holding a counter to be incremented and then increments the counter.
  8. All domain events were handled, save to database is performed.

So the MediatR for me works only in Infrastructure and Application layer.

Are people just using MediatR for Commands and Queries just for sake of using it? For me it looks like adding commands and queries handlers, query and command request and response types only adds more code that has no real value and only makes it less understandable.

Here are some links I visited:

  • https://referbruv.com/blog/posts/implementing-cqrs-using-mediator-in-aspnet-core-explained
  • https://www.edument.se/en/blog/post/net-5-source-generators-mediatr-cqrs
  • https://itnext.io/why-and-how-i-implemented-cqrs-and-mediator-patterns-in-a-microservice-b07034592b6d
  • https://www.rubicon-world.com/blog/2020/06/a-developers-guide-to-cqrs-using-net-core-and-mediatr/
  • https://dotnetdetail.net/cqrs-and-mediator-patterns-in-asp-net-core-3-1/

A lot of sites in my native language has this as well.

Having too many handlers used throughout your application makes difficult to read what your application does and what triggers what. What I saw was that people were handling domain events in Commands layer but the domain probably shouldn't send commands directly?

Do you need to use MediatR at all in CQRS?

like image 469
Tomasz Sikora Avatar asked Mar 11 '21 21:03

Tomasz Sikora


2 Answers

MediatR is just one library to solve a specific need. As the repository puts it:

In-process messaging with no dependencies.

Command Query Responsibility Segregation (or CQRS) is a design pattern that can be implemented in many ways. Fundamentally as long as your reads and writes are independent you are "following" this pattern.

So no, you can build an entire app that is CQRS "compliant" without using MediatR or any other messaging library. That said you can also build it as one massive file. CQRS is simply one of many tools for managing code you can deploy depending on your needs.

You can also use MediatR for in process messeging only and not apply the CQRS pattern.


That said a common reason you likely see tutorials, blogs and other .NET CQRS resources use MediatR (or a similar messaging library) is that generally any app using CQRS is also going to want the following:

  1. A way to validate at some level that queries remain queries and commands remain commands
  2. A greater degree of "separation of concerns" applied to the code as a whole.

MediatR generally solves both these problems very nicely by separating out the execution of the command to it's implementation (which can live in a separate project)

In your case for example, Presentation has to have a knowledge of the database implementation as well as it's schema in order to execute queries as well as mapping them to database resources rather then leaving that as a concern of infrastructure and infrastructure alone. In my experience this can lead to either lots of duplicate code or lots of unintended coupling between projects. Better to have the presentation layer souly focus on presentation and send a message out to whichever service (it does not care which or deal with registering them in MediatR) can give it Query information upon request.

Essentially in your diagram MediatR (and/or NServiceBus, Brighter, MassTransit, Rebus... if you need to scale beyond a single process) would act as a way to control the flow of data and detatch the client of the Query/Command from it's handler.


So finally to answer:

Are people just using MediatR for Commands and Queries just for sake of using it?

Yes and no, they are mostly using it as a separate good practice that plays very nicely together with the CQRS pattern to control their dependency flow. Although you are right that for many beginners to these ideas they can tie the two together in ways that are not required or recommended.


I recommend you take a look at his other work on Clean Code to understand how all these pieces play together to create (what he calls) a "pit of success" for future developers working on the project. He has a template repo here and several talks online about it: https://github.com/jasontaylordev/CleanArchitecture

like image 94
Menachem Hornbacher Avatar answered Oct 21 '22 13:10

Menachem Hornbacher


You are right in that MediatR library and DDD/CQRS for some reason have become synonymous. But these are mutually exclusive. You can use MediatR without Domain-Driven Design and visa-versa. The approach has become popular due to its simplicity and availability of a variety of sample projects.

One of the reasons MediatR is popular is because it takes the business and application logic outside of Controller, which, historically, are difficult to write tests for.

MediatR pattern gives you an easy way to separate these concerns. There are other alternates available as well such as Service Stack. The same concept could be also be used with Vertical Slice Architecture that aims to separate the code by features.

Similarly, while implementing Domain-Driven Design, you are free to choose what works for you. However, MediatR does give you the flexibility to keep your code extensible (Open-Close Principle). In future, if you need to replace this architecture with a real Message Bus using NServiceBus or other mechanisms you do not need to change your entire code-base.

like image 34
Ankit Vijay Avatar answered Oct 21 '22 15:10

Ankit Vijay