Integración entre bounded contexts | Nicolás Garzón
Bounded contexts necesitan colaborar sin compartir internals ni imponer un modelo universal. La integración correcta depende de qué información necesita el consumidor, cuánto puede esperar y qué consistencia exige el negocio.
Cada contexto posee su lenguaje y modelo. Integrar no significa fusionarlos, sino establecer contratos y traducciones explícitas.
Texto
Copiar contexto proveedor
→ contrato de integración
→ traducción
→ modelo local del consumidorLa forma de colaboración —síncrona, asíncrona o mediante datos replicados— cambia latencia, disponibilidad, consistencia y operación.
Si Pedidos lee directamente tablas de Inventario:
Depende de su schema.
Puede saltarse invariantes.
Cambios requieren coordinación.
No existe ownership claro.
Si ambos comparten una librería de entidades:
El significado debe ser universal.
Los despliegues se coordinan.
El modelo de un contexto invade otro.
La integración debe permitir autonomía sin ocultar los costos de comunicación.
Texto
Copiar Orders → Inventory.reserve → resultado inmediatoConviene cuando el consumidor necesita una respuesta antes de continuar.
Flujo fácil de seguir.
Resultado inmediato.
Errores visibles al caller.
Acoplamiento temporal.
Latencia compuesta.
Disponibilidad dependiente.
Timeouts y resultados desconocidos.
Riesgo de cadenas síncronas.
El contrato debe distinguir:
TypeScript
Copiar type ReservationResult =
| { type: "reserved" ; reservationId: string }
| { type: "insufficient-stock" }
| { type: "unknown" ; requestId: string } ; Un timeout no equivale a rechazo. Puede requerir consulta o reconciliación.
Texto
Copiar Orders publica OrderConfirmed
→ Notifications envía mensajes
→ Reporting actualiza proyecciónRepresenta un hecho ya ocurrido. Reduce acoplamiento temporal y permite fan-out.
Duplicados.
Desorden.
Consistencia eventual.
Schemas duraderos.
Diagnóstico distribuido.
El consumidor debe ser idempotente y tolerar retrasos.
Indica que algo cambió y obliga a consultar al proveedor.
JSON
Copiar { "type" : "ProductUpdated" , "productId" : "p-1" } Contrato pequeño, pero crea llamadas y dependencia de disponibilidad.
Incluye datos suficientes para actualizar una proyección local.
JSON
Copiar {
"type" : "ProductPriceChanged" ,
"productId" : "p-1" ,
"price" : { "minorUnits" : 150000 , "currency" : "COP" } ,
"version" : 12
} Mejora autonomía de lectura, pero amplía exposición y compatibilidad.
Un contexto puede mantener una copia local derivada.
Texto
Copiar Catalog es fuente de verdad
→ publica cambios
→ Orders mantiene ProductSnapshot
Retraso tolerable.
Versiones.
Replay.
Reconciliación.
Comportamiento ante divergencia.
La réplica no se convierte en autoridad.
Traduce lenguaje externo:
Texto
Copiar Payments:
requires_capture
Orders:
paymentAuthorizedLa ACL puede incluir adapters, mappers, facades y políticas. Es especialmente útil frente a legacy o proveedores con modelos ajenos.
Un contrato publicado mediante OpenAPI, AsyncAPI, Protobuf o JSON Schema ayuda a compartir forma, pero debe acompañarse de semántica, ejemplos, compatibilidad y errores.
Upstream y downstream negocian evolución.
El consumidor adopta el modelo del proveedor. Reduce traducción, aumenta dependencia.
Ambos coordinan estrechamente. Útil cuando el éxito es compartido, costoso si se vuelve permanente.
Los contextos evitan integración porque el beneficio no justifica el costo.
Pedidos necesita conocer el precio utilizado al confirmar.
Consultar Catálogo sincrónicamente.
Mantener snapshot local actualizado por eventos.
Incluir precio dentro del command desde una sesión confiable.
Frescura exigida.
Disponibilidad.
Autoridad del precio.
Necesidad de auditoría.
Una solución razonable es que Orders consulte o reciba el precio autorizado y lo guarde como snapshot del pedido. Cambios posteriores de Catálogo no alteran pedidos históricos.
Cambios seguros no son únicamente aditivos. Agregar un enum puede romper consumidores exhaustivos.
Schemas versionados.
Consumers tolerantes.
Deprecación medida.
Expand-and-contract.
Contract tests.
Telemetría de versiones.
Usa versión por entidad y descarta estados antiguos.
Guarda event ID o aplica operación idempotente.
Define fallback, estado pendiente o degradación. No uses retries ilimitados.
Duplica representación local si evita coordinación semántica. La duplicación de datos puede ser preferible al acoplamiento.
Crea proyección o composición; no permitas escritura cruzada.
Shared-domain global.
Lectura directa de tablas.
Eventos con modelo interno completo.
Llamadas síncronas encadenadas.
Ignorar timeout y resultado desconocido.
Contract tests solo de schema.
Consumers que exigen orden global.
Contract tests.
Pruebas de compatibilidad.
Simulación de duplicados y desorden.
Fallos de proveedor.
Replay y reconciliación.
Métricas de lag y errores.
Trazas con correlation ID.
Integrar no significa compartir modelo.
Síncrono ofrece respuesta inmediata y acoplamiento temporal.
Asíncrono ofrece autonomía y consistencia eventual.
Réplicas necesitan fuente de verdad y reconciliación.
Una ACL protege lenguaje propio.
Compatibilidad incluye semántica y temporalidad.
¿Cuándo preferirías una proyección local?
¿Por qué un timeout no equivale a fallo definitivo?
¿Qué costo introduce event-carried state?
¿Cuándo Conformist puede ser razonable?
¿Cómo manejarías eventos fuera de orden?
Estilos arquitectónicos y criterios de elección compara estructuras posibles según los drivers y capacidades del equipo.