En el desarrollo de aplicaciones empresariales con ASP.NET Core, mantener un código organizado y escalable se vuelve cada vez más importante a medida que crece la complejidad del proyecto. Una de las arquitecturas más utilizadas para lograr este objetivo es CQRS (Command Query Responsibility Segregation) junto con MediatR, una biblioteca que facilita la comunicación entre componentes de manera desacoplada.
¿Qué es CQRS?
CQRS es un patrón arquitectónico que propone separar las operaciones de lectura y escritura de una aplicación.
- Commands: realizan cambios en los datos (crear, actualizar o eliminar).
- Queries: obtienen información sin modificar el estado del sistema.
En una arquitectura tradicional, un mismo servicio suele encargarse tanto de leer como de escribir datos. Con CQRS, estas responsabilidades se dividen, permitiendo una mejor organización, mantenibilidad y escalabilidad.
¿Qué es MediatR?
MediatR es una implementación del patrón Mediator para .NET. Su objetivo es reducir el acoplamiento entre controladores, servicios y lógica de negocio.
En lugar de que un controlador invoque directamente un servicio, envía una solicitud a MediatR, que se encarga de encontrar y ejecutar el manejador correspondiente.
Esto permite:
- Código más limpio.
- Menor dependencia entre capas.
- Mayor facilidad para realizar pruebas unitarias.
- Mejor separación de responsabilidades.
Estructura recomendada del proyecto
Una API ASP.NET Core moderna que implemente CQRS suele organizarse de la siguiente manera:
Application
│
├── Commands
│ ├── CreateProduct
│ └── UpdateProduct
│
├── Queries
│ ├── GetProductById
│ └── GetProducts
│
├── Handlers
│
├── DTOs
│
└── Interfaces
Infrastructure
│
├── Persistence
└── Services
API
│
├── Controllers
└── Program.cs
Esta estructura facilita la localización de funcionalidades y mantiene una clara separación entre las distintas capas de la aplicación.
Creando un Command
Supongamos que necesitamos crear un producto.
using MediatR;
public record CreateProductCommand(
string Name,
decimal Price
) : IRequest<int>;
El comando representa una acción que modificará el sistema.
Ahora creamos el Handler:
public class CreateProductHandler
: IRequestHandler<CreateProductCommand, int>
{
private readonly ApplicationDbContext _context;
public CreateProductHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<int> Handle(
CreateProductCommand request,
CancellationToken cancellationToken)
{
var product = new Product
{
Name = request.Name,
Price = request.Price
};
_context.Products.Add(product);
await _context.SaveChangesAsync(cancellationToken);
return product.Id;
}
}
El Handler contiene toda la lógica necesaria para ejecutar la operación.
Creando una Query
Para obtener un producto por su identificador:
using MediatR;
public record GetProductByIdQuery(int Id)
: IRequest<ProductDto>;
Handler correspondiente:
public class GetProductByIdHandler
: IRequestHandler<GetProductByIdQuery, ProductDto>
{
private readonly ApplicationDbContext _context;
public GetProductByIdHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<ProductDto> Handle(
GetProductByIdQuery request,
CancellationToken cancellationToken)
{
return await _context.Products
.Where(x => x.Id == request.Id)
.Select(x => new ProductDto
{
Id = x.Id,
Name = x.Name,
Price = x.Price
})
.FirstOrDefaultAsync(cancellationToken);
}
}
Las Queries únicamente leen información y no modifican el estado de la aplicación.
Uso desde el controlador
El controlador permanece extremadamente limpio:
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly IMediator _mediator;
public ProductsController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<IActionResult> Create(
CreateProductCommand command)
{
var id = await _mediator.Send(command);
return Ok(id);
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var product = await _mediator.Send(
new GetProductByIdQuery(id));
return Ok(product);
}
}
El controlador ya no necesita conocer servicios específicos ni lógica de negocio.
Registro de MediatR
En .NET moderno basta con registrarlo en Program.cs:
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(
typeof(CreateProductCommand).Assembly);
});
Con esto, MediatR detectará automáticamente todos los Handlers disponibles.
Beneficios de CQRS con MediatR
Implementar CQRS junto con MediatR aporta múltiples ventajas:
- Separación clara entre lectura y escritura.
- Código más modular y mantenible.
- Mayor facilidad para escalar aplicaciones complejas.
- Mejor experiencia de pruebas unitarias.
- Controladores más simples y enfocados.
- Integración natural con Clean Architecture.
CQRS y MediatR se han convertido en una combinación estándar para desarrollar APIs modernas con ASP.NET Core. Al separar las operaciones de consulta y modificación de datos, y utilizar MediatR como intermediario entre capas, es posible construir aplicaciones más limpias, escalables y fáciles de mantener. Para proyectos empresariales que buscan una arquitectura robusta y preparada para crecer, esta combinación representa una de las mejores prácticas dentro del ecosistema .NET actual.

