Using Mediator In Web API's For CQRS Pattern

In my last article, I shared topics about Clean architecture and CQRS pattern. Here is the link for my previous article.
Now let us see how the mediator helps in achieving clean architecture and how the CQRS pattern can be achieved.

When is Mediator useful?

If your application has many objects which are communicating with other other it's very useful. So if the number of objects grows the number of references of the objects increases, and hence it will be difficult to maintain the code. So in short, when your code is tightly coupled and any change in single object will be break your system. In this picture, Mediator is your savior. It helps to make your code loosely coupled. The word itself says that it acts as Mediator between the objects such that it reduces the dependencies among the objects you want to manage. The mediator controls the communication between objects.
The other benefit of using the mediator design pattern is that it improves code readability and maintainability. This will keep you away from spaghetti code.
Now let’s see how we can use this MediatR in our web API’s,
Let’s first create a project with web API controller.
Once the project is created, let's create the heart of the application, the Domain project. Create all the entities , dbcontext and repositories in this project.
Now create one more project which should define our application layer such that it depends only on domain layer. On top of the application layer project create a web api project and this project depends only on our application layer project. Your solution should look like this.
Once your architecture is set up, now let's install the required nugets for MediatR pattern.

Installing Packages

Step 1
The first thing we need to do is install the MediatR nuget from package manager console. Open Package Manager console and execute the below command,
Install-Package MediatR
Step 2
Once the MediatR is installed, we need to install the package for using the inbuilt IOC container in .Net core.
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
After installing the packages, we need to configure the service in our Startup.cs file.
  1. using Domain;  
  2. using Domain.Repositories;  
  3. using MediatR;  
  4. using Microsoft.AspNetCore.Builder;  
  5. using Microsoft.AspNetCore.Hosting;  
  6. using Microsoft.EntityFrameworkCore;  
  7. using Microsoft.Extensions.Configuration;  
  8. using Microsoft.Extensions.DependencyInjection;  
  9. using Microsoft.Extensions.Hosting;  
  10. using Microsoft.OpenApi.Models;  
  11. using NetCoreLearnings.Application.QueryHandlers;  
  12. using System;  
  13. using System.Reflection;  
  14. namespace NetCoreLearnings.Controllers.API {  
  15.     public class Startup {  
  16.         public Startup(IConfiguration configuration) {  
  17.             Configuration = configuration;  
  18.         }  
  19.         public IConfiguration Configuration {  
  20.             get;  
  21.         }  
  22.         // This method gets called by the runtime. Use this method to add services to the container.  
  23.         public void ConfigureServices(IServiceCollection services) {  
  24.             services.AddControllers();  
  25.             services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly);  
  26.         }  
  27.     }  
  28. }   
Creating Command/Queries and their respective Handlers
In the first step I will be creating a query to get the list of customers for an example. So the query will return all the properties mentioned in the object.
  1. using Domain.DomainDTO;  
  2. using MediatR;  
  3. using System;  
  4. using System.Collections.Generic;  
  5. namespace NetCoreLearnings.Application.Queries {  
  6.     public class GetCustomersQuery: IRequest < List < CustomerDto >> {  
  7.         public Guid Id {  
  8.             get;  
  9.         }  
  10.         public string CustomerName {  
  11.             get;  
  12.         }  
  13.         public string Country {  
  14.             get;  
  15.         }  
  16.         public DateTime JoiningDate {  
  17.             get;  
  18.         }  
  19.         public bool PrimeUser {  
  20.             get;  
  21.         }  
  22.         public GetCustomersQuery() {}  
  23.     }  
  24. }   
In the next step, let us create a handler for this query called as query handler. This query handler will handle the logic for the operation.
  1. using Domain.DomainDTO;  
  2. using Domain.Repositories;  
  3. using MediatR;  
  4. using NetCoreLearnings.Application.Queries;  
  5. using System;  
  6. using System.Collections.Generic;  
  7. using System.Linq;  
  8. using System.Text;  
  9. using System.Threading;  
  10. using System.Threading.Tasks;  
  11. namespace NetCoreLearnings.Application.QueryHandlers {  
  12.     public class GetCustomerQueryHandlers: IRequestHandler < GetCustomersQuery, List < CustomerDto >> {  
  13.         private readonly ICustomerRepository _repo;  
  14.         public GetCustomerQueryHandlers(ICustomerRepository repo) {  
  15.             _repo = repo;  
  16.         }  
  17.         public Task < List < CustomerDto >> Handle(GetCustomersQuery request, CancellationToken cancellationToken) {  
  18.             var customers = _repo.GetAll();  
  19.             return Task.FromResult(customers.Select(i => new CustomerDto() {  
  20.                 Id = i.Id,  
  21.                     CustomerName = i.Name,  
  22.                     JoiningDate = i.JoiningDate,  
  23.                     PrimeUser = i.PrimeUser  
  24.             }).ToList());  
  25.         }  
  26.     }  
  27. }   
This handle function will return the list of customers after querying the data from database.
So we have our handlers ready in our application layer. Let's create an API and see how our API can communicate with the handler with MediatR.
Creating Web API
Let's create a web api controller with a GET API End point. Inject the MediatR into our controller by which we will be sending the queries/commands to our application layer.
  1. using Domain.DomainDTO;  
  2. using MediatR;  
  3. using Microsoft.AspNetCore.Http;  
  4. using Microsoft.AspNetCore.Mvc;  
  5. using Microsoft.Extensions.Logging;  
  6. using NetCoreLearnings.Application.Queries;  
  7. using System.Collections.Generic;  
  8. using System.Threading.Tasks;  
  9. namespace NetCoreLearnings.Controllers.API.Controllers {  
  10.     [ApiController]  
  11.     [Route("[controller]")]  
  12.     public class CustomerController: ControllerBase {  
  13.         private readonly IMediator _mediator;  
  14.         private readonly ILogger < CustomerController > _logger;  
  15.         public CustomerController(IMediator mediator, ILogger < CustomerController > logger) {  
  16.                 _mediator = mediator;  
  17.                 _logger = logger;  
  18.             }  
  19.             [HttpGet]  
  20.             [ProducesResponseType(typeof(List < CustomerDto > ), StatusCodes.Status200OK)]  
  21.         public async Task < IActionResult > Get() {  
  22.             var clients = await _mediator.Send(new GetCustomersQuery());  
  23.             return Ok(clients);  
  24.         }  
  25.     }  
  26. }   
If we see this below line of code, the mediator is responsible for sending the GetCustomersQuery and this is how it interacts with the other object.
  1. var clients = await _mediator.Send(new GetCustomersQuery());   
And now in this scenario, the API doesn’t depend upon the application layer and the main object of API is to send the command and receive the command, but it won’t depend on other objects. In this way the API and application layer is loosely coupled.
So even if you create unit test cases, there will be two unit tests. One for the API to check if the mediator sent the command or query to object or not successfully and one to check if the required command or query has returned the object or not. The other unit test is for the command or query handler to process the logic for the operation requested.
This way the MediatR nuget package helps in achieving the CQRS pattern and clean architecture.