CQRS y Event Sourcing en C#: Arquitectura para sistemas complejos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Cuando el CRUD normal deja de funcionar: Introducción a CQRS y Event Sourcing

\n\n

Imagine que está escribiendo un sistema para un banco. Cada segundo, cientos de usuarios verifican saldos, transfieren dinero, abren depósitos. Su aplicación en C# funciona con Entity Framework estándar y operaciones CRUD típicas. Y de repente — la base de datos falla. ¿Por qué? Porque un informe pesado bloqueó la tabla mientras se escribía una transacción. ¿Le suena familiar?

\n\n

Los desarrolladores de sistemas complejos en C# tarde o temprano chocan con el techo de la arquitectura clásica. Dos potentes patrones vienen al rescate: CQRS (Command Query Responsibility Segregation) y Event Sourcing. Analicémoslos en la práctica — con ejemplos vivos, no teoría seca.

\n\n

¿Qué es CQRS y por qué no es solo "separación"?

\n\n

CQRS no se trata de poner métodos en carpetas diferentes. Es una filosofía: los comandos (cambian el estado) y las consultas (leen el estado) deben vivir separados. En el CRUD clásico, tiene un solo modelo de datos tanto para lectura como para escritura. En CQRS — al menos dos.

\n\n

El problema del enfoque clásico

\n

Supongamos que tiene un UserRepository con un método GetUserById. Cuando lee un usuario para mostrarlo en pantalla, necesita 3 campos. Cuando lo actualiza — 10 campos y validaciones. Como resultado, un modelo se convierte en un monstruo con campos null y mapeadores confusos.

\n\n

En CQRS se separan las responsabilidades:

\n
    \n
  • Command — ejecuta una acción (por ejemplo, CreateOrderCommand)
  • \n
  • Query — devuelve datos (por ejemplo, GetOrderQuery)
  • \n
  • Read Model — DTO plano, optimizado para una pantalla específica
  • \n
  • Write Model — modelo de dominio rico con lógica de negocio
  • \n
\n\n

Escribiendo CQRS en C# con MediatR

\n

La forma más popular de implementar CQRS en C# es usar la biblioteca MediatR. Implementa el patrón Mediator y se adapta perfectamente al concepto de comandos y consultas.

\n\n

Paso 1: Definir un comando

\n
public record CreateOrderCommand(\n    Guid CustomerId,\n    List<OrderItemDto> Items,\n    string ShippingAddress\n) : IRequest<Guid>;  // Devuelve el ID del pedido creado
\n\n

Paso 2: Manejador del comando

\n
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, Guid>\n{\n    private readonly IOrderRepository _orderRepository;\n    private readonly IUnitOfWork _unitOfWork;\n\n    public CreateOrderCommandHandler(IOrderRepository orderRepository, IUnitOfWork unitOfWork)\n    {\n        _orderRepository = orderRepository;\n        _unitOfWork = unitOfWork;\n    }\n\n    public async Task<Guid> Handle(CreateOrderCommand request, CancellationToken cancellationToken)\n    {\n        // Lógica de negocio: verificación, validación, creación\n        var order = new Order(request.CustomerId, request.ShippingAddress);\n        \n        foreach (var item in request.Items)\n        {\n            order.AddItem(item.ProductId, item.Quantity, item.Price);\n        }\n\n        await _orderRepository.AddAsync(order);\n        await _unitOfWork.SaveChangesAsync(cancellationToken);\n        \n        return order.Id;\n    }\n}
\n\n

Paso 3: Definir una consulta

\n
public record GetOrderSummaryQuery(Guid OrderId) : IRequest<OrderSummaryDto>;
\n\n

Paso 4: Manejador de la consulta (¡lee desde una BD separada!)

\n
public class GetOrderSummaryQueryHandler : IRequestHandler<GetOrderSummaryQuery, OrderSummaryDto>\n{\n    private readonly IReadDbContext _readDbContext; // Contexto separado para lectura\n\n    public GetOrderSummaryQueryHandler(IReadDbContext read                

Blogs

Book Recommendations