Playbook

¿Cómo funciona un corte vertical desde el interior? (Como Funciona Un Corte Vertical Desde El Interior)

¿Cómo fluye internamente un segmento vertical desde la solicitud hasta la validación, desde el controlador hasta el agregado, desde el evento de la bandeja de salida hasta la lectura del modelo y el costo de la nube? CQRS...

Corte vertical: ingeniería orientada a funciones

Parte 2 de 4

A vertical slice request pipeline from endpoint to handler, validation, persistence, event publication, and tests

Resumen en 30 segundos

Cuando llega una solicitud HTTP al sistema, en realidad no va a un solo método. Primero, se importa desde la frontera exterior, se verifica, se pasa por el control de autorización, si es necesario, se abre una transacción, se aplica la regla de dominio, se registran los datos, se asegura el evento y se actualiza la parte de lectura.

Vertical Slice organiza este viaje en torno a la intención de un único usuario.```text HTTP Request | v Endpoint / Controller | v Mediator.Send(command) | v Pipeline Behaviors | +--> Validation +--> Authorization +--> Logging / Metrics +--> Idempotency +--> Transaction v Command Handler | v Aggregate | v Repository | v Outbox | v Database Commit


## Problema: ¿por qué un albarán pequeño se vuelve caro?

Imaginemos que añadimos un albarán de entrega al pedido en una aplicación de Marketplace. A primera vista esto es sólo un campo de cadena. Sin embargo, en producción, el mismo campo aparece en el resumen de pago, el panel del vendedor, la integración de envío, el correo electrónico del cliente y el registro de auditoría.```text
AddDeliveryNote
  -> sipariş kuralı
  -> validasyon
  -> audit
  -> notification
  -> seller projection
  -> müşteri görünümü
```En la estructura en capas, este cambio se distribuye entre los roles técnicos. En Vertical Slice, la pregunta cambia: ¿A qué intención del usuario pertenece este comportamiento y en qué límite de coherencia se debe completar?

## Conceptos en la primera mención```text
Command
Sistemin durumunu değiştirmek isteyen niyettir. Örneğin AddDeliveryNote bir command'dir; başarılı olabilir, reddedilebilir veya hata alabilir.

Query
Sistemin durumunu değiştirmeden veri okuma isteğidir. Örneğin GetOrderSummary bir query'dir.

Handler
Bir command veya query'nin uygulama seviyesindeki akışını yöneten sınıftır. Handler orkestrasyon yapar; iş kuralının kalıcı sahibi olmamalıdır.

Pipeline Behavior
Handler'a ulaşmadan önce veya sonra çalışan ortak teknik adımdır. Validation, authorization, logging, idempotency ve transaction burada konumlanabilir.

Aggregate
Sistemde asla bozulmaması gereken iş kurallarını, yani invariant'ları, koruyan domain nesnesidir. Örneğin iptal edilmiş siparişe teslimat notu eklenememesi aggregate kuralıdır.

Read Model
Domain entity'nin kopyası değildir; bir ekranın veya API cevabının ihtiyaç duyduğu okuma şeklidir.

Projection
Event veya write model değişikliklerinden read model üreten süreçtir. Örneğin DeliveryNoteUpdated event'inden SellerOrderSummary read model'ini günceller.

Outbox
Veritabanı değişikliği ile event yayınını aynı local transaction içinde güvenceye alan kayıt desenidir.

CDC
Change Data Capture, veritabanı loglarından değişiklikleri okuyup dış sistemlere taşıma yaklaşımıdır. Domain event tasarımı yerine DB değişimini kaynak alır.
```Estas piezas no son intercambiables. El controlador no es una regla de dominio; Invoca el comportamiento del dominio. El oleoducto no es una política comercial; La aplicación común es la línea de seguridad. Outbox no es un intermediario; Es el registro transaccional del evento lo que no debe perderse.

## Historia real: segmento de nota de pedido

Diseñemos un caso de uso `AddDeliveryNote`. El usuario quiere añadir un albarán de entrega al pedido. El sistema primero verifica el derecho del usuario a cambiar el orden, verifica la longitud de la nota, ejecuta el comportamiento en conjunto, guarda el cambio y genera eventos para actualizar otras pantallas.```text
Features/Orders/AddDeliveryNote
  AddDeliveryNoteEndpoint.cs
  AddDeliveryNoteCommand.cs
  AddDeliveryNoteValidator.cs
  AddDeliveryNoteHandler.cs
  AddDeliveryNoteResponse.cs
  AddDeliveryNoteTests.cs
```Este directorio no debería ser un cementerio de "minicapas". Cada archivo tiene una única responsabilidad: el punto final lleva el contrato HTTP, el comando expresa la intención, el validador mantiene el límite de entrada, el controlador gestiona el flujo, el agregado implementa la regla de negocio y las pruebas prueban el comportamiento.

## Diseño de algoritmos

A nivel de pseudocódigo, el segmento funciona así:```text
algorithm AddDeliveryNote(command):
  input: orderId, customerId, note, requestId
  output: AddDeliveryNoteResult

  validate command
  reject if requestId was processed before

  order = orderRepository.load(orderId)
  reject if order does not exist
  reject if order.customerId != customerId

  order.addDeliveryNote(note)
  outbox.add(DeliveryNoteUpdated(orderId, note, occurredAt))

  transaction.commit()
  return success(orderId, note)
```La complejidad del tiempo es O(1) en flujo normal; debido a que se carga a través de una única identidad agregada, se ejecuta una cantidad fija de reglas y se escribe una cantidad fija de eventos. Si el control de idempotencia se realiza con un índice único, el costo de búsqueda es prácticamente O(log n), mientras que con un caché basado en hash o un almacén clave-valor, el costo promedio es O(1). La complejidad de la memoria es O(1); El controlador solo lleva el estado agregado necesario y los datos de comando, no todo el historial de pedidos.

## ¿Por qué fue un problema?

El problema no suele ser la longitud del código. El problema es que el mismo comportamiento se representa con información a medias en múltiples capas técnicas.```text
Controller doğrular gibi yapar
Service iş kuralı gibi yapar
Repository veri kuralı gibi yapar
Frontend tekrar kontrol eder
Test mock sırasını korur
```En este modelo, la Responsabilidad Única es visible pero no a nivel de comportamiento. Técnicamente, cada clase puede ser pequeña; pero la decisión empresarial está fragmentada. Vertical Slice mueve SOLID de los nombres de clase a la propiedad del comportamiento. La segregación de interfaz aquí significa usar puertos estrechos como `IAddDeliveryNoteAuthorization`, `IOrderRepository` y `IRequestDeduplicationStore` en lugar de un contrato inflado como `IOrderService`.

## CQRS y canalización de mediadores

La distinción entre comando y consulta deja clara la intención del segmento.```csharp
public sealed record AddDeliveryNoteCommand(
    Guid OrderId,
    Guid CustomerId,
    string Note,
    string RequestId) : ICommand<AddDeliveryNoteResult>;

public sealed class AddDeliveryNoteHandler(
    IOrderRepository orders,
    IRequestDeduplicationStore deduplication,
    IOutboxWriter outbox,
    IUnitOfWork unitOfWork)
    : ICommandHandler<AddDeliveryNoteCommand, AddDeliveryNoteResult>
{
    public async Task<AddDeliveryNoteResult> Handle(
        AddDeliveryNoteCommand command,
        CancellationToken cancellationToken)
    {
        if (await deduplication.ExistsAsync(command.RequestId, cancellationToken))
        {
            return AddDeliveryNoteResult.AlreadyProcessed(command.OrderId);
        }

        var order = await orders.GetByIdAsync(command.OrderId, cancellationToken)
            ?? throw new OrderNotFoundException(command.OrderId);

        order.EnsureOwnedBy(command.CustomerId);
        order.AddDeliveryNote(command.Note);

        await outbox.AddAsync(
            DeliveryNoteUpdated.From(order),
            cancellationToken);

        await unitOfWork.CommitAsync(cancellationToken);
        return AddDeliveryNoteResult.Updated(order.Id, order.DeliveryNote);
    }
}
```Usar Mediator es una herramienta, no un objetivo aquí. El valor real es que la canalización saca del controlador flujos repetitivos como verificación, autorización, transacción, registro e idempotencia. Sin embargo, la selección de la biblioteca debe evaluarse en función de la licencia, el cumplimiento de AOT, el costo de reflexión y el proceso de aprobación institucional, así como el desempeño técnico. Antes de hacer permanente una decisión marco, se debe verificar el texto de la licencia y la política organizativa.

## ¿Cómo fluye el lado de la consulta?

El lado del comando se centra en las reglas comerciales y las transacciones. El lado de la consulta se centra en la eficiencia de la lectura y la experiencia del usuario. La mayoría de las veces no queremos cargar agregados para mostrar una lista de pedidos; Un modelo de lectura estrecha adecuado para la pantalla es suficiente.```text
GET /orders
      |
      v
Authorization
      |
      v
Cache
      |
      v
Read Database / Search Index
      |
      v
DTO Mapping
      |
      v
Response
```La caché no es sólo una herramienta de rendimiento en esta cadena; También es una decisión de costos. Los datos que se leen con frecuencia, cambian raramente y toleran un retraso de unos segundos son adecuados para el almacenamiento en caché. Para los datos que requieren una gran coherencia, como el estado de los pagos, la política de caché debe diseñarse con mucho más cuidado.

## Alternativas

| Alternativa | Fuerza | Precio aceptado |
| --- | --- | --- |
| Servicio CRUD sencillo | Costo inicial más bajo | A medida que crece el comportamiento, aumenta el servicio común |
| Rebanada vertical, sin CQRS | Característica asegura localidad | La intención de lectura/escritura puede ser menos visible |
| Rebanada vertical + CQRS | La distinción entre intención, prueba y canalización queda clara | Se necesita más disciplina en el aula y en los contratos |
| Modelo de lectura/escritura físicamente separado | Rendimiento lector e independencia de escala | Agrega consistencia eventual, bandeja de salida, proyección y costo de operación |
| Microservicio | Implementación y escalamiento independientes | Aumentan los costos de red, consistencia de datos, observabilidad y equipo |

En una superficie CRUD pequeña, la mejor decisión suele ser Vertical Slice + simple handler. Se debe elegir CQRS físico o microservicio solo si la sobrecarga de lectura/escritura, la propiedad del equipo y el aislamiento de fallas justifican el costo.

## Decisión: primero límite lógico, luego separación física

Mi decisión predeterminada es: primero separar lógicamente el segmento dentro de la aplicación; Luego, si la medición fuerza la separación física, agregue un modelo de lectura, un intermediario, un caché o un servicio separado.```text
Aşama 1: Modular monolith içinde slice
Aşama 2: Command/query ayrımı
Aşama 3: Outbox ve projection
Aşama 4: Ayrı read store veya cache
Aşama 5: Gerekirse ayrı deploy birimi
```Esta secuencia pospone el costo pero no el diseño. Si el límite del código se dibuja correctamente, la separación física no es una reescritura sino una resta controlada.

## Separación de datos: lógico no es lo mismo que físico

La réplica de lectura no es CQRS. La réplica lee el mismo esquema desde un nodo diferente; CQRS, por otro lado, remodela el modelo de lectura según el patrón de uso.```text
Replica
  → aynı tablo
  → aynı join ihtiyacı
  → daha fazla okuma kapasitesi

Projection
  → ekran için hazırlanmış veri
  → daha az join
  → daha düşük latency ve I/O
```Por ejemplo, en lugar de unir `CityId` cada vez en la lista de pedidos, escribir `CityName` en la proyección es una decisión de desnormalización. Esta decisión crea un costo de sincronización adicional en el lado de la escritura; En el lado de la lectura, puede alimentar la pantalla con una sola lectura.

## Escritura dual, Bandeja de salida y CDC

El flujo más peligroso es:```text
1. Siparişi veritabanına yaz
2. Broker'a event gönder
3. İkinci adımda sistem çöksün
```En este caso hay orden pero no evento. Atrás quedan el envío, la notificación o la proyección. Transactional Outbox cierra esta brecha: los datos comerciales y el evento a publicar se escriben en la misma transacción local. Luego, un editor o un proceso basado en CDC mueve el registro de la bandeja de salida al corredor.

| Criterio | Bandeja de salida | CDC |
| --- | --- | --- |
| Control de eventos de dominio | La aplicación determina | DB determina el cambio |
| Intención del evento | Abrir | Indirecto |
| Dependencia del esquema de base de datos | inferior | Alto |
| Contenido del evento | Diseñado con lenguaje empresarial | Afectado por la estructura de tabla/registro |
| Cumplimiento de microservicios | Muy alto | Depende |
| Costo de instalación | Más códigos de aplicación | Más información sobre infraestructura |

Los CDC son una herramienta poderosa; Es especialmente valioso para infundir cambios a partir de los sistemas existentes. Sin embargo, si la aplicación determina qué significa el evento de dominio, Outbox genera un contrato más claro.

Una entrega al menos una vez puede producir una entrega repetida. Por tanto el consumidor debe ser idempotente.```text
DeliveryNoteUpdated event'i iki kez geldi
  → projection aynı version'ı gördü
  → ikinci işlem no-op oldu
```La idempotencia no es aquí un adorno; Es el seguro práctico de la coherencia de los datos en un sistema distribuido.

## Costo de la nube: ¿Por qué el segmento afecta la factura?

Vertical Slice no es solo organización de código; También hace visible la unidad de escala. Si el lado de escritura de extracción consume mucha CPU y el lado de lectura del catálogo consume mucha memoria y caché, forzar a ambos al mismo perfil de recursos produce un desperdicio.```text
Checkout command side
  → düşük concurrency
  → yüksek tutarlılık
  → transaction ve fraud kontrolü

Catalog query side
  → yüksek concurrency
  → cache ve projection
  → düşük latency beklentisi
```Las cargas asimétricas requieren recursos asimétricos. Sin embargo, cada caché, NAT, balanceador de carga, cola, almacén de lectura y unidad de implementación separada agrega un nuevo elemento a la factura. Por lo tanto, la decisión arquitectónica debe tomarse con métricas reales: latencia p95, relación lectura/escritura, saturación de conexión, salida, volumen de reintentos y radio de explosión incidente.

## Análisis de rendimiento y memoria.

La ruta activa de un segmento no debería producir asignaciones innecesarias. En lugar de traducir directamente el modelo de solicitud a la entidad de dominio, el controlador mueve solo los campos que necesita. Leer la proyección en lugar de cargar agregados en el lado de la consulta reduce los costos de CPU, memoria y E/S.```text
Command path
  → tek aggregate
  → kısa transaction
  → sabit event sayısı

Query path
  → ihtiyaca göre projection
  → minimum column set
  → pagination veya cursor
```La complejidad de la consulta de listado es O(p) si el tamaño de la página es `p`. Con la paginación del cursor, la memoria permanece O(p); Almacenar la lista completa consume memoria O(n) y crea riesgos innecesarios en la producción.

## Estrategia de prueba

Las pruebas de corte vertical deberían optimizar la confianza en el comportamiento, no la cantidad de simulacros.

1. Las pruebas del validador demuestran valores límite: nota en blanco, longitud máxima, caracteres prohibidos.
2. Las pruebas del controlador verifican el flujo exitoso y los flujos de error: sin pedido, error de propiedad, solicitud duplicada.
3. Las pruebas de dominio preservan las invariantes agregadas: no se pueden agregar notas a las órdenes canceladas.
4. Las pruebas de integración demuestran que la transacción y el registro de la bandeja de salida ocurren juntos.
5. Las pruebas de arquitectura verifican que un segmento no dependa de las clases internas de otro segmento.
6. Las pruebas de proyección muestran que el resultado sigue siendo idempotente cuando el evento se repite.

El propósito de la pirámide de pruebas no es aislar cada clase; Es encontrar rápidamente el punto donde se anula la decisión.

## Compensación

Vertical Slice produce archivos más pequeños. Este es un precio consciente. A su vez, el motivo del cambio, la cobertura de la prueba y la superficie de reversión se vuelven más visibles.

Cuando se aplica incorrectamente, surgen dos riesgos. La primera es que cada segmento produce su propio minimarco. La segunda es que la carpeta `Shared` crece infinitamente y se convierte en el nuevo nombre del antiguo monolito en capas. La solución son interfaces estrechas, puertos abiertos, pruebas de arquitectura y registro de decisiones con ADR.

## Frecuentemente confundido```text
❌ Vertical Slice = controller'dan repository'ye kadar her şeyi aynı klasöre koymak
✓ Kullanıcı niyetinin davranış, veri ve test sınırını birlikte tasarlamaktır.

❌ CQRS = mutlaka ayrı veritabanı
✓ CQRS önce niyet ayrımıdır; fiziksel ayrım ayrı bir maliyet kararıdır.

❌ Mediator = mimari
✓ Mediator yalnızca dispatch ve pipeline aracıdır; mimari sınırı feature sahipliği belirler.

❌ Outbox = exactly-once garantisi
✓ Outbox event kaybını önler; consumer yine idempotent olmalıdır.

❌ Read replica = projection
✓ Replica aynı modeli çoğaltır; projection okuma ihtiyacına göre yeni model üretir.

Lista de verificación de decisiones

  1. ¿Slice representa la intención de un único usuario?
  2. ¿El controlador solo realiza la orquestación de aplicaciones o almacena reglas de dominio?
  3. ¿El comando y la consulta tienen que compartir el mismo patrón de respuesta o es más claro que están separados?
  4. ¿El límite de la transacción permanece dentro de un único agregado o un límite de coherencia consciente?
  5. ¿La transmisión del evento está conectada a la misma transacción local que el registro de datos vía bandeja de salida?
  6. ¿El consumidor sigue siendo idempotente cuando recibe un evento duplicado?
  7. ¿Projection realmente reduce la necesidad de pantallas o agrega costos operativos innecesarios?
  8. ¿Existe alguna evidencia de latencia, costo o radio de explosión de p95 para la decisión de almacenar en caché, intermediar, leer, almacenar e implementar por separado?
  9. ¿Las interfaces son estrechas o se han convertido en una bolsa que contiene todos los casos de uso, como IOrderService?
  10. ¿Las pruebas arquitectónicas preservan los límites de las características en la etapa de compilación o CI?

Si la respuesta a estas preguntas no está clara, a menudo es más barato simplificar el límite del segmento en lugar de agregar más infraestructura.

Lo que debes recordar de este artículo

  1. Un segmento vertical es el límite de comportamiento de la intención de un único usuario desde la solicitud hasta la prueba.
  2. CQRS, Mediador, Bandeja de salida y Proyección no son la misma decisión; Cada uno resuelve diferentes problemas de costos y seguridad.
  3. Verificar primero los límites de las características lógicas mantiene controlado el costo de la separación física.
  4. Reduce la pérdida por eventos de la bandeja de salida en el sistema distribuido; La idempotencia evita que la reenvío cause daños.
  5. El costo de la nube no es externo a la arquitectura; Cada límite se convierte en un elemento de cálculo, E/S, red y operación.

Flujo de un extremo a otro```text

POST /orders | v Endpoint / Controller | v Mediator.Send() | v Validation | v Authorization | v Transaction | v Command Handler | v Aggregate | v Repository | v Outbox | v Commit | v Broker / Kafka | v Projection | v Read Database | v GET /orders | v Query Handler | v Frontend


En la siguiente sección, examinaremos cómo mantener juntos los límites de corte vertical, agregado DDD y monolito modular en producción.

FAQ

Frequently asked questions

¿Es correcto "Vertical Slice = poner todo, desde el controlador hasta el repositorio, en la misma carpeta"?

Se trata de diseñar juntos el comportamiento, los datos y los límites de prueba de la intención del usuario.

¿Es correcto "CQRS = base de datos necesariamente separada"?

CQRS es una distinción de intención primero; La separación física es una decisión de costos separada.

"¿Cómo funciona un corte vertical desde el interior?" ¿Qué dice?

¿Cómo fluye internamente un segmento vertical desde la solicitud hasta la validación, desde el controlador hasta el agregado, desde el evento de la bandeja de salida hasta la lectura del modelo y el costo de la nube? CQRS...

Principios de ingeniería aprendidos

  • El límite real de un segmento no es la carpeta, sino la intención del usuario y la decisión de coherencia, que varía por la misma razón.
  • CQRS y Outbox no son un requisito para la separación física, sino herramientas que hacen explícitos los costos de intención y confiabilidad.
  • La arquitectura de producción debe medirse mediante la organización del código, así como los costos de latencia p95, E/S, salida, reintento y reversión.

Continuar leyendo

Continuar leyendo

Siguiente en la serie

Artículos relacionados

Artículos relacionados

Paylaş