Playbook

¿Cómo funciona la canalización CQRS (segregación de responsabilidad de consulta de comando)? Anatomía del flujo de comandos y consultas (Como Funciona La Canalizacion CQRS Segregacion DE Responsabilidad DE Consulta DE Comando Anatomia Del Flujo DE Comandos Y Consultas)

¿Qué es la canalización de solicitudes CQRS? ¿Cómo procede una solicitud HTTP a través del controlador, MediatR, comportamiento de canalización, controlador, bandeja de salida y modelo de lectura?

CQRS- Anatomía de las Decisiones

Parte 2 de 4

CQRS no con sintaxis de marco; Un libro de ingeniería de cuatro partes que examina las tensiones de decisión, escala y producción.

Command and query pipelines flowing through a CQRS architecture

¿Qué sucede cuando llega una solicitud HTTP al sistema? ¿Dónde funciona la validación, cuándo se abre la transacción y en qué nivel se aplican las reglas del dominio? En un sistema que utiliza CQRS, este viaje es diferente de la arquitectura CRUD clásica. En este artículo, examinaremos los pasos que sigue una solicitud desde la API a la base de datos y al modelo de lectura.

Para un lector que no esté familiarizado con CQRS, el diccionario más corto es este: Comando es la intención de cambiar el sistema; Consulta solicita información sin cambiar el sistema; Handler es el controlador de esta solicitud. Un mediador como MediatR permite al controlador reenviar la solicitud al flujo apropiado en lugar de reconocer directamente al controlador correcto.```text POST /orders ↓ Controller ↓ Mediator.Send(command) ↓ Pipeline behaviors ↓ Command handler ↓ Aggregate + Repository ↓ Database


El comportamiento de la canalización son controles técnicos comunes que no queremos reescribir en cada comando. Ninguna de estas son reglas comerciales; La regla de negocio reside en el controlador y en el agregado.```text
ValidationBehavior
  ↓
AuthorizationBehavior
  ↓
LoggingBehavior
  ↓
MetricsBehavior
  ↓
RetryBehavior (yalnızca güvenli işlemlerde)
  ↓
TransactionBehavior
  ↓
Handler
```La transacción se abre al final; porque no queremos mover innecesariamente la conexión de la base de datos y bloquearla para una solicitud que podría no ser válida, no autorizada o rechazada prematuramente. Una vez que llegamos a Handler, estamos listos para implementar la decisión comercial de forma atómica.

**Agregado** preserva las reglas comerciales del sistema (es decir, las invariantes) que nunca deben infringirse. Por ejemplo, si una orden completada no se puede cancelar nuevamente, esta regla debe mantenerse en el agregado de órdenes, no en el Controlador.

El lado de la consulta sigue un camino diferente. Para el mismo orden, lo que el usuario necesita puede no ser el objeto de dominio completo, sino una vista breve adecuada para la pantalla:```text
GET /orders
  ↓
Authorization
  ↓
Cache (uygunsa)
  ↓
Read database / Search index
  ↓
OrderSummary DTO
  ↓
Frontend

Conceptos en la primera mención```text

📦 Handler Tek bir command veya query'nin uygulama iş akışını yürütür.

📦 Mediator Controller'ın handler'ı doğrudan bilmeden isteği doğru işleyiciye göndermesini sağlar.

📦 Pipeline Behavior Validation, authorization veya logging gibi her istekte çalışan ortak teknik katmandır.

📦 Repository Aggregate'i yükleyen ve kalıcı olarak saklayan uygulama sınırıdır.

📦 DTO Domain modelin tamamını değil, bir ekranın ihtiyacı olan veriyi taşır.


Mientras lee el ejemplo del Handler, tenga en cuenta esta distinción: el Handler simplemente implementa la decisión comercial. La validación, la autorización y el registro no están incluidos en el controlador; Estos se resuelven mediante comportamientos de canalización. De esta manera, el controlador sigue siendo pequeño y cada solicitud pasa por las mismas reglas de seguridad/trazabilidad.

A menudo vemos CQRS como dos carpetas, algunos controladores MediatR y un caché. Esta imagen es engañosa. El trabajo principal de CQRS no es dividir el código; Se trata de distinguir entre **decisiones que cambian el estado de un sistema** y **solicitudes que solicitan información sobre ese estado**.

Esta es la segunda parte de la colección CQRS-Anatomía de las Decisiones. En la primera parte hablaremos de por qué la segregación se ha vuelto necesaria. Aquí entramos en el mecanismo: ¿a través de qué puertas pasa un comando, por qué una consulta no debería seguir el mismo camino y cuándo el modelo de lectura se convierte en un sistema separado?

## Punto de partida: una petición, dos intenciones diferentes

`CreateOrder` es un comando. Quiere añadir una nueva realidad al sistema. Ejecuta las reglas, solicita autorización, abre transacciones y debe ser auditable.

`GetOrderSummary` es una consulta. No produce una nueva realidad. Quiere devolver una vista rápida, estrecha y compatible con la pantalla. No es necesario cargar el mismo agregado, ejecutar reglas de dominio ni competir con bloqueos de escritura.

La implicación algorítmica de esta distinción es clara: en el lado del comando, el costo a menudo se considera para la validación y la coherencia; Por el lado de las consultas, el costo aumenta con la cantidad de datos consultados. El uso de un modelo de lectura personalizado en lugar de recorrer el gráfico agregado para una pantalla de lista reduce tanto las E/S innecesarias como la carga cognitiva.

## Canal de comando: camino seguro hacia la decisión

En el lado del comando, el propósito no es solo llamar a `handler`. La solicitud debe pasar por las reglas comunes del sistema antes de llegar a la decisión empresarial.```text
API
  → Authentication / Authorization
  → Validation
  → Idempotency check
  → Transaction
  → Command Handler
  → Aggregate + Domain Rules
  → Persist + Outbox
  → Commit
```Pseudocódigo:```text
handle(command):
  authorize(command.actor)
  validate(command)
  return idempotency.execute(command.key):
    begin transaction
    aggregate = repository.load(command.aggregateId)
    aggregate.apply(command)
    repository.save(aggregate)
    outbox.store(aggregate.domainEvents)
    commit transaction
```Cada paso tiene una única responsabilidad. La validación no reemplaza una regla comercial; rechaza anticipadamente el formulario inválido. La autorización comprueba si la decisión pertenece al propietario. El agregado conserva las invariantes. Outbox, por otro lado, garantiza que el estado permanente y el evento a publicar ocurran dentro del mismo límite de transacciones.

En el lado de C#, Mediator hace que este flujo sea práctico:```csharp
public sealed record PlaceOrder(Guid CustomerId, IReadOnlyList<OrderLine> Lines) : IRequest<OrderId>;

public sealed class PlaceOrderHandler : IRequestHandler<PlaceOrder, OrderId>
{
    public async Task<OrderId> Handle(PlaceOrder command, CancellationToken ct)
    {
        var order = Order.Place(command.CustomerId, command.Lines);
        await repository.AddAsync(order, ct);
        await outbox.AddAsync(order.DomainEvents, ct);
        return order.Id;
    }
}
```El controlador sigue siendo corto porque las responsabilidades transversales, como auditoría, medición, validación o reintento, se trasladan a comportamientos de canalización. La ventaja de esto es que no todos los manipuladores reproducen manualmente el mismo comportamiento. El precio es el riesgo de perder visibilidad de la cadena de llamadas. Por lo tanto, la secuencia de comportamiento debe documentarse y observarse claramente.

## Canal de consultas: lea todo lo que necesite

En cuanto a las consultas, la pregunta básica es: ¿Qué información desea el usuario y dentro de qué presupuesto de latencia? La respuesta a esta pregunta no es el modelo de dominio, sino el escenario de uso.```text
API
  → Authorization for the view
  → Query Handler
  → Read model / cache / search index
  → DTO shaped for the screen
```A menudo no es necesario recargar el agregado `Order`, las relaciones con los clientes y las reglas de inventario para una lista de pedidos. El controlador de consultas selecciona directamente los campos que necesita la pantalla. Por lo tanto, el costo se limita aproximadamente al número de filas y columnas devueltas en lugar de a todo el gráfico de dominio.```csharp
public sealed record GetOrderSummary(Guid OrderId) : IRequest<OrderSummaryDto?>;

public sealed class GetOrderSummaryHandler : IRequestHandler<GetOrderSummary, OrderSummaryDto?>
{
    public Task<OrderSummaryDto?> Handle(GetOrderSummary query, CancellationToken ct) =>
        readDb.OrderSummaries
            .Where(x => x.Id == query.OrderId)
            .Select(x => new OrderSummaryDto(x.Id, x.Status, x.Total, x.UpdatedAt))
            .SingleOrDefaultAsync(ct);
}
```Aquí `DTO` no es una decoración para ocultar el dominio; Es un acuerdo de lectura. El modelo de lectura puede cambiar cuando cambia la pantalla. El modelo de comando puede cambiar cuando cambian las reglas comerciales. Los dos cambios no tienen por qué ocurrir al mismo ritmo.

## CQRS lógico y CQRS físico

El primer paso es CQRS lógico para la mayoría de los equipos: el comando y la consulta están separados en el código, pero utilizan la misma base de datos relacional. Las transacciones ACID están protegidas, los costos operativos son bajos y la depuración es sencilla.

En CQRS físico, el modelo de lectura se mueve a un almacén de datos, caché o índice de búsqueda independiente. Esto brinda la oportunidad de escalar el lado de lectura de forma independiente; Pero a cambio viene la frescura de los datos, los errores de proyección y los procesos de reconstrucción. Por lo tanto, la separación física no es una fantasía escénica sino la respuesta a un cuello de botella comprobado.

| Elección | Ganancias | Precio aceptado |
| --- | --- | --- |
| CQRS lógico | Bajo costo operativo, fuerte consistencia | La lectura y la escritura comparten la misma infraestructura |
| CQRS físico | Escalado independiente, modelos ajustados a pantalla | Consistencia final, operación de proyección |

## Del modelo de escritura al modelo de lectura: ¿CDC o Bandeja de salida?

La cuestión crítica en la separación física es cómo fluyen los datos. Change Data Capture puede capturar cambios en la base de datos a partir de registros; Puede ser potente para reproducir sin tocar los sistemas existentes. Pero acerca el contrato de mensaje al esquema de la base de datos.

El enfoque de la bandeja de salida escribe el evento del dominio en la tabla de la bandeja de salida en la misma transacción. Un relé lleva estos registros al corredor; Los consumidores se comportan idempotentes. Así, el dilema `database write başarılı, event publish başarısız` se vuelve manejable. Pero la retransmisión, el reintento, el seguimiento de mensajes no entregados y la idempotencia del consumidor son ahora parte del diseño.```text
Command commit
  → Outbox record
  → Relay publishes event
  → Projection consumes event
  → Read model updates
```En este flujo, es más realista diseñar al menos una entrega y un procesamiento idempotente en lugar de promesas exactamente una vez. Por ejemplo, la proyección registra el ID del evento; Si el mismo evento vuelve a ocurrir, no cambia la vista por segunda vez.

## ¿Por qué fracasan las soluciones ingenuas?

- Envío de un mensaje directamente al broker desde el Handler: es posible que el mensaje ya se haya consumido mientras se revierte la transacción.
- Carga agregada para cada consulta: las pantallas de lista se vuelven innecesariamente costosas con reglas de dominio y uniones.
- Considerar el retraso en la lectura del modelo como un error: algunas pantallas requieren datos nuevos, otras toleran segundos de retraso. Esta distinción debe determinarse con el producto.
- Resolver todos los problemas con CQRS físico: el segundo almacén de datos no es solo tecnología sino una sobrecarga operativa permanente.

## Lista de verificación de decisiones

1. ¿Están activados el interruptor de idempotencia y condición de éxito del comando?
2. ¿Se conservan todos los invariantes en el límite agregado?
3. ¿La consulta se configura según el DTO del escenario de uso en lugar del dominio?
4. ¿Se define una expectativa en el lenguaje del producto para el retraso en la lectura del modelo?
5. ¿Cómo se monitoreará y verificará el sistema cuando se reconstruya Projection?

CQRS no es un billete automático a una escala ilimitada. Cuando se usan correctamente, las decisiones se pueden tomar en un proceso; Transforma la lectura en un modelo que se adapta a las necesidades. De esta manera, el sistema se vuelve más visible y más resistente al cambio.

En la siguiente sección, esta lógica irá más allá del límite del servicio único: examinaremos CQRS con corredores de eventos, proyecciones y estrategias de reversión en sistemas distribuidos.

## ¿Qué es el modelo de lectura?

El modelo de lectura no es una copia del modelo de dominio en el lado de escritura; Es la vista preparada para las necesidades de una pantalla.```text
Orders (write model)
Id | CustomerId | Status | Lines | Rules
          ↓ Projection
OrderSummary (read model)
Id | CustomerName | Status | Total | BadgeColor
```## CDC frente a Bandeja de salida

| Criterio | CDC | Bandeja de salida |
| --- | --- | --- |
| Intención del evento de dominio | Indirecto | Abrir |
| Dependencia del esquema de base de datos | Alto | inferior |
| Repetir | Fuerte | Fuerte |
| Control del contenido del evento | Limitado | Completo |
| Comunicación de microservicios | Depende | Muy asequible |

## ¿Por qué las soluciones ingenuas fallan en la producción?```text
Handler
  → DbContext.SaveChanges()
  → Message publish
```Si el segundo paso falla, los datos se escriben y el evento se pierde. O, mientras se revierte la transacción, es posible que la siguiente llamada ya haya producido un efecto secundario:```text
await emailService.Send(...)
```Por lo tanto, Outbox toma el evento a publicar con el cambio permanente en el mismo límite de transacciones; Los efectos secundarios, como el correo electrónico, se manejan después de la confirmación, en consumidores resistentes a los reintentos.

## ¿Cómo fluye una solicitud real?```text
POST /orders
  ↓
Controller
  ↓
Mediator.Send()
  ↓
Validation → Authorization → Logging → Metrics → Transaction
  ↓
Handler
  ↓
Aggregate
  ↓
Repository + Outbox
  ↓
Commit
  ↓
Relay → Broker / Kafka
  ↓
Projection
  ↓
Read database
  ↓
GET /orders → Query handler → DTO → Frontend
```CQRS no es una báscula automática. Pero si puede explicar por qué existe cada paso del proceso, introducirá una separación consciente de responsabilidades en su sistema. Si no puede explicarlo, es posible que aún no esté listo para usar CQRS.

## ¿Por qué debería saber sobre este flujo?

Si sabe por qué capas pasa una solicitud:

- No escribe Validación aleatoriamente en el Controlador o controlador.
- No colocas la regla de negocio en la capa HTTP, la proteges en el límite agregado.
- No abres la transacción antes de lo necesario.
- No amplías los manipuladores con códigos de registro, autorización y medición.
- Trasladar preocupaciones transversales al oleoducto.

La diferencia en el lado de la consulta es igualmente práctica:```text
Sipariş detayı
  → Order aggregate
  → kurallar ve davranış için doğru model

Sipariş listesi
  → OrderSummaryDto
  → ekrana hızlı ve dar bir görünüm
```Ver esta distinción significa que CQRS no es un diseño de carpeta; Te permite usarlo como disciplina para poner cada responsabilidad en el lugar correcto.

FAQ

Frequently asked questions

¿Qué es el controlador?

Ejecuta el flujo de trabajo de la aplicación de un único comando o consulta.

¿Qué es el mediador?

Garantiza que el controlador envíe la solicitud al controlador correcto sin conocerlo directamente.

¿Qué le dice "¿Cómo funciona la canalización CQRS (segregación de responsabilidad de consultas de comandos)? Anatomía del flujo de comandos y consultas"?

¿Qué es la canalización de solicitudes CQRS? ¿Cómo procede una solicitud HTTP a través del controlador, MediatR, comportamiento de canalización, controlador, bandeja de salida y modelo de lectura?

Principios de ingeniería aprendidos

  • CQRS no son dos bases de datos; Es aceptar que leer y escribir son responsabilidades diferentes.
  • Pipeline elimina controles repetitivos de los manejadores; deja visible la decisión empresarial.
  • La separación física sólo es valiosa cuando la latencia, los reintentos y la actualización de los datos se diseñan como una decisión del producto.

Continuar leyendo

Continuar leyendo

Siguiente en la serie

Siguiente en la serie

Misma serie

Paylaş