Command Query Responsibility Segregation (CQRS) pattern
Mohd Saeed
Technical Lead @ Capital Numbers | Application Developer | .Net Core | C# | Web API | Entity Framework Core | Type Script | JavaScript | SQL | GIT | Docker
In software architecture, achieving scalability, maintainability, and performance is essential. One pattern that has gained significant traction in achieving these goals is the Command Query Responsibility Segregation (CQRS) pattern. In this article, we will explore what CQRS is, why it’s beneficial, and how to implement it in a .NET Core application with multiple examples.
What is CQRS?
CQRS stands for Command Query Responsibility Segregation. It is a pattern that separates read and write operations in a system. The main idea is to use different models for updating data (commands) and reading data (queries). This separation can provide several benefits:
Why Use CQRS?
Implementing CQRS in .NET Core
Let's dive into how to implement CQRS in a .NET Core application with some practical examples.
Example 1: Basic Setup
Start by creating a .NET Core Web API project.
dotnet new webapi -n CQRSExample
cd CQRSExample
1. Define Commands and Queries
Create folders for Commands and Queries.
mkdir Commands Queries
In the Commands folder, create a CreateProductCommand.cs file.
public class CreateProductCommand
{
public string Name { get; set; }
public decimal Price { get; set; }
}
In the Queries folder, create a GetProductByIdQuery.cs file.
public class GetProductByIdQuery
{
public int Id { get; set; }
}
2. Implement Handlers
Create handlers for the commands and queries.
In the Commands folder, create a CreateProductCommandHandler.cs file.
public class CreateProductCommandHandler
{
private readonly ApplicationDbContext _context;
public CreateProductCommandHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task Handle(CreateProductCommand command)
{
var product = new Product
{
Name = command.Name,
Price = command.Price
};
_context.Products.Add(product);
await _context.SaveChangesAsync();
}
}
In the Queries folder, create a GetProductByIdQueryHandler.cs file.
public class GetProductByIdQueryHandler
{
private readonly ApplicationDbContext _context;
public GetProductByIdQueryHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<Product> Handle(GetProductByIdQuery query)
{
return await _context.Products.FindAsync(query.Id);
}
}
3. Register Handlers in Dependency Injection
In Startup.cs, register the handlers in the ConfigureServices method.
领英推荐
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<CreateProductCommandHandler>();
services.AddScoped<GetProductByIdQueryHandler>();
services.AddControllers();
}
4. Create API Endpoints
In Controllers, create a ProductsController.cs file.
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly CreateProductCommandHandler _createHandler;
private readonly GetProductByIdQueryHandler _getByIdHandler;
public ProductsController(
CreateProductCommandHandler createHandler,
GetProductByIdQueryHandler getByIdHandler)
{
_createHandler = createHandler;
_getByIdHandler = getByIdHandler;
}
[HttpPost]
public async Task<IActionResult> Create(CreateProductCommand command)
{
await _createHandler.Handle(command);
return Ok();
}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
var product = await _getByIdHandler.Handle(new GetProductByIdQuery { Id = id });
return Ok(product);
}
}
Example 2: Advanced CQRS with MediatR
MediatR is a popular library that simplifies implementing CQRS in .NET Core. It uses the mediator pattern to decouple request handling from business logic.
1. Install MediatR and MediatR.Extensions.Microsoft.DependencyInjection
dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
2. Define Commands and Queries
In the Commands folder, create a CreateProductCommand.cs file.
using MediatR;
public class CreateProductCommand : IRequest
{
public string Name { get; set; }
public decimal Price { get; set; }
}
In the Queries folder, create a GetProductByIdQuery.cs file.
using MediatR;
public class GetProductByIdQuery : IRequest<Product>
{
public int Id { get; set; }
}
3. Implement Handlers
In the Commands folder, create a CreateProductCommandHandler.cs file.
using MediatR;
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand>
{
private readonly ApplicationDbContext _context;
public CreateProductCommandHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(CreateProductCommand command, CancellationToken cancellationToken)
{
var product = new Product
{
Name = command.Name,
Price = command.Price
};
_context.Products.Add(product);
await _context.SaveChangesAsync();
return Unit.Value;
}
}
In the Queries folder, create a GetProductByIdQueryHandler.cs file.
using MediatR;
public class GetProductByIdQueryHandler : IRequestHandler<GetProductByIdQuery, Product>
{
private readonly ApplicationDbContext _context;
public GetProductByIdQueryHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<Product> Handle(GetProductByIdQuery query, CancellationToken cancellationToken)
{
return await _context.Products.FindAsync(query.Id);
}
}
4. Register MediatR in Dependency Injection
In Startup.cs, register MediatR in the ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMediatR(typeof(Startup));
services.AddControllers();
}
5. Create API Endpoints
In Controllers, update the ProductsController.cs file.
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IMediator _mediator;
public ProductsController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<IActionResult> Create(CreateProductCommand command)
{
await _mediator.Send(command);
return Ok();
}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
var product = await _mediator.Send(new GetProductByIdQuery { Id = id });
return Ok(product);
}
}
Conclusion
The CQRS pattern is a powerful architectural approach that can significantly enhance the performance, scalability, and maintainability of your .NET Core applications. By separating read and write operations, you can optimize each side independently and provide a more robust solution to complex business problems. Implementing CQRS with tools like MediatR further simplifies the process, making it easier to manage and evolve your application.
Embrace CQRS in your next project and experience the benefits of a cleaner, more efficient architecture. Happy coding!