Customer-Supplier es un patron estrategico de Domain-Driven Design que define la relacion entre dos Bounded Contexts cuando uno de ellos (el Supplier/Upstream) provee capacidades que el otro (el Customer/Downstream) necesita para funcionar.
A diferencia de otros patrones de integracion como Shared Kernel o Conformist, Customer-Supplier establece una relacion donde ambos equipos tienen poder de negociacion: el Supplier no impone unilateralmente su modelo, y el Customer puede influir en la interfaz que consume. Existe un acuerdo formal entre ambas partes.
- Upstream (Supplier): Define y publica una interfaz estable. Tiene la responsabilidad de no romper el contrato con el downstream. Sus cambios internos no deben afectar al consumidor mientras la interfaz se mantenga.
- Downstream (Customer): Consume la interfaz publicada por el upstream. Puede solicitar cambios o nuevas capacidades al supplier. No accede directamente al modelo interno del upstream.
- Interfaz negociada: El contrato entre ambos contextos se define de forma colaborativa. El supplier expone solo lo necesario a traves de una Facade o un servicio de aplicacion, protegiendo su modelo de dominio.
- Autonomia de modelos: Cada Bounded Context mantiene su propio Ubiquitous Language y su modelo de dominio independiente. No hay acoplamiento estructural entre los modelos internos.
- Cuando dos equipos trabajan en Bounded Contexts que tienen una dependencia funcional clara (uno necesita datos o comportamiento del otro).
- Cuando existe una relacion de colaboracion real entre los equipos — el upstream esta dispuesto a dar soporte al downstream.
- Cuando se quiere evitar el acoplamiento fuerte que implica un Shared Kernel, pero se necesita mas coordinacion que en un modelo Conformist.
- Si el upstream no tiene interes en dar soporte al downstream, el patron real es Conformist (el downstream se adapta sin negociacion).
- Si no hay relacion entre los contextos, se usa Separate Ways.
- Si ambos contextos estan tan acoplados que comparten modelo, se usa Shared Kernel.
src/main/java/
├── payments/ # Bounded Context: Payments (Customer/Downstream)
│ ├── domain/
│ │ └── Payment.java # Entidad de dominio
│ └── application/
│ └── PaymentService.java # Servicio de aplicacion — consume la Facade
│
└── accounting/ # Bounded Context: Accounting (Supplier/Upstream)
├── domain/
│ ├── Money.java # Value Object
│ ├── JournalEntry.java # Entidad de dominio (asiento contable)
│ ├── LadgerLine.java # Linea del libro mayor
│ └── LedgerType.java # DEBIT / CREDIT
└── application/
└── port/
├── PaymentAccountingFacade.java # Interfaz publica (contrato)
├── PaymentAccountingFacedeImpl.java # Implementacion del contrato
└── JournalRepository.java # Puerto de persistencia
El contexto accounting publica la interfaz PaymentAccountingFacade como punto de entrada controlado. Esta interfaz es el contrato negociado entre ambos contextos:
public interface PaymentAccountingFacade {
void registerPayment(
String paymentId,
String fromAccount,
String toAccount,
BigDecimal amount,
String currency);
}La firma usa tipos primitivos — no expone Money, JournalEntry, ni ningun otro objeto del modelo interno de accounting. Esto es intencional: el Customer no necesita conocer ni depender del modelo de dominio del Supplier.
El contexto payments depende unicamente de la interfaz, nunca de la implementacion ni del modelo interno:
public final class PaymentService {
private final PaymentAccountingFacade facade;
public void processPayment(final Payment payment) {
// Payment logic
facade.registerPayment(
payment.getId(),
payment.getDebtorAccount(),
payment.getCreditorAccount(),
payment.getAmount(),
payment.getCurrency());
}
}PaymentService no importa nada de accounting.domain. Su unica dependencia hacia el upstream es la interfaz PaymentAccountingFacade.
La implementacion PaymentAccountingFacedeImpl traduce los parametros primitivos a objetos de su propio dominio (Money, JournalEntry) y ejecuta su logica interna:
public void registerPayment(...) {
Money money = new Money(amount, currency);
JournalEntry entry = JournalEntry.forPayment(paymentId, currency, toAccount, money);
entry.validateBalance();
journalRepository.save(entry);
}El Supplier puede refactorizar Money, cambiar como se construye un JournalEntry, o modificar la validacion de balance sin afectar al Customer, siempre que la firma de la Facade se mantenga.
┌─────────────────────────┐ ┌─────────────────────────┐
│ payments (Customer) │ │ accounting (Supplier) │
│ │ │ │
│ PaymentService ────────┼── usa ──►│ PaymentAccountingFacade│
│ │ │ │ │
│ Payment (dominio) │ │ ▼ │
│ │ │ PaymentAccountingImpl │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ JournalEntry, Money │
│ │ │ LadgerLine, LedgerType │
└─────────────────────────┘ └─────────────────────────┘
Downstream ──────────────────────► Upstream
(depende de la interfaz) (publica la interfaz)
- Java (sin frameworks)
- Maven
