S

super-dev — full-stack

@super-dev.app · Full-stack · ★ disponible para proyectos
12,8k suscriptores·47 vídeos·Activo desde 2017

Desarrollador full-stack .NET / Angular con perfil DevOps en Azure Cloud. También cómodo con Flutter + Firebase. Construyo productos, los despliego, los monitorizo.

Volver a los artículos
.NET
.NET

CQRS y vertical slices sin sobreingeniería

Artículo 3 de 5 — El .NET moderno
APIs mínimas, CQRS, gRPC, source generators: .NET 8 limpio y testeable, sin complicaciones.

En cuanto alguien pronuncia CQRS , muchos equipos sacan la artillería pesada: event sourcing, un bus de mensajes, dos bases de datos. Sin embargo, CQRS es ante todo una idea modesta — separar las lecturas de las escrituras — que se puede aplicar sin ninguna parafernalia, organizando el código por vertical slices .

Dividir por funcionalidad, no por capa

La arquitectura en capas fragmenta una funcionalidad en cinco carpetas: Controllers , Services , Repositories , DTOs , Validators . Para entender «crear una orden», hay que saltar de archivo en archivo. La vertical slice invierte la lógica: una carpeta por funcionalidad, todo lo que la concierne en el mismo lugar.

Bash
1Features/
2 Orders/
3 CreateOrder.cs # commande + handler + validateur
4 GetOrderById.cs # requête + handler
5 ListOrders.cs

Cada slice es autónoma. Se lee de arriba a abajo, se elimina sin efectos secundarios, y dos slices solo comparten el dominio — nunca un «servicio» cajón de sastre.

Comando y consulta, dos intenciones distintas

Un comando modifica el estado y devuelve (idealmente) solo un identificador. Una consulta no lee nada más que lo que la vista necesita, a menudo cortocircuitando el dominio para proyectar directamente hacia un DTO. Modelarlos por separado clarifica la intención:

C#
1public sealed record CreateOrder(Guid CustomerId, IReadOnlyList<LineItem> Items)
2 : IRequest<Guid>;
3
4public sealed class CreateOrderHandler(AppDbContext db)
5 : IRequestHandler<CreateOrder, Guid>
6{
7 public async Task<Guid> Handle(CreateOrder command, CancellationToken ct)
8 {
9 var order = Order.Create(command.CustomerId, command.Items);
10 db.Orders.Add(order);
11 await db.SaveChangesAsync(ct);
12
13 return order.Id;
14 }
15}

El handler permanece delgado : orquesta, no razona. La lógica de negocio vive en Order.Create , no en el handler — de lo contrario, simplemente habremos movido el «servicio» a otro archivo.

El mediador, opcional

A menudo se ve CQRS asociado a MediatR . El mediador desacopla el endpoint del handler y ofrece un punto de enganche para los pipeline behaviors (validación, logging, transacción). Es práctico, pero no es CQRS: se puede perfectamente inyectar el handler directamente.

C#
1group.MapPost("/", async (CreateOrder command, ISender sender) =>
2{
3 var id = await sender.Send(command);
4
5 return TypedResults.Created($"/orders/{id}", new { id });
6});

Si la aplicación es pequeña, omitir el mediador y llamar al handler directamente sigue siendo perfectamente legítimo — menos indirección, menos magia.

No sobrediseñar

La pregunta que hay que hacerse en cada slice: ¿realmente necesito esto? Bases de datos separadas, proyecciones asíncronas, event sourcing — son respuestas a problemas de escala precisos (lecturas masivamente superiores a las escrituras, auditoría inmutable). Sin ese problema, solo añaden latencia y bugs de coherencia. El buen CQRS, en el 90 % de los casos, es: comandos y consultas distintos, un único DbContext , slices legibles.

CQRS no es una arquitectura, es una disciplina de nomenclatura . Separa las intenciones, mantén los handlers delgados, y no añadas un bus de mensajes hasta el día en que una métrica te obligue a ello.
super-dev — portfolio.app
// Continuar en .NET
{ }
Estrangular un monolito .NET 8 sin romper nada
9 min • 3,2k lecturas
ƒ()
Minimal APIs + EF Core: una API .NET 8 limpia
8 min • 3,1k lecturas
gRPC entre microservicios .NET
9 min • 1,8k lecturas