Command Query Responsibility Segregation (CQRS) pattern
LinkedIn

Command Query Responsibility Segregation (CQRS) pattern

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:

  • Scalability: By isolating read and write operations, you can scale them independently.
  • Performance: Optimizing queries and commands separately can lead to performance improvements.
  • Maintainability: Clear separation of concerns makes the system easier to understand and maintain.

Why Use CQRS?

  1. Improved Performance: Separate models for read and write operations can be optimized independently, enhancing overall system performance.
  2. Scalability: By decoupling reads from writes, you can scale each part of the system independently based on demand.
  3. Flexibility: Evolving your system becomes easier as you can modify the read or write side without affecting the other.
  4. Security: Different security policies can be applied to read and write operations, increasing the overall security of the system.

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!


要查看或添加评论,请登录

Mohd Saeed的更多文章

  • RabbitMQ and .NET Core: A Powerful Duo for Resilient Applications

    RabbitMQ and .NET Core: A Powerful Duo for Resilient Applications

    Imagine building a complex system where different parts need to communicate with each other, like a team working on a…

  • Understand the Option Pattern for Ultimate Configurability and Flexibility

    Understand the Option Pattern for Ultimate Configurability and Flexibility

    In software development, managing settings efficiently is essential. The Option Pattern in .

  • JWT Authentication with Refresh Tokens .NET Core Web API

    JWT Authentication with Refresh Tokens .NET Core Web API

    Securing APIs is essential in today's web development. JSON Web Tokens (JWT) are now a popular choice for managing…

  • Understanding CORS in .NET Core Web API

    Understanding CORS in .NET Core Web API

    In today’s online world, it’s important for different apps to safely talk to each other, even if they’re hosted on…

  • Unlocking the Power of Middleware in .NET Core

    Unlocking the Power of Middleware in .NET Core

    In the dynamic world of software development, staying abreast of efficient and modular design practices is crucial. One…

  • SOLID Principles

    SOLID Principles

    The SOLID principles are a set of five design principles for writing maintainable, scalable, and robust object-oriented…

    1 条评论
  • Lifetime of Dependency Injection

    Lifetime of Dependency Injection

    Dependency Injection (DI) is an important feature of modern software development, which makes it easier to manage…

    1 条评论
  • Property Dependency Injection (PDI)

    Property Dependency Injection (PDI)

    Dependency Injection (DI) is a design pattern that is widely used in software development to achieve loosely coupled…

    2 条评论
  • Dependency Injection (DI)

    Dependency Injection (DI)

    Dependency Injection (DI) is a technique that enables objects to receive dependencies without having to create or…

社区洞察

其他会员也浏览了