0

Arquitectura de Software con C# 22: Implementación de Vertical Slice con MediatR y CQRS en C#

Introducción a la clase

En la clase anterior aprendimos a organizar una aplicación por Features en lugar de capas. Ahora veremos cómo usar MediatR y CQRS (Command Query Responsibility Segregation) para estructurar Vertical Slice Architecture de forma más eficiente.

En esta clase aprenderás:

  • Qué es MediatR y cómo ayuda en Vertical Slice Architecture
  • Qué es CQRS y cómo aplicarlo en C#
  • Cómo manejar comandos y consultas con MediatR
  • Implementar un ejemplo práctico con MediatR en C#

¿Qué es MediatR y cómo ayuda en Vertical Slice Architecture?

  • MediatR es una librería que permite la comunicación entre componentes sin acoplamiento directo.
  • Se basa en el patrón Mediator, donde los handlers actúan como intermediarios entre los diferentes módulos de la aplicación.
  • En Vertical Slice Architecture, MediatR permite manejar Commands y Queries de forma estructurada.

Ejemplo sin MediatR:

var handler = new CrearPedidoHandler(new PedidoRepository());
handler.Handle(new CrearPedidoCommand());

El controlador está directamente acoplado a la implementación del handler.

Ejemplo con MediatR:

await _mediator.Send(new CrearPedidoCommand());

El controlador solo conoce a MediatR, no sabe qué handler ejecutará el comando.

¿Qué es CQRS y cómo aplicarlo en Vertical Slice Architecture?

CQRS (Command Query Responsibility Segregation) separa operaciones de lectura (Queries) y operaciones de escritura (Commands). En Vertical Slice Architecture, cada Feature tiene sus propios Commands y Queries organizados por funcionalidad.

  • Commands → Modifican datos (CrearPedido, ActualizarPedido, EliminarPedido).
  • Queries → Obtienen datos (ObtenerPedido, ListarPedidos).

Ejemplo en C#: Implementando Vertical Slice con MediatR y CQRS

Instalar MediatR en el proyecto:
Ejecuta el siguiente comando en la terminal:

dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection

Estructura del código con MediatR:

/PedidosAPI
│── /Features
│   ├── /CrearPedido
│   │   ├── CrearPedidoCommand.cs
│   │   ├── CrearPedidoHandler.cs
│   │   ├── PedidoRepository.cs
│   ├── /ObtenerPedido
│   │   ├── ObtenerPedidoQuery.cs
│   │   ├── ObtenerPedidoHandler.cs
│── Program.cs
│── appsettings.json

Paso 1: Configurar MediatR en ASP.NET Core

Registrar MediatR en Program.cs

var builder = WebApplication.CreateBuilder(args);

// Registrar MediatR
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));

// Registrar Repositorios
builder.Services.AddScoped<IPedidoRepository, PedidoRepository>();

var app = builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});
app.Run();

Paso 2: Implementar la Feature «Crear Pedido» con MediatR

Definir el Comando (CrearPedidoCommand)

public class CrearPedidoCommand : IRequest
{
    public string Cliente { get; set; }
    public List<string> Productos { get; set; }
}

Implementar el Handler (CrearPedidoHandler)

public class CrearPedidoHandler : IRequestHandler<CrearPedidoCommand>
{
    private readonly IPedidoRepository _repository;

    public CrearPedidoHandler(IPedidoRepository repository)
    {
        _repository = repository;
    }

    public Task Handle(CrearPedidoCommand request, CancellationToken cancellationToken)
    {
        var pedido = new Pedido(0, request.Cliente);

        foreach (var producto in request.Productos)
        {
            pedido.AgregarProducto(new Producto(0, producto, 100)); // Precio por defecto
        }

        _repository.Guardar(pedido);
        return Task.CompletedTask;
    }
}

Implementar el Adaptador de Persistencia (PedidoRepository)

public class PedidoRepository : IPedidoRepository
{
    private readonly List<Pedido> _pedidos = new List<Pedido>();

    public void Guardar(Pedido pedido)
    {
        _pedidos.Add(pedido);
    }

    public Pedido ObtenerPorId(int id)
    {
        return _pedidos.FirstOrDefault(p => p.Id == id);
    }
}

Implementar el Controlador REST con MediatR

[ApiController]
[Route("api/pedidos")]
public class PedidoController : ControllerBase
{
    private readonly IMediator _mediator;

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

    [HttpPost]
    public async Task<IActionResult> CrearPedido([FromBody] CrearPedidoCommand command)
    {
        await _mediator.Send(command);
        return Ok("Pedido creado exitosamente");
    }
}

Paso 3: Implementar la Feature «Obtener Pedido» con MediatR

Definir la Query (ObtenerPedidoQuery)

public class ObtenerPedidoQuery : IRequest<Pedido>
{
    public int Id { get; set; }
}

Implementar el Handler (ObtenerPedidoHandler)

public class ObtenerPedidoHandler : IRequestHandler<ObtenerPedidoQuery, Pedido>
{
    private readonly IPedidoRepository _repository;

    public ObtenerPedidoHandler(IPedidoRepository repository)
    {
        _repository = repository;
    }

    public Task<Pedido> Handle(ObtenerPedidoQuery request, CancellationToken cancellationToken)
    {
        var pedido = _repository.ObtenerPorId(request.Id);
        return Task.FromResult(pedido);
    }
}

Modificar el Controlador REST para Obtener Pedidos con MediatR

[HttpGet("{id}")]
public async Task<IActionResult> ObtenerPedido(int id)
{
    var pedido = await _mediator.Send(new ObtenerPedidoQuery { Id = id });
    return pedido != null ? Ok(pedido) : NotFound();
}

Beneficios de usar MediatR y CQRS en Vertical Slice Architecture

  • Reduce el acoplamiento → Los controladores no necesitan saber qué handler se ejecuta.
  • Mejor mantenibilidad → Los comandos y consultas se organizan por Features.
  • Facilita la escalabilidad → Cada Feature crece de manera independiente.
  • Ideal para aplicaciones complejas → Separa la lógica de escritura y lectura.

Errores comunes al usar MediatR y CQRS

  • Usar CQRS en aplicaciones simples → No siempre es necesario separar lectura y escritura.
  • No manejar correctamente los errores → Se deben capturar excepciones en los Handlers.
  • No registrar MediatR en Program.cs → Es necesario para que funcione la inyección de dependencias.

Cuestionario de Autoevaluación

  • ¿Por qué MediatR ayuda a reducir el acoplamiento en Vertical Slice Architecture?
  • ¿Qué diferencia hay entre un Command y un Query en CQRS?
  • ¿Cómo evitarías la duplicación de lógica en múltiples Features?
  • ¿Cuándo NO deberías usar CQRS?
  • ¿Cómo se configuran Handlers en MediatR?

Resumen de la Clase

  • MediatR desacopla la ejecución de Commands y Queries en Vertical Slice Architecture.
  • CQRS separa las operaciones de lectura y escritura para mejorar escalabilidad y mantenibilidad.
  • Cada Feature tiene su propio Handler, asegurando independencia y modularidad.
  • MediatR permite ejecutar handlers sin que los controladores los conozcan directamente.

Próximo paso

En la siguiente clase veremos cómo integrar DDD en Vertical Slice Architecture para modelar el dominio de manera más efectiva.

Fernando Sonego

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *