Using Mediator In Web API's For CQRS Pattern

In my last article, I shared topics about Clean architecture and CQRS patterns. 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 a 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, the Mediator is your savior. It helps to make your code loosely coupled. The word itself says that it acts as a Mediator between the objects such that it reduces the dependencies among the objects you want to manage. The mediator controls the communication between objects.

 Mediator

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 a web API controller.

 Web API

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 that should define our application layer such that it depends only on the 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.

Solution Explorer

Once your architecture is set up, now let's install the required nuggets for the MediatR pattern.

Installing Packages

Step 1. The first thing we need to do is install the MediatR nugget from the package manager console. Open the 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 the .Net core.

Install-Package MediatR.Extensions.Microsoft.DependencyInjection

After installing the packages, we need to configure the service in our Startup.cs file.

using Domain;
using Domain.Repositories;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using NetCoreLearnings.Application.QueryHandlers;
using System;
using System.Reflection;

namespace NetCoreLearnings.Controllers.API
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly);
        }
    }
}

Creating Commands/Queries and their respective Handlers

In the first step, I will be creating a query to get the list of customers for example. So the query will return all the properties mentioned in the object.

using Domain.DomainDTO;
using MediatR;
using System;
using System.Collections.Generic;

namespace NetCoreLearnings.Application.Queries
{
    public class GetCustomersQuery : IRequest<List<CustomerDto>>
    {
        public Guid Id { get; }
        public string CustomerName { get; }
        public string Country { get; }
        public DateTime JoiningDate { get; }
        public bool PrimeUser { get; }

        public GetCustomersQuery() { }
    }
}

In the next step, let us create a handler for this query called query handler. This query handler will handle the logic for the operation.

using Domain.DomainDTO;
using Domain.Repositories;
using MediatR;
using NetCoreLearnings.Application.Queries;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace NetCoreLearnings.Application.QueryHandlers
{
    public class GetCustomerQueryHandlers : IRequestHandler<GetCustomersQuery, List<CustomerDto>>
    {
        private readonly ICustomerRepository _repo;

        public GetCustomerQueryHandlers(ICustomerRepository repo)
        {
            _repo = repo;
        }

        public Task<List<CustomerDto>> Handle(GetCustomersQuery request, CancellationToken cancellationToken)
        {
            var customers = _repo.GetAll();
            return Task.FromResult(customers.Select(i => new CustomerDto()
            {
                Id = i.Id,
                CustomerName = i.Name,
                JoiningDate = i.JoiningDate,
                PrimeUser = i.PrimeUser
            }).ToList());
        }
    }
}

This handle function will return the list of customers after querying the data from the 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 Endpoint. Inject the MediatR into our controller by which we will be sending the queries/commands to our application layer.

using Domain.DomainDTO;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NetCoreLearnings.Application.Queries;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace NetCoreLearnings.Controllers.API.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class CustomerController : ControllerBase
    {
        private readonly IMediator _mediator;
        private readonly ILogger<CustomerController> _logger;

        public CustomerController(IMediator mediator, ILogger<CustomerController> logger)
        {
            _mediator = mediator;
            _logger = logger;
        }

        [HttpGet]
        [ProducesResponseType(typeof(List<CustomerDto>), StatusCodes.Status200OK)]
        public async Task<IActionResult> Get()
        {
            var clients = await _mediator.Send(new GetCustomersQuery());
            return Ok(clients);
        }
    }
}

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.

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


Similar Articles