A Developer's Guide to CQRS Using .NET Core and MediatR
A Developer's Guide to CQRS Using .NET Core and MediatR
In this article, we discuss how to implement the CQRS pattern in a .NET Core application with MediatR for cleaner application architecture.
Join the DZone community and get the full member experience.Join For Free
“What is CQRS?” you might ask. I hope you didn’t think you were going to get a definition because that’s what Wikipedia is for. Instead, what I’m hoping to achieve with this blog post is to help you understand CQRS through some practical examples.
I will assume you’re either a beginner or someone who is unfamiliar with this topic, so maybe you get scared whenever you run into these big programming acronyms and concepts. I myself have been there, so I’m here to help you figure it out in the easiest way possible.
Before we get talking about CQRS, we need to understand a few concepts, such as Clean Architecture, for a better understanding. If you are already familiar with Clean Architecture, then feel free to skip onto the next section. If you are one of those people reading this blog who dislikes theory and just want to get your hands on the code, I encourage you to be patient and try and grasp these concepts and patterns as they will prove to be helpful in the end.
In this blog, I’ll lead you through a step-by-step tutorial so you learn both about CQRS and also end up with a beautiful project structure that you can brag to your friends about. You may also learn an additional trick or two. At the end of the blog, I’ve provided a link to the entire solution.
Before tackling the concept of CQRS let’s learn a bit about Clean Architecture.
Because combining this duo gives us a pretty nice base for further development. Clean Architecture is all about layers and boundaries and creating a clean project structure just as the name itself implies.
We can see how these layers form one solution. It is important to know that the outer layers depend upon the inner layers and not vice versa.
In a perfect world, this layer wouldn’t have any dependencies, and it would only contain entities, value objects, and maybe some Domain level custom exceptions and entity logic. This layer can be shaped by following the Domain-Driven Design guidelines. I would recommend that you explore these guidelines in-depth. Since it’s a broad subject I’ll leave it up to you.
Together with the Domain layer, the Application layer forms the Core of the solution that should be able to operate and provide business logic independently from outer layers and depend solely upon the Domain layer. It contains all of the good stuff, such as the business logic (use cases), DTO’s, interfaces, and all of the CQRS stuff that we will be discussing later.
This is the layer where all of the communication logic with the outside systems should be implemented such as sending emails, communication with 3rd party API, etc. It only depends on the Application layer. It can also contain persistence logic if it’s not overly massive and/or complex.
Compared to the Infrastructure layer, this layer also holds the logic for communication with outside systems, but its specific purpose is to communicate with databases. All of this logic can also be placed under the Infrastructure layer. This layer only depends on the Application layer.
This is the interactable layer (by the outside world) which allows clients to get visible results after requesting data. This layer can be in the form of an API, console application, GUI client application, etc. Like Persistence, it also depends only on the Application layer.
Now, since you have this quick overview of the architecture, we can move forward to explore what CQRS is all about.
Let's Dive in
Have you ever experienced having to tweak some part of the logic or models and upon finishing that task you realize that you blew up half of the app? Or have you ever had to fix some bug (created by some other developer of course) and then you go strolling through the codebase searching for some specific part of logic but it’s hard to find because it’s all spaghetti code? Or maybe the number of users on your application has drastically increased, your current machine can’t handle it anymore, the “scale up” button is greyed out because it was so long ago that you already reached a top-level machine, you think of balancing load with microservices, but you do a facepalm because you know how much effort and time it will take to refactor all of that spaghetti?
That’s what CQRS strives to solve!
CQRS stands for Command Query Responsibility Segregation and my initial thought when I was learning this for the first time was: “Well this name doesn’t help very much in understanding this” even though it does when you start understanding the concept behind the name. So the name basically is all there is: Let’s separate responsibilities of commands & queries.
Then the next question arises, “What are commands and queries?”
Well, it’s rather simple and I will use CRUD operations as an example.
CREATE, UPDATE, and DELETE are methods used to tell the system to insert, change, or remove something. As you’ve probably already figured out, you are giving out commands. While with the READ method you just want to get some data, and yes that’s a query, just like when you query the database.
Now that we have some basic idea of what CQRS should do, we come to the following question: but how can we use all of this in practice? Which then brings us to a more specific question - How do I separate these responsibilities?
That’s the next thing we are about to tackle.
CQRS in Practice
Let’s take a look at how CQRS looks in practice.
For now, let’s say we have an Application layer with the business logic separated into use cases, or rather services. Perhaps you would have a service for forum posts that would contain all of the logic regarding forum posts and may be dependent upon other services. In addition, this service could possibly be reused somewhere else.
It would look something like this:
There may be issues with this approach down the line when you need to adjust a method in some service to adhere for a second service which could break logic in some other third service where the first service is used. You’ll end up with a headache since you need to adhere to multiple cases and then figure out the way to adjust logic for all these edge cases.
Or maybe you want to separate the application into microservices until you realize how hard it will be because of the intertwined logic?
The CQRS pattern solves these problems and has many pros. Of course, nothing is perfect, so the CQRS pattern also has its cons such as not being totally DRY (Don’t Repeat Yourself) and managing it would take a bit more time for some global changes.
Now let’s see how and why.
The CQRS structure would look something like this:
As you can see, every one of these classes has only one responsibility and they aren’t reused. Every single one of them has its own models, even though they might be alike or exact copies of other models. However, programming and project architecture are subjective things so you can combine approaches by having some reusable common things. All of this separation makes it easy for us to find issues and not ruin the rest of the codebase by fiddling with something. As well, it makes it easy to extract microservices from code eventually.
Additional Nuggets Used
Check out some additional nuggets I used:
Before getting hooked on MediatR, I was really missing out. MediatR is such a handy addition to CQRS and Clean Architecture, which makes a developer’s life much easier with how everything is handled independently from each other which you will see later on in this blog.
I highly recommend checking it out since we are going to use it for our little project.
AutoMapper is a tool that makes it easy to do mapping between two objects.
No backend application is whole without Swagger. It’s documentation GUI for endpoints and all of its details which are necessary for it to be consumed.
Enough Theory Already!
Now, before we get into the code, it’s important to keep in mind that even though we could create a Domain layer and some entities, we won’t be using them in this example project.
As an alternative to creating everything from scratch, you may want to check out this repo before you start reading so you can follow along with all the examples. It’s based on .NET Core 3.1.
The first step would be to prepare the project structure by layers, as defined per Clean Architecture and then add the additional Nuggets and make sure everything is running properly.
For the theme of the project, we are going to make a CQRS backend for a forum-like app, but it’s not important because the business logic is not what matters. As well, I used the JSONPlaceholder for obtaining data as a replacement for the database. In addition, the way I decided to go is to create it as an API backend solution.
In our case, the Presentation layer is the entry point of the application and the two main entry files are Program.cs and Startup.cs. For what we are trying to learn here, we will just need to modify Startup since we define our services there, dependency injection, and request pipeline. Also, in this layer, we have controllers with the primary function to obtain some inputs and trigger our MediatR request pipelines.
In Startup, an important part is adding those dependency injection container methods from the Application and Infrastructure layer named
AddInfrastructure(). I also added the basic Swagger configurations.
The base controller has been created using new C# 8 features so that we follow the DRY principle and keep our controllers as clean as possible.
Since we created the base controller with all of its perks, we can use it to keep the rest of our controllers simple. As you can see, the traditional way would be to inject some PostsService here and to call its methods. But you can see the difference is that we only send commands or queries as objects to MediatR and it will take care of handling the rest through its pipelines.
Just like on the Infrastructure, the root of the layer contains a DI container and the rest of the folders.This is the place where the CQRS magic happens.
First, let’s talk about the Common folder. As the name itself says, it is supposed to contain some commonly used stuff. In this particular case, I used it to add helpers for AutoMapper which will help to keep our code clean and located at proper places (for rebinding models) so it would be easier to maintain later down the line.
By inheriting IMapFrom on model we get access to this Mapping function which we can then use to set our transformations.
Now for the main part, the Posts folder. The root contains the interface for PostsApi with the Commands and Queries folders.
In the next part, we will focus on querying posts.
So as you can see above, there are three main parts when using MediatR. We have a query class, query handler class, and handler method. If we were doing a CREATE method, our query class which in that case would be called command would hold all necessary input properties.
It’s defined as a MediatR class by inheriting the IRequest interface and since it’s of generic nature we have to add a response type to it. If your method is void, then don’t add any return type, but the return type of your handle method would have to be of Task<Unit> type.
Handler class is defined by inheriting IRequestHandler<queryClass returnType> and if you return void just omit returnType.
And finally, we have a handler method which holds the business logic and if it’s supposed to return void it’s signature return type should be defined as Task<Unit> as said previously and it should be returning Unit.Value.
Here we have defined a class that will be serialized as a response from a 3rd party API. We also added data annotations to serve as a guide for System.Text.Json on how it should bind properties.
And finally, we see a DTO model that will serve as a view model and in it, we can see the example of using auto mapping from response class.
I have created a typed base HttpClient which is inherited by a specific client made for JSONPlaceholder API and did an abstraction of forum posts logic as PostsApi.cs. All of it is added through a DI container which is located on the root of the layer. I tried to keep it as simple as possible while using some best practices. You may wonder, "Why are there so many abstractions for the HTTP client?" The answer is simple - I’m a .NET developer and I love my abstractions. It also makes code cleaner and more understanding about what it's doing.
Now that we have implemented a generic base HttpClient which can be further extended with more methods, I will leave it up to you to make the decision of whether you’d like to further extend it or not. The reason why I have decided to do it this way is because of making it simpler to use, making it reusable, and keeping it DRY.
After the implementation of base HttpClient, we can use it for specific tasks like in our case communicating with JsonPlaceholder API for obtaining a list of posts. Again, since we created the base HttpClient the way we did we can create more of these specific type clients for communication with more than one 3rd party API.
Here we have one more abstraction on our way to the HttpClient. Why? Why not combine JsonPlaceholderClient and PostsApi into one class?
The reason is the instancing of HttpClient primarily and separation. Most of us are used to repository patterns so by somewhat following them we know what this class is about.
If you want to learn more about issues with instancing HttpClient, I suggest you read this.
The entire project can be found here.
After reading this blog, I hope that you’ve gained a better understanding of CQRS and are excited to take on new challenges. Personally, for me, CQRS is the way to go and there is no better way to organize your project, subjectively speaking, until the next new big programming acronym comes around to do bigger and better things. Thank you for your patience for going through this with me and I wish you good luck and happy coding!
You can find the original blog source here.
Published at DZone with permission of Faris Karcic . See the original article here.
Opinions expressed by DZone contributors are their own.