CQRS y división de lectura/escritura
Qué es CQRS
CQRS (Command Query Responsibility Segregation) es un enfoque arquitectónico que separa el modelo de datos y los componentes responsables de escribir (commands) y leer (queries).
Idea: el proceso de cambio de estado se optimiza para las invariantes válidas y las transacciones, y la lectura para las proyecciones rápidas, dirigidas y escaladas.
¿Por qué necesitas esto?
Rendimiento de lectura: proyecciones materializadas para escenarios específicos (cintas, informes, directorios).
Estabilidad de la vía crítica: la grabación está aislada de joines y agregados «pesados».
Libertad de selección de almacenamiento: OLTP para escribir, OLAP/caché/motores de búsqueda para leer.
Evolución acelerada: añade nuevas representaciones sin riesgo de «romper» las transacciones.
Observabilidad y auditoría (especialmente en la combinación con Event Sourcing): es más fácil recuperar y reproducir la condición.
Cuándo aplicar (y cuándo no)
Adecuado si:- Prevalecen las lecturas con diferentes cortes de datos y agregación compleja.
- La ruta de grabación crítica debe ser sutil y predecible.
- Necesita diferentes SLO/SLA para leer y escribir.
- Requiere el aislamiento de la lógica de registro de dominio de las necesidades analíticas/de búsqueda.
- El dominio es simple, la carga es baja; CRUD maneja.
- Una fuerte consistencia entre lectura y escritura es obligatoria para todos los escenarios.
- El equipo es inexperto, y la complejidad operativa es inaceptable.
Conceptos básicos
Comando (Command): permite cambiar el estado ('CreateOrder', 'CapturePayment'). Comprueba los invariantes.
Consulta: obtención de datos ('GetOrderById', 'ListUserTransactions'). Sin efectos secundarios.
Modelo de registro: agregados/invariantes/transacciones; almacenamiento - relacional/clave-valor/registro de eventos.
Modelo de lectura (proyección): tablas/índices/caché materializados, sincronizados asíncrónicamente.
Consistencia: a menudo eventual entre escritura y lectura; caminos críticos - a través de la lectura directa desde el modelo write.
Arquitectura (esqueleto)
1. Servicio de escritura: acepta comandos, valida invariantes, registra cambios (DB o eventos).
2. Outbox/CDC: publicación garantizada del hecho de los cambios.
3. Procesadores de proyección: escucha eventos/CDC y actualiza modelos de lectura.
4. Servicio de lectura: sirve queries de vistas/cachés/búsquedas materializadas.
5. Sagas/orquestación: coordinan los procesos de agregación cruzada.
6. Observabilidad: maga de proyecciones, porcentaje de aplicaciones exitosas, DLQ.
Diseño de modelos de escritura
Agregados: límites claros de las transacciones (por ejemplo, 'Orden', 'Pago', 'Balance de usuarios').
Invariantes: formalizar (sumas de dinero ≥ 0, singularidad, límites).
Los comandos son idempotentes por clave (por ejemplo, 'idempotency _ key').
Las transacciones son mínimas en cobertura; efectos secundarios externos - a través de outbox.
Ejemplo de comando (pseudo-JSON)
json
{
"command": "CapturePayment",
"payment_id": "pay_123",
"amount": 1000,
"currency": "EUR",
"idempotency_key": "k-789",
"trace_id": "t-abc"
}
Diseño de modelos de lectura
Repita las consultas: ¿Qué pantallas/informes necesita?
La denormalización es admisible: el modelo de lectura es «caché optimizado».
Varias proyecciones para diferentes tareas: búsqueda (OpenSearch), informes (almacenamiento de columnas), tarjetas (KV/Redis).
TTL y recomposición: las proyecciones deben ser capaces de recuperarse de la fuente (réplica de eventos/snapshot).
Coherencia y UX
Consistencia eventual: la interfaz puede mostrar brevemente datos antiguos.
Patrones UX: «los datos se actualizan»..., UI optimista, indicadores de sincronización, bloqueo de actividades peligrosas antes de la confirmación.
Para las operaciones que requieren una fuerte consistencia (por ejemplo, mostrar un balance exacto antes de cargar), lea directamente el modelo de escritura.
CQRS y Event Sourcing (opcional)
Event Sourcing (ES) almacena los eventos y el estado del agregado es el resultado de su convolución.
El conjunto CQRS + ES proporciona una auditoría perfecta y una fácil recomposición de las proyecciones, pero aumenta la complejidad.
Alternativa: OLTP-BD + outbox/CDC → proyección convencional.
Replicación: Outbox y CDC
Outbox (en una sola transacción): registro de cambios de dominio + registro de eventos en outbox; pablisher entrega en el neumático.
CDC: lectura desde el registro de la DB (Debezium, etc.) → transformación en eventos de dominio.
Garantías: por defecto en-least-once, los usuarios y las proyecciones deben ser idempotentes.
Selección de almacenamiento
Escritura: relacional (PostgreSQL/MySQL) para transacciones; KV/Document - donde los invariantes son simples.
Read:- KV/Redis - tarjetas y lecturas clave rápidas;
- Búsqueda (OpenSearch/Elasticsearch): búsqueda/filtros/facetas;
- Columnas (ClickHouse/BigQuery) - informes;
- Caché en CDN - catálogos públicos/contenido.
Patrones de integración
Capa API: Endpoints/servicios individuales para 'comandos' y 'queries'.
Idempotencia: clave de operación en el encabezado/cuerpo; almacenamiento recent-keys con TTL.
Sagas/orquestación: tiempos de espera, compensaciones, repetibilidad de pasos.
Backpressure: limitar el paralelismo de los procesadores de proyección.
Observabilidad
Métricas de escritura: p95/99 latencia de comandos, proporción de transacciones exitosas, errores de validación.
Métricas de lectura: p95/99 consultas, hit-rate kash, carga en el clúster de búsqueda.
Valor de las proyecciones (tiempo y mensajes), tasa DLQ, porcentaje de deduplicaciones.
Treking: 'trace _ id' pasa por el comando → outbox → proyección → query.
Seguridad y cumplimiento
Separación de derechos: diferentes scopes/roles para escribir y leer; el principio de los menos privilegios.
PII/PCI: minimizar en proyecciones; encriptación en línea/en vuelo; enmascaramiento.
Auditoría: fijar el comando, actor, resultado, 'trace _ id'; Archivos WORM para dominios críticos (pagos, KYC).
Pruebas
Pruebas de contrato: para comandos (errores, invariantes) y queries (formatos/filtros).
Projection tests: alimenta una serie de eventos/CDC y revisa el modelo de lectura final.
Chaos/latency: inyección de latencia en los procesadores de proyección; comprobación de UX durante la laguna.
Replayability: Reconfiguración de proyecciones en el stand de snapshots/logs.
Migración y evolución
Los nuevos campos son aditivos en el evento/CDC; Los modelos read se superponen.
Grabación doble (dual-write) en el rediseño de esquemas; las viejas proyecciones se mantienen hasta el cambio.
Versioning: 'v1 '/' v2' eventos y endpoints, Sunset-plan.
Flags de características: introducción de nuevas queries/proyecciones por Canarias.
antipatterny
CQRS «por el bien de la moda» en simples servicios CRUD.
Dependencia rígida de lectura y escritura sincrónica (mata el aislamiento y la estabilidad).
Un índice para todo: mezclar solicitudes heterogéneas en una sola tienda de lectura.
No hay idempotencia en las proyecciones → toma y discrepancia.
Proyecciones no recuperables (sin replay/snapshots).
Ejemplos de dominios
Pagos (servicio en línea)
Escritura: 'Authorize', 'Capture', 'Refund' en el DB transaccional; outbox publica 'payment.'.
Read:- Redis «tarjeta de pago» para IU;
- ClickHouse para informes;
- OpenSearch para buscar transacciones.
- Ruta crítica: autorización ≤ 800 ms p95; consistencia de lectura para IU - eventual (hasta 2-3 c).
KYC
Escribir: comandos en el estado de inicio/actualización; Almacenamiento de PII en una base de datos protegida.
Leer: proyección ligera de los estados sin PII; PII se aprieta puntualmente si es necesario.
Seguridad: diferentes scopes para leer el estado y acceder a los documentos.
Balances (iGaming/finanzas)
Escritura: unidad 'UserBalance' con incrementos/decrementos atómicos; llaves idempotentes para la operación.
Leer: caché para «equilibrio rápido»; para deducir: lectura directa de escritura (coherencia estricta).
Saga: depósitos/retiros son coordinados por eventos, en caso de fallas - compensación.
Lista de comprobación de implementación
- Se han seleccionado agregados e invariantes del modelo write.
- Se identifican las queries clave y se diseñan las proyecciones debajo de ellas.
- Outbox/CDC y procesadores de proyección idempotente están configurados.
- Hay un plan de recomposición de proyecciones (snapshot/replay).
- SLO: latencia de los comandos, valor de las proyecciones, disponibilidad de lectura/escritura por separado.
- Derechos de acceso separados y cifrado de datos implementado.
- Alertas en DLQ/lag/fallas de deduplicación.
- Pruebas: contratos, proyecciones, caos, replay.
FAQ
¿El origen de eventos es obligatorio para CQRS?
No. Se puede construir sobre un BD + outbox/CDC normal.
¿Cómo lidiar con la resincronización?
Diseñar explícitamente UX, medir el valor de las proyecciones, dejar que las operaciones críticas lean de escritura.
¿Es posible mantener tanto write como read en el mismo servicio?
Sí, la separación física es opcional; la separación lógica de responsabilidades es obligatoria.
¿Qué hay de las transacciones entre unidades?
A través de sagas y eventos; evite las transacciones distribuidas, si es posible.
Resultado
CQRS desata las manos: una ruta de escritura sutil y confiable con invariantes claros y lecturas rápidas y dirigidas a partir de proyecciones materializadas. Esto mejora la productividad, simplifica la evolución y hace que el sistema sea más resistente a las cargas, si se administra disciplinadamente la coherencia, la observabilidad y las migraciones.