Domain-Driven Design: Partnership, cómo colaborar entre Bounded Contexts con objetivos y responsabilidades compartidas.
Aprende a coordinar Sales y Payments cuando su entrega depende del éxito de ambos. Define responsabilidades y contratos, diseña pruebas conjuntas y planifica cambios compatibles sin fusionar modelos ni asumir transacciones compartidas.
Dos entregas terminadas que todavía no permiten vender
Sales termina la pantalla de confirmación. Payments termina la integración con la pasarela de pruebas. Cada equipo demuestra su parte, pero la compra no funciona de principio a fin: Sales interpreta «autorizado» como «pagado» y Payments no sabe qué referencia debe devolver para identificar el pedido.
Ambos pueden tener código correcto para sus propios supuestos y fallar en la entrega conjunta.
El patrón Partnership, o asociación, permite organizar una relación donde los equipos necesitan colaborar para alcanzar un resultado que ninguno puede entregar solo. No define cómo enviar HTTP ni qué broker instalar. Define cómo coordinar decisiones, compromisos e integración sin fusionar los modelos.
Trabajaremos con una tienda hipotética: Sales y Payments quieren habilitar una compra cuyo pedido se confirme después de comprobar el cobro. Los ejemplos se limitan a contratos y lectura de resultados; no procesan pagos reales.
Qué hace que exista una Partnership
Evans describe Partnership cuando el fracaso de desarrollo en cualquiera de los contextos compromete la entrega de ambos. Los equipos coordinan planificación, evolución de interfaces e integración para alcanzar el resultado compartido. DDD Reference: Partnership, página 30 del documento.
La dependencia es de éxito, no solamente de tráfico:
Payments necesita que Sales entregue una referencia estable y condiciones de cobro correctas para la capacidad acordada.
Sales necesita que Payments permita conocer el resultado y asociarlo al pedido correcto.
La funcionalidad comercial no está terminada si solo una de esas partes funciona.
Hacer dos llamadas en sentidos opuestos no demuestra Partnership. Una integración puede tener peticiones y notificaciones bidireccionales y mantener una relación organizacional asimétrica.
Tampoco implica igualdad absoluta de presupuesto o autoridad. Exige que los responsables puedan comprometerse, resolver obstáculos juntos y atender las necesidades de ambos en el alcance elegido.
Representar la relación sin dibujar infraestructura
[ Sales ] <========== Partnership ==========> [ Payments ]
Objetivo: compra con cobro comprobado
Planificación y aceptación conjuntas
Sales conserva el modelo del pedido.
Payments conserva el modelo del pago.
La línea doble representa colaboración y dependencia de éxito mutuas. No significa memoria compartida, llamada síncrona ni transacción distribuida.
Podemos acompañarla con un contrato para solicitar un cobro y otro para consultar su resultado. Cada interacción técnica tendrá un proveedor y un consumidor, aunque el acuerdo estratégico entre equipos sea de Partnership.
Definir un resultado compartido y responsabilidades separadas
«Integrar pagos» es demasiado amplio. Para esta primera capacidad, proponemos:
Una compra puede mostrar un pedido confirmado cuando Sales verifica un resultado de cobro capturado que corresponde al pedido, al importe y a la moneda esperados. Los resultados pendientes o desconocidos no se presentan como confirmación.
Es una política del ejemplo. Otras tiendas pueden confirmar pedidos con una autorización y capturar más adelante. La elección debe ser explícita; no puede deducirse del nombre SUCCESS de una pasarela.
Sales decide las condiciones comerciales, el importe esperado y qué significa confirmar su pedido.
Payments decide cómo representar e interpretar los resultados financieros que obtiene de su proveedor. No delega su adaptación a la pasarela en Sales.
Ambos acuerdan referencias, estados públicos, errores y ejemplos de aceptación. También deciden qué ocurre cuando la respuesta llega tarde, falta o contradice los datos esperados.
Compartir objetivo no significa que cualquiera pueda actualizar las tablas del otro contexto. Mantener propietarios claros ayuda a resolver problemas sin crear una responsabilidad colectiva indefinida.
Planificar una capacidad que pueda integrarse pronto
En lugar de terminar todo Payments y conectar Sales al final, los equipos pueden acordar una primera secuencia:
Escribir ejemplos de pedido, importe y resultado aceptado.
Definir la representación pública mínima y sus errores.
Probar la lectura en Sales con respuestas controladas.
Conectar con Payments en un entorno de pruebas aislado.
Recorrer juntos un caso capturado, uno pendiente y uno fallido.
Habilitar la capacidad cuando ambas partes cumplan los criterios.
Este es un plan propuesto, no una afirmación sobre entregas de un equipo real. Hay que asignar responsables y fechas según su capacidad.
Si aparece un bloqueo, se evalúa su efecto sobre la funcionalidad conjunta. Payments no puede declarar terminada una interfaz imposible de utilizar; Sales tampoco puede imponer una solución que el proveedor no puede sostener.
Una reunión breve para decidir una ambigüedad puede ser suficiente. Partnership no exige convertir cualquier ajuste interno en un comité.
Contratos explícitos sin compartir entidades
Para identificar una intención de cobro, los equipos podrían acordar este DTO de entrada:
Es un fragmento de contrato, no una operación ejecutable. amountMinor expresa céntimos de EUR; attemptId identifica el intento lógico según las reglas acordadas. El proveedor debe definir cómo trata repeticiones y discrepancias. Incluir un identificador no implementa automáticamente esa protección.
PENDING: aún no hay un resultado final que permita confirmar.
CAPTURED: Payments declara el importe capturado para ese intento.
DECLINED: el intento ha sido rechazado.
No añadimos un estado AUTHORIZED para interpretarlo por intuición. Si se necesita, hay que discutir su significado y su efecto en el proceso.
El contrato es una representación pública, no una serialización del agregado de Payments. Sales no necesita conocer credenciales, identificadores internos de la pasarela o su modelo completo de transacciones.
Una interfaz TypeScript tampoco valida datos de red. Los dos lados deben comprobar sus entradas y el consumidor debe tratar la respuesta externa como desconocida antes de traducirla.
Un fragmento de decisión en Sales
Sales define un puerto local de consulta. Su adaptador HTTP valida el contrato y lo traduce a este resultado del consumidor; no importa entidades internas de Payments.
Este bloque es autocontenido como decisión didáctica y no necesita un framework. Presupone que el adaptador implementa la validación en ejecución de PaymentEvidence, incluyendo tipos, identificadores y rangos numéricos.
ready-to-confirm no cambia un pedido ni registra un pago. Un caso de uso real de Sales tendría que aplicar y persistir la transición permitida por su modelo, teniendo en cuenta concurrencia, repeticiones y resultados ya procesados. Esas piezas se omiten para concentrarnos en el acuerdo de colaboración.
La función tampoco realiza una reserva de importe o de existencias. Probar el resultado compartido requiere recorrer más que este fragmento.
Por qué la colaboración no crea una transacción común
Payments puede haber registrado un cobro y Sales fallar antes de persistir la confirmación. Una respuesta puede perderse aunque la operación financiera haya ocurrido.
Por tanto:
Un timeout no significa automáticamente que no se cobró.
Un fallo local no deshace el estado remoto.
Repetir una petición de cobro sin conocer su contrato puede producir un efecto no deseado.
Revertir una versión de código no revierte operaciones financieras.
Los equipos deben acordar cómo identificar el intento y cómo consultar y resolver situaciones pendientes. Aquí no implementamos ese proceso ni introducimos una arquitectura de coordinación distribuida.
La responsabilidad compartida consiste en reconocer y asignar esas situaciones, no en afirmar que desaparecen por trabajar juntos.
Esta precaución también aplica a un monolito modular cuando la integración se realiza por HTTP con contratos separados. No se debe asumir una transacción compartida por estar en el mismo servidor.
Pruebas conjuntas con responsabilidades identificables
Una estrategia acotada puede tener tres niveles.
Pruebas locales: Sales verifica cómo interpreta resultados; Payments verifica sus reglas y su traducción del proveedor financiero.
Pruebas de contrato: comprueban que las peticiones y respuestas públicas satisfacen expectativas de ambas partes.
Pruebas de integración del recorrido: conectan implementaciones reales en un entorno controlado y verifican el resultado acordado. Pueden utilizar una pasarela de pruebas, nunca cobros reales para este ejercicio.
No basta con que ambos mantengan mocks distintos que se parecen al contrato. Las expectativas deben contrastarse con implementaciones verificables.
Una prueba local que identifica una regla de Sales
Este ejemplo complementa el archivo anterior, supuesto como confirmation.ts, y utiliza APIs integradas de Node.js. Presupone compilación de TypeScript a JavaScript ESM. Es una prueba propuesta; no se ha ejecutado como parte del artículo.
Estas pruebas muestran dos expectativas locales. No prueban la persistencia de Sales ni que Payments publique el mismo contrato.
Una especificación de integración compartida
Para probar el recorrido sin infraestructura incidental, podemos expresar un arnés de pruebas. Sus funciones deben conectarse a adaptadores HTTP reales y a la preparación aislada del escenario.
Es una especificación ejecutable cuando se implementa el arnés, no una prueba de integración completa lista para ejecutar. No se proporciona ese arnés ni se afirma haberlo ejecutado.
prepareScenario debe preparar Sales y Payments con identificadores, importe y moneda coherentes en una base de pruebas o entorno aislado. runSalesConfirmation debe invocar el recorrido real de Sales, que utiliza la integración con Payments y persiste su resultado. No debe simular directamente la confirmación esperada.
El arnés pertenece al ámbito de pruebas. No es una puerta administrativa de producción ni permiso para acceder a tablas ajenas desde el código de negocio.
Los equipos deben agregar escenarios de rechazo, referencias incorrectas, respuestas inválidas y pérdida de comunicación. El responsable de cada fallo puede ser local, pero su efecto sobre la entrega se evalúa conjuntamente.
Evolucionar juntos sin desplegar todo a la vez
Supongamos que aparece una nueva modalidad de pago: algunas operaciones requieren acción adicional del comprador. El contrato inicial no distingue esa situación de un simple pendiente.
Publicar ACTION_REQUIRED sin coordinación rompería a un consumidor que rechaza estados desconocidos. Tratarlo automáticamente como capturado sería peor.
Los equipos pueden diseñar una evolución compatible:
Acordar el nuevo estado, la acción esperada y su presentación en Sales.
Preparar una versión del contrato que lo distinga.
Hacer que Payments soporte la versión anterior mientras la nueva no esté habilitada para consumidores migrados.
Actualizar el adaptador y la experiencia de Sales.
Probar ambos recorridos.
Activar la capacidad para la versión nueva cuando las partes estén preparadas.
Retirar la anterior solo después de comprobar la migración de sus consumidores.
Si el contrato anterior define PENDING simplemente como «no hay resultado final», Payments puede seguir utilizándolo en esa versión mientras Sales todavía no ofrece la experiencia interactiva. Si esa semántica no encaja, debe mantenerse la nueva modalidad deshabilitada para ese consumidor; no se debe forzar una equivalencia falsa.
Esto exige planificar compatibilidad y activación. Terminar una funcionalidad coordinadamente no obliga a desplegar los binarios en el mismo instante. El orden concreto depende de cómo conviven las versiones y de cuándo se habilita el nuevo comportamiento.
Tampoco basta con agregar un campo «opcional»: si el nuevo proceso solo funciona cuando el consumidor lo utiliza, hay una dependencia de capacidad aunque el JSON siga siendo válido.
Documentar lo que ambos pueden sostener
Una ficha de Partnership puede registrar:
Objetivo compartido: habilitar confirmación de ventas con evidencia de cobro válida. Sales: condiciones del pedido, decisión comercial y persistencia de su estado. Payments: interpretación financiera y consulta del intento. Integración: contratos, traducciones HTTP del consumidor y escenarios acordados. Entrega: responsables, criterios de aceptación y secuencia de habilitación. Fallos: quién investiga pagos sin confirmación y cómo se da seguimiento. Revisión: cambios de estados, importes, referencias o garantías.
Las fechas y los compromisos se completan mediante acuerdos reales. No se deducen del diagrama.
Una nota breve de decisión puede explicar por qué se eligió Partnership, qué alcance tiene y qué condiciones la harían cambiar. No hace falta convertir cada contrato en un documento extenso.
Si todos los cambios de Sales requieren revisar Payments, pregunta si se ha ampliado demasiado la coordinación. El objetivo compartido es concreto; no debería absorber todo el trabajo independiente.
Cuándo Partnership no es la descripción adecuada
Customer/Supplier: suele haber una dirección dominante de dependencia y un proveedor que incorpora necesidades del consumidor en su planificación. Puede existir excelente colaboración sin la dependencia mutua de éxito que justifica Partnership.
Shared Kernel: se comparte una parte del modelo y su evolución. Sales y Payments pueden ser socios sin compartir clases ni una biblioteca de entidades.
Conformist: el consumidor adopta el modelo externo sin una influencia suficiente sobre su evolución. No describe una planificación conjunta entre responsables capaces de comprometerse.
Una pasarela comercial no se convierte en socio de desarrollo porque Payments consuma su API. Partnership puede existir entre Sales y Payments, mientras Payments mantiene otra relación con su proveedor externo.
Dos situaciones que ayudan a evaluar el patrón
Educación: matrícula y asignación de plaza
Inscripciones y planificación académica lanzan juntos una nueva modalidad de matrícula con plazas por grupo. Cada parte necesita capacidades nuevas de la otra para que la funcionalidad pueda entregarse.
Si ambos equipos pueden planificar y verificar el recorrido conjuntamente, Partnership puede ser apropiado. Si uno solo ofrece una consulta estable utilizada por muchos consumidores y no depende de esa entrega, una relación asimétrica puede describir mejor la situación.
El aprendizaje es mirar la dependencia de éxito de la capacidad, no la cantidad de llamadas.
Reservas: evitar coordinación innecesaria
Una aplicación de reservas utiliza una herramienta externa para enviar correos informativos. Si el proveedor no participa en su planificación, etiquetar la integración como Partnership oculta la falta de influencia.
Para una primera versión, incluso puede ser suficiente mostrar una confirmación en pantalla y añadir avisos después, si el negocio acepta ese alcance. La opción más sencilla reduce una dependencia que no es necesaria para la entrega inicial.
Un fallo de comunicación entre equipos no se resuelve automáticamente aumentando reuniones. A veces hay que reducir el alcance compartido o revisar los límites.
Dar a la IA acuerdos, no solo diagramas
Para Cursor, Claude o Codex, combina el Context Map con responsabilidades, ejemplos y límites de la entrega.
Para analizar la relación:
Analiza estas dependencias de Sales y Payments para esta funcionalidad. Distingue dependencia técnica de dependencia de éxito. Justifica Partnership o una relación asimétrica. Señala compromisos que faltan sin inventar acuerdos ni fechas.
Para implementar una parte:
Trabaja únicamente en Sales. Consume evidencia de pago mediante un puerto local y una ACL HTTP con contrato del consumidor. No importes agregados ni repositorios de Payments. CAPTURED debe coincidir con intento, pedido, importe y moneda. Un timeout no demuestra que no hubo cobro.
Para revisar la evolución:
Con este contrato actual y esta nueva capacidad, propón un orden compatible de entrega, despliegue y activación. Identifica combinaciones de versiones que fallan, escenarios conjuntos y responsabilidades. No supongas una transacción común ni reversiones automáticas del cobro.
Una persona debe verificar que cada compromiso es posible y está aceptado. La IA puede señalar contradicciones; no puede decidir por otro equipo que una entrega está comprometida.
Ejercicios
1. Dos llamadas, una dependencia dominante
Sales consulta una API estable de Payments y recibe notificaciones. Payments no necesita ninguna capacidad nueva de Sales para su propia entrega. ¿La comunicación bidireccional basta para afirmar Partnership?
2. El nuevo estado
Payments quiere devolver ACTION_REQUIRED al consumidor antiguo sin cambiar su parser porque «es parecido a pendiente». ¿Qué revisarías?
3. El despliegue de vuelta atrás
Payments registra el cobro, Sales falla y el equipo propone revertir el despliegue para deshacer la operación. ¿Qué supuesto es incorrecto?
Soluciones razonadas
1. No. El transporte en ambos sentidos no demuestra dependencia mutua de éxito. Revisaría objetivos, compromisos y capacidad real de planificación conjunta; una relación asimétrica puede ser más precisa.
2. Revisaría semántica, compatibilidad y experiencia necesaria en Sales. El consumidor antiguo puede rechazar el nuevo valor o no ofrecer la acción requerida. Hay que acordar versiones, habilitación y escenarios antes de activarlo.
3. Revertir código no revierte un estado financiero remoto. Los responsables deben consultar la evidencia y resolver el caso según acuerdos explícitos. Partnership obliga a asignar ese problema, no proporciona una transacción automática.
Colaborar sin borrar los límites
Partnership aporta valor cuando una capacidad necesita que dos equipos alcancen juntos un resultado y ambos pueden comprometerse con esa colaboración.
Comprueba que el objetivo es verificable, que cada responsabilidad tiene propietario y que las versiones pueden integrarse sin supuestos ocultos. Si la coordinación invade cualquier cambio local, reduce el alcance o revisa la relación.
El acuerdo permite construir una entrega común. Los modelos conservan sus límites y las garantías técnicas siguen necesitando diseño explícito.
Diseña una interfaz común de Inventory para Sales, Shipping y Marketplace. Define disponibilidad y reservas con TypeScript y Express, valida entradas y errores y planifica pruebas y evolución sin exponer el modelo interno.
Construye una ACL para integrar un proveedor de pagos sin filtrar sus estados al dominio. Define un contrato interno, valida respuestas, traduce identidades y significados y distingue errores técnicos de resultados del negocio con TypeScript.
Aprende cuándo adoptar el modelo de un proveedor sin influencia sobre sus decisiones. Explora un catálogo externo con TypeScript, validación y errores, reconoce el acoplamiento semántico y decide cuándo una ACL aporta autonomía.