microservice-with-cqrs

Microservice with CQRS in ASP.NET Core

What is CQRS?

CQRS (Command Query Responsibility Segregation) is an architectural pattern that separates the read and writes operations of a data source. Here Query refers to querying data from a source and Command refers to database command that can be either Insert/Update or Delete operations.

So the main idea with CQRS is to allow an application to work with different models. In traditional architectures, the same data model is used to query and update data sources. But with CQRS you can have different data models for update/insert and query which provides flexibility for complex scenarios. Even in more complex applications like microservices or any kind of application that has high demand of data consumption, in that case the traditional architecture doesn’t fit well because you will stick with the same data source and having much writing and reading in the same data source can affect the performance of the system. In that scenario, we can think about CQRS because it gives us the flexibility to use a separate data source that ensures the scalability of our application.

Advantages of CQRS

  1. Independent scaling
  2. Optimized data transfer objects
  3. Provides separation of concern
  4. High scalability

Disadvantages of CQRS

  1. More complexity especially in bigger systems because often you have reads which also update some data, for example, a user logs in (read) and you want to store its IP and time of login (write)
  2. Eventual consistency  when using a database for writing and one for reading. The read database needs to be synchronized to hold the new data. This could take a while.
  3. Not applicable in all projects: CQRS brings some complexity to your system and especially simple applications that do only basic CRUD operations shouldn’t use CQRS.

Implementation of the CQRS in our ProductMicroservice

Here for the Implementation of the CQRS, we will use the MediatR Library that helps the implementation of Mediator Pattern in .NET. The mediator is a behavioral pattern that let us reduce dependencies between objects by restricting direct communications between the objects and forces them to collaborate only via a mediator object.

Open the solution: ProductMicroservice-DotNET-Core-Master and in project: ProductMicroservice

add a new folder: CQRS and under this add two folders: Commands  and Queries.

in this project install install the MediatR , AutoMapper and  MediatR.Extension.Microsoft.DependencyInjection  from NuGet Package Manager. as we will add the dependency of the MediatR  and AutoMapper in the startup class of the this project

the NuGet Package Manager.

CQRS-in-microservices-2.png
Installing Mediator via NuGet Package Manager.

Now let’s do the CreateProductCommand operation which is an insert operation so this will be our command. so in the Commands folder create a class CreateProductCommand.cs file.

In the Commands folder create Interface class: CreateProductCommand.cs as following:

using MediatR;
using ProductMicroservice.Models;
namespace ProductMicroservice.CQRS.Commands
{
public class CreateProductCommand : IRequest<Product>
{
public Product Product { get; set; }
}
}

As this is also a request so it will implement the IRequest interface. And for this command, we also have to create a handler. The concrete class CreateProductCommandHandler as following:

using MediatR;
using ProductMicroservice.Models;
using ProductMicroservice.Repository;
using System.Threading;
using System.Threading.Tasks;
namespace ProductMicroservice.CQRS.Commands
{
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Product>
{
private readonly IProductRepository _ProductRepository;
public CreateProductCommandHandler(IProductRepository productRepository)
{
_ProductRepository = productRepository;
}
public async Task<Product> Handle(CreateProductCommand request, CancellationToken cancellationToken)
{
return await Task.FromResult(_ProductRepository.InsertProduct(request.product));
}
}
}

As we see in the code above, CreateProductCommandHandler is calling Product repository to access the data ( database) which is an insert to the database. now we should change the code in the Conroller that can call to the CreateProductCommand  then it shall be a Madiator (CQRS  plays as a midiator roll here) and that is because the code for Post in controller should be changed as following:

[HttpPost]
public async Task<ActionResult<Product>> Post([FromBody] Product product)
{
try
{
var prod = new CreateProductCommand
{
product = _mapper.Map<Product>(product)
};
return await _mediator.Send(prod);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}

In the Queries folder create GetProductLisQuery  Interface as following:

using System;
using System.Collections.Generic;
using MediatR;
using ProductMicroservice.Models;
namespace ProductMicroservice.CQRS.Queries
{
public class GetProductLisQuery : IRequest<IEnumerable<Product>>
{
}
}

And a concrete class to the above Interface as a Handeller GetProductByIdQueryHandler as following:

using System;
using System.Threading.Tasks;
using ProductMicroservice.Repository;
using ProductMicroservice.Models;
using System.Threading;
using MediatR;
using System.Collections.Generic;
namespace ProductMicroservice.CQRS.Queries
{
public class GetProductListQueryHandler : IRequestHandler<GetProductLisQuery, IEnumerable<Product>>
{
private readonly IProductRepository _productRepository;
public GetProductListQueryHandler(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public async Task<IEnumerable<Product>> Handle(GetProductLisQuery request, CancellationToken cancellationToken)
{
return await Task.FromResult( _productRepository.GetProducts());
}
}
}

Here the handler must implement the IRequestHandler interface where we need to pass two generics the first one is the request name that the handler will process (for our example as we will handle GetProductLisQuery so we are passing this) and in the second generic we will pass the output of the request(as this request will return a product.

As a result we should change the Get() action in the controller should be changed to call the CQRS handeller ( GetProductListQueryHandler)

The code for this Get() Action in the Controller should be as folling:

[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> Get()
{
try
{
var products = await _mediator.Send(new GetProductLisQuery());
return new OkObjectResult(products);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}

I have created CQRS Handeller  for all functions in the Controller and change all  action methods in the Controller to call CQRS handeller methods.

Adding dependency

Now in the MicroserviceProject inside the startup.cs at ConfigureServices() method register the services for MediatR. and AutoMapper as following:

services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddAutoMapper(typeof(Startup));

I have modified small changes in the Repository to adapt to the CQRS.

All Completed code can be find in the GitHub.

Conclusion

In this post we gone through  overview of CQRS and how it can be used to separate the read and write operations and how to implement it in the ASP.net Core in  application. In my ProductMicroservice I have implemented CQRS for all  the Action methods in the Controller.

In my Next post  I will describe Mediator Pattern in ASP.NET Core

This post is part of Microservices-Step by step”.

Back to home page