DEV Community

Cover image for A Developer's Guide to CQRS Using .NET Core and MediatR
RUBICON
RUBICON

Posted on

A Developer's Guide to CQRS Using .NET Core and MediatR

“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.

Clean Architecture

Before tackling the concept of CQRS let’s learn a bit about Clean Architecture.

Why?

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.

Alt Text

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.

Domain

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 and since it’s a broad subject I’ll leave it up to you.

Application

Together with the Domain layer, the Application layer forms up a Core of the solution which 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.

Infrastructure

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.

Persistence

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.

Presentation

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 a load of users on your application has drastically increased and your current machine can’t handle it anymore and the “scale up” button is greyed out because it was so long ago that you already reached a top-level machine and then 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!

Alt Text

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:

Alt Text

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:

Alt Text

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:

MediatR

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

AutoMapper is a tool that makes it easy to do mapping between two objects.

Swashbuckle Swagger

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.

Presentation

Necessary Nuggets:

  • Swashbuckle.AspNetCore

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.

Startup.cs

using Application;
using Infrastructure;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;

namespace Presentation
{
   public class Startup
   {
       public Startup(IConfiguration configuration)
       {
           Configuration = configuration;
       }

       private IConfiguration Configuration { get; }

       public void ConfigureServices(IServiceCollection services)
       {
           services.AddApplication();
           services.AddInfrastructure(Configuration);
           services.AddControllers();

           services.AddSwaggerGen(config =>
           {
               config.SwaggerDoc("v1", new OpenApiInfo() {Title = "CQRS Forum", Version = "v1"});
           });

           // Make routes globally lowercase.
           services.AddRouting(options =>
           {
               options.LowercaseUrls = true;
               options.LowercaseQueryStrings = true;
           });
       }

       public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
       {
           if (env.IsDevelopment())
           {
               app.UseDeveloperExceptionPage();
           }

           app.UseHttpsRedirection();

           app.UseRouting();

           app.UseAuthorization();

           app.UseEndpoints(endpoints => { endpoints.MapControllers(); });

           app.UseSwagger();

           app.UseSwaggerUI(config => config.SwaggerEndpoint("/swagger/v1/swagger.json", "CQRS Forum v1"));
       }
   }
}
Enter fullscreen mode Exit fullscreen mode

In Startup, an important part is adding those dependency injection container methods from the Application and Infrastructure layer named AddApplication() and AddInfrastructure(). I also added the basic Swagger configurations.

Controllers/BaseController.cs

using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

namespace Presentation.Controllers
{
   [ApiController]
   [Route("api/[controller]")]
   public class BaseController : ControllerBase
   {
       private IMediator _mediator;

       protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService<IMediator>();
   }
}
Enter fullscreen mode Exit fullscreen mode

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.

Controllers/PostsController.cs

using System.Collections.Generic;
using System.Threading.Tasks;
using Application.Posts.Commands.CreatePost;
using Application.Posts.Queries.GetAllPosts;
using Microsoft.AspNetCore.Mvc;

namespace Presentation.Controllers
{
   public class PostsController : BaseController
   {
       [HttpGet]
       public async Task<ActionResult<IEnumerable<GetAllPostsDto>>> GetAllPosts()
       {
           var response = await Mediator.Send(new GetAllPostsQuery());

           return Ok(response);
       }

       [HttpPost]
       public async Task<ActionResult<CreatePostDto>> CreatePost(CreatePostCommand command)
       {
           var response = await Mediator.Send(command);

           return CreatedAtAction(nameof(CreatePost), response);
       }
   }
}
Enter fullscreen mode Exit fullscreen mode

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.

Application

Necessary Nuggets:

  • AutoMapper
  • AutoMapper.Extensions.Microsoft.DependencyInjection
  • MediatR
  • MediatR.Extensions.DependencyInjection

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.

Common/Mappings/IMapFrom.cs

using AutoMapper;

namespace Application.Common.Mappings
{
   public interface IMapFrom<T>
   {
       void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
   }
}
Enter fullscreen mode Exit fullscreen mode

By inheriting IMapFrom on model we get access to this Mapping function which we can then use to set our transformations.

Common/Mappings/MappingProfile.cs

using System;
using System.Linq;
using System.Reflection;
using AutoMapper;

namespace Application.Common.Mappings
{
   public class MappingProfile : Profile
   {
       public MappingProfile()
       {
           ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
       }

       private void ApplyMappingsFromAssembly(Assembly assembly)
       {
           var types = assembly.GetExportedTypes()
               .Where(t => t.GetInterfaces().Any(i =>
                   i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
               .ToList();

           foreach (var type in types)
           {
               var instance = Activator.CreateInstance(type);
               var methodInfo = type.GetMethod("Mapping");
               methodInfo?.Invoke(instance, new object[] { this });
           }
       }
   }
}
Enter fullscreen mode Exit fullscreen mode

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.

Posts/IPostsApi.cs

using System.Collections.Generic;
using System.Threading.Tasks;
using Application.Posts.Commands.CreatePost;
using Application.Posts.Queries.GetAllPosts;

namespace Application.Posts
{
   public interface IPostsApi
   {
       Task<IEnumerable<GetAllPostsResponse>> GetAllPosts();
       Task<CreatePostResponse> CreatePost(CreatePostRequest request);
   }
}
Enter fullscreen mode Exit fullscreen mode

Posts/Queries/GetAllPosts/GetAllPostsQuery.cs

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using MediatR;

namespace Application.Posts.Queries.GetAllPosts
{
   public class GetAllPostsQuery : IRequest<IEnumerable<GetAllPostsVm>>
   {
       public class GetAllPostsQueryHandler : IRequestHandler<GetAllPostsQuery, IEnumerable<GetAllPostsVm>>
       {
           private readonly IPostsApi _postsApi;
           private readonly IMapper _mapper;

           public GetAllPostsQueryHandler(IPostsApi postsApi, IMapper mapper)
           {
               _postsApi = postsApi;
               _mapper = mapper;
           }

           public async Task<IEnumerable<GetAllPostsVm>> Handle(GetAllPostsQuery request, CancellationToken cancellationToken)
           {
               var posts = await _postsApi.GetAllPosts();
               return _mapper.Map<IEnumerable<GetAllPostsVm>>(posts);
           }
       }
   }
}
Enter fullscreen mode Exit fullscreen mode

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 CREATEmethod, 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 type.

Handler class is defined by inheriting IRequestHandler 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 as said previously and it should be returning Unit.Value.

Posts/Queries/GetAllPosts/GetAllPostsResponse.cs

using System.Text.Json.Serialization;

namespace Application.Posts.Queries.GetAllPosts
{
   public class GetAllPostsResponse
   {
       [JsonPropertyName("id")]
       public int Id { get; set; }
       [JsonPropertyName("title")]
       public string Title { get; set; }
       [JsonPropertyName("body")]
       public string Body { get; set; }
       [JsonPropertyName("userId")]
       public int UserId { get; set; }
   }
}
Enter fullscreen mode Exit fullscreen mode

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.

Posts/Queries/GetAllPosts/GetAllPostsDto.cs

using Application.Common.Mappings;
using AutoMapper;

namespace Application.Posts.Queries.GetAllPosts
{
   public class GetAllPostsDto : IMapFrom<GetAllPostsResponse>
   {
       public int UserId { get; set; }
       public int Id { get; set; }
       public string Title { get; set; }
       public string Body { get; set; }

       public void Mapping(Profile profile)
       {
           profile.CreateMap<GetAllPostsResponse, GetAllPostsDto>();
       }
   }
}
Enter fullscreen mode Exit fullscreen mode

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.

Infrastructure

Necessary Nuggets:

  • Microsoft.Extensions.Configuration
  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.DependencyInjection.Abstractions
  • Microsoft.Extensions.Http

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.

BaseHttpClient.cs

using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace Infrastructure
{
   public class BaseHttpClient
   {
       private readonly HttpClient _httpClient;

       protected BaseHttpClient(HttpClient httpClient)
       {
           _httpClient = httpClient;
       }

       protected async Task<T> Get<T>(string uri)
       {
           var request = CreateRequest(HttpMethod.Get, uri);

           return await ExecuteRequest<T>(request);
       }

       protected async Task<T> Post<T>(string uri, object content)
       {
           var request = CreateRequest(HttpMethod.Post, uri, content);

           return await ExecuteRequest<T>(request);
       }

       private static HttpRequestMessage CreateRequest(HttpMethod httpMethod, string uri, object content = null)
       {
           var request = new HttpRequestMessage(httpMethod, uri);
           if (content == null) return request;

           // Serialize content
           var json = JsonSerializer.Serialize(content);
           request.Content = new StringContent(json, Encoding.UTF8, "application/json");

           return request;
       }

       private async Task<T> ExecuteRequest<T>(HttpRequestMessage request)
       {
           try
           {
               var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
                   .ConfigureAwait(false);
               var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

               response.EnsureSuccessStatusCode();

               return string.IsNullOrEmpty(responseContent) ? default : JsonSerializer.Deserialize<T>(responseContent);
           }
           catch (Exception ex) when (ex is ArgumentNullException ||
                                      ex is InvalidOperationException ||
                                      ex is HttpRequestException ||
                                      ex is JsonException)
           {
               throw new Exception("HttpClient exception", ex);
           }
       }
   }
}
Enter fullscreen mode Exit fullscreen mode

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.

JsonPlaceholderApi/JsonPlaceholderClient.cs

using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Application.Posts.Commands.CreatePost;
using Application.Posts.Queries.GetAllPosts;

namespace Infrastructure.JsonPlaceholderApi
{
   public class JsonPlaceholderClient : BaseHttpClient
   {
       public JsonPlaceholderClient(HttpClient httpClient) : base(httpClient)
       {

       }

       public async Task<IEnumerable<GetAllPostsResponse>> GetAllPosts()
       {
           return await Get<IEnumerable<GetAllPostsResponse>>(Endpoints.Posts.GetAllPosts);
       }

       public async Task<CreatePostResponse> CreatePost(CreatePostRequest request)
       {
           return await Post<CreatePostResponse>(Endpoints.Posts.CreatePost, request);
       }
   }
}
Enter fullscreen mode Exit fullscreen mode

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.

JsonPlaceholderApi/PostsApi.cs

using System.Collections.Generic;
using System.Threading.Tasks;
using Application.Posts;
using Application.Posts.Commands.CreatePost;
using Application.Posts.Queries.GetAllPosts;

namespace Infrastructure.JsonPlaceholderApi
{
   public class PostsApi : IPostsApi
   {
       private readonly JsonPlaceholderClient _client;

       public PostsApi(JsonPlaceholderClient client)
       {
           _client = client;
       }

       public async Task<IEnumerable<GetAllPostsResponse>> GetAllPosts()
       {
           return await _client.GetAllPosts();
       }

       public async Task<CreatePostResponse> CreatePost(CreatePostRequest request)
       {
           return await _client.CreatePost(request);
       }
   }
}
Enter fullscreen mode Exit fullscreen mode

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.

DependencyInjection.cs

using System;
using Application.Posts;
using Infrastructure.JsonPlaceholderApi;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Infrastructure
{
   public static class DependencyInjection
   {
       public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
       {
           services.AddHttpClient<JsonPlaceholderClient>("JsonPlaceholderClient", config =>
           {
               config.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
               config.Timeout = TimeSpan.FromSeconds(30);
           });

           services.AddTransient<IPostsApi, PostsApi>();

           return services;
       }
   }
}
Enter fullscreen mode Exit fullscreen mode

GitHub Codebase

The entire project can be found HERE.

Conclusion

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!


Original blog post: A Developer's Guide to CQRS Using .NET Core and MediatR

Top comments (1)

Collapse
 
sanghyeon profile image
Damien

What do you think about this?
github.com/sang-hyeon/Plastic