CQRS and MediatR Pattern

Nourhaidarahmad
5 min readDec 22, 2023

--

CQRS is an architectural pattern for software development ,stands for Command and Query Responsibility Segregation.

In CQRS, data-read operations are separated from data-write or update operations. It is a pattern used to separate the logic between commands and queries.

What are the keywords?

1-Queries = GET methods.

Queries just return a state and do not change it.

2-Commands = POST/PUT/DELETE methods.

Commands only change the state.

Benefits of CQRS:

· Improved Read Model Clarity:

Easily organize and access queries and domain objects for a clearer understanding.

· Efficient Scalability:

Optimize read and write operations independently for better system scalability.

· Simplified Logic and Roles:

Clarify code by separating command and query roles, making development more straightforward.

Why Using CQRS:

The main objective of CQRS is to allow an application to work correctly using different data models, offering flexibility in scenarios that require a complex model. You have the possibility to create multiple DTOs without breaking any architectural pattern or losing any data in the process.

Creating the Project

1-Create the project in visual studio :

Searching for the ASP.NET Core Web API then click next.

2.Project Dependencies

You can add the package you need from NuGet package manager or via the manager console.

(Needed Packages:

Microsoft.EntityFrameworkCore.SqlServer.

Microsoft.EntityFrameworkCore.Tools).

3. Creating the Model Entity

Add a new folder called “Models” and inside it add the following class:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ProductCatalog.Models;
public class Product
{
public int Id { get; set; }
[StringLength(80, MinimumLength = 4)]
public string Name { get; set; }
[StringLength(80, MinimumLength = 4)]
public string Description { get; set; }
[StringLength(80, MinimumLength = 4)]
public string Category { get; set; }
public bool Active { get; set; } = true; [Column(TypeName = "decimal(10,2)")]
public decimal Price { get; set; }
}

1. Creating the Database Context

Add a new folder called “Data” and inside it add the following class:

using Microsoft.EntityFrameworkCore;
using ProductCatalog.Models;
namespace ProductCatalog.Data;
public class ApplicationDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
}

5. Configure The context class

in the Program.cs, add the following code

builder.Services.AddDbContext<ApplicationDbContext>(
options => options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection")
));

6.Adding Default Connection

"ConnectionStrings": {
"DefaultConnection":
"Server=localhost;
Database=ProductCatalog;
Trusted_Connection=True;
MultipleActiveResultSets=true;
Integrated Security=True;
Encrypt=False"
}

7.Add Migrations And Update Database

In the Package Manager Console write this commands :

Add-Migration AddProductModel

Update-Database

So Now that the base application and the database are ready, we can apply the CQRS pattern to implement the CRUD methods, separating the query from the persistence.

But to help with this implementation, there is a very important feature called mediator.

The Mediator Pattern

The mediator pattern uses a very simple concept that perfectly fulfills its role: Provide a mediator class to coordinate the interactions between different objects and thus reduce the coupling and dependency between them. In short, mediator makes a bridge between different objects, which eliminates the dependency between them as they do not communicate directly.

Benefits of Mediator:

  • Independence between different objects
  • Centralized communication
  • Easy maintenance

8.Adding Mediator Package to the project.

From NuGet package management install this 2 packages:

MediatR
MediatR.Extensions.Microsoft.DependencyInjection

9.Creating a Folder Named Resources

10.Creating 4 Folders :

Queries-QureyHandler-Command-CommandHandler

11.Creating The Queries

CQRS is used through the implementation of the query pattern composed of two objects:

composed of two objects:

· Query — Defines the objects to be returned.

· Query Handler — Responsible for returning objects defined by the class that implements the query pattern.

GetProductById

Inside the folder Queries create the following class :

using MediatR;
using ProductCatalog.Models;
namespace ProductCatalog.Resources
{
namespace Resources.Query
{
public record GetProductByIdQuery(int productId) : IRequest<Product>;
}
}

Here we define a class that returns a Product object. Through it, we send a request to the mediator that will execute the query.

The next class will execute the query and return the product, so inside the QueryHandler folder adds the class below:

using MediatR;
using Microsoft.EntityFrameworkCore;
using ProductCatalog.Data;
using ProductCatalog.Models;
using ProductCatalog.Resources.Resources.Query;
namespace ProductCatalog.Resources.QueryHandler
{
public class GetProductByIdQueryHandler : IRequestHandler<GetProductByIdQuery, Product>
{
private readonly ApplicationDbContext _context;
public GetProductByIdQueryHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<Product> Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
{
var p= await _context.Products.FindAsync( request.productId);
return p;
}
}
}

12.Creating Command

CQRS is used through the implementation of the command pattern composed of two objects:

· Command — Defines which methods should be executed.

· Command Handler — Responsible for executing the methods defined by the Command classes.

The commands will execute the Create/Update/Delete persistence methods.

All command classes implement the IRequest<T> interface, where the type of data to be returned is specified. This way, MediatR knows which ver object is invoked during a request.

So, inside the “Commands” folder, create the class below:

using MediatR;
using ProductCatalog.Models;
namespace ProductCatalog.Resources.Command
{
public record AddProductCommand(Product product) : IRequest<bool>;
}

After , Inside The “CommandHandler” Folder add the class below:

using MediatR;
using ProductCatalog.Data;
using ProductCatalog.Models;
using ProductCatalog.Resources.Command;
using System.Xml.Linq;
namespace ProductCatalog.Resources.CommandHandler
{
public class AddProductCommandHandler : IRequestHandler<AddProductCommand, bool>
{
private readonly ApplicationDbContext _context;
public AddProductCommandHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<bool> Handle(AddProductCommand request, CancellationToken cancellationToken)
{
try
{
await _context.Products.AddAsync(new Product()
{
Name = request.product.Name,
Description = request.product.Description,
Category = request.product.Category,
Price = request.product.Price,
Active = request.product.Active,
});
_context.SaveChanges();
return true;
}
catch (Exception ex)
{
return false;
}
}
}
}

13.Configuring the program.cs class

In the Program.cs class, add the following code line:

builder.Services.AddMediatR(Assembly.GetExecutingAssembly());

14. Add The controller


using Microsoft.AspNetCore.Mvc;

using MediatR;

using ProductCatalog.Resources.Command;
using ProductCatalog.Resources.Resources.Query;
using ProductCatalog.Models;

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

public ProductController(IMediator mediator)
{
_mediator = mediator;
}

[HttpGet]
[Route("GetProductById/{productId}")]
public async Task<Product> GetProductById(int productId)
{
try
{
var query = new GetProductByIdQuery(productId);
var response = await _mediator.Send(query);
return response;
}
catch (Exception ex)
{
return new Product();
}
}

[HttpPost]
[Route("AddProduct")]
public async Task<IActionResult> AddProduct([FromForm] Product product)
{
try
{
var addProductCommand = new AddProductCommand(product);
var addProductCommandResponse = await _mediator.Send(addProductCommand);


return Ok("Product Added successfully");

}
catch (Exception ex)
{
return StatusCode(500, $"Internal server error: {ex.Message}");
}
}
}
}

15.Run the Project

Conclusion:

CQRS is a development standard that brings many advantages, such as the possibility for separate teams to work on the read and persistence layer and also to be able to scale database resources as needed.

--

--