Domain-Driven Design: Conformist, cuándo adaptar tu modelo al de otro Bounded Context.
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.
Sales consulta un catálogo externo para mostrar productos antes de iniciar una compra. El proveedor llama ACTIVE a un producto publicable y RETIRED a uno retirado. Sales necesita exactamente esa distinción para su ficha comercial.
Un desarrollador propone traducirlos a VISIBLE y HIDDEN, crear una clase local y mantener un mapa de equivalencias. Otro propone utilizar directamente cualquier JSON recibido.
Hay una tercera decisión: adoptar conscientemente el modelo publicado por el proveedor, validando lo que entra y documentando la dependencia. Esa es la alternativa que vamos a explorar.
No siempre conviene. Si Sales tiene reglas propias de publicación, aceptar los estados externos puede borrar una distinción importante. El trabajo consiste en reconocer cuándo ambos significados encajan y cuánto cuesta depender del otro modelo.
Qué significa Conformist
En una relación Conformist, el contexto Downstream adopta el modelo del Upstream para simplificar la integración, asumiendo que no dispone de influencia suficiente para obtener un modelo diseñado según sus propias necesidades.
Evans presenta el patrón ante una relación donde el proveedor no tiene motivación para atender las necesidades específicas del consumidor. La elección consiste en reducir la traducción mediante la adopción de su modelo, aceptando la limitación de autonomía que eso implica. DDD Reference: Conformist, página 33 del documento.
En nuestra tienda hipotética:
Catálogo externo [Upstream]
|
| modelo publicado: Product, ACTIVE, RETIRED
v
Sales [Downstream / Conformist para la oferta consultada]
La flecha expresa dependencia del modelo. No indica quién inicia HTTP.
La conformidad se refiere a un alcance concreto. Sales puede adoptar los conceptos del catálogo para consultar ofertas y conservar sus propias reglas de pedido. No tiene que convertir toda su aplicación en una copia del proveedor.
No poder influir no obliga a conformarse
Un consumidor sin influencia todavía puede mantener un modelo propio y traducir, buscar otro proveedor o prescindir de una capacidad. Conformist es una decisión entre alternativas, no una consecuencia inevitable de usar una API externa.
Tampoco depende de que el proveedor sea otra empresa. Dos equipos internos pueden encontrarse en esa situación si sus prioridades y responsabilidades no permiten negociar cambios.
Un caso donde adoptar el modelo puede ser razonable
Para el ejemplo, el catálogo publica esta representación ficticia:
ACTIVE permite presentar la oferta en el catálogo comercial.
RETIRED indica que la oferta está retirada.
El precio está expresado en céntimos de EUR.
La información describe la oferta actual, sin garantizar existencias ni reservar un precio para un pedido.
Sales no puede modificar esos términos. Para la ficha consultada, los considera adecuados.
La decisión documentada sería: usar Product, ACTIVE, RETIRED y el precio del proveedor con sus significados publicados. No inventamos estados equivalentes que no expresan ninguna diferencia local.
Esa conformidad no autoriza a confirmar pedidos con información desactualizada. La aceptación del precio y las condiciones de compra pertenecen al proceso comercial de Sales, que queda fuera de esta consulta.
La diferencia entre conformidad consciente y acoplamiento accidental
Una implementación problemática podría hacer esto:
El llamador recibe cualquier contenido, utiliza una URL sin restricciones y no distingue errores HTTP de productos válidos. Si luego aparecen códigos externos en todo el sistema, nadie sabe si fueron elegidos o simplemente se propagaron.
Una decisión consciente responde preguntas distintas:
¿Qué significado del proveedor estamos adoptando?
¿En qué parte de Sales es válido?
¿Quién revisa avisos y cambios del catálogo?
¿Qué respuestas son válidas y cuáles deben rechazarse?
¿Qué necesidad futura nos haría introducir un modelo propio?
Puedes aceptar vocabulario externo sin perder esas fronteras. No necesitas importar el código interno del Upstream ni usar su SDK como modelo universal.
Representar el contrato adoptado en TypeScript
Este tipo pertenece a Sales y describe el subconjunto del contrato externo que utiliza. No es una entidad importada del proveedor ni un contrato negociado entre ambos.
Aunque la interfaz sea local, sus conceptos y reglas siguen al proveedor. Repetir una representación para verificarla no crea automáticamente una ACL.
La diferencia es semántica: aquí no traducimos a un modelo distinto de oferta. Si mañana cambiara el significado de ACTIVE, Sales tendría que revisar su adopción.
Validar sin inventar otro modelo
Una respuesta JSON debe tratarse como datos desconocidos. Este parser comprueba lo necesario para el contrato elegido y devuelve solo los campos utilizados.
functionisRecord(value: unknown): value is Record<string, unknown> {
returntypeof value === "object" && value !== null &&
!Array.isArray(value);
}
exportfunctionparseCatalogProduct(raw: unknown,
expectedId: string,
): CatalogProduct {
if (!isRecord(raw) || raw.id !== expectedId) {
thrownewError("El catálogo respondió con otro producto");
}
if (
typeof raw.title !== "string" ||
raw.title.trim().length === 0 ||
raw.title.length > 200
) {
thrownewError("Título de catálogo inválido");
}
if (raw.status !== "ACTIVE" && raw.status !== "RETIRED") {
thrownewError("Estado de catálogo no soportado");
}
if (!isRecord(raw.price) || raw.price.currency !== "EUR") {
thrownewError("Moneda de catálogo no soportada");
}
const amount = raw.price.minorUnits;
if (
typeof amount !== "number" ||
!Number.isSafeInteger(amount) ||
amount < 0
) {
thrownewError("Importe de catálogo inválido");
}
return {
id: expectedId,
title: raw.title,
status: raw.status,
price: {
currency: "EUR",
minorUnits: amount,
},
};
}
Aceptamos campos adicionales, pero no los conservamos. Rechazamos estados desconocidos en lugar de tratarlos como activos. Los límites mostrados son supuestos explícitos del catálogo ficticio y del alcance de la tienda; deben contrastarse con un proveedor real.
Validar tipos y rangos no comprueba que el proveedor haya mantenido el significado del precio. Para eso hacen falta documentación, ejemplos y revisión de cambios. Un número puede ser estructuralmente correcto y representar otra unidad.
Una consulta HTTP acotada
Los tres fragmentos de tipos, parser y cliente pueden organizarse en un archivo catalog-client.ts. Son una implementación didáctica de lectura; no implementan pedidos, persistencia ni pagos.
El entorno debe disponer de fetch, URL y AbortSignal.timeout, documentados entre las APIs globales de Node.js. Documentación oficial de Node.js.
El origen procede de configuración administrativa controlada y debe corresponder al proveedor permitido. Comprobar HTTPS no sustituye una lista de destinos autorizados si un usuario pudiera elegir el origen. No se debe aceptar esa configuración desde una petición pública.
El tiempo de espera es un ejemplo, no una recomendación universal. Los fallos de red, tiempo o JSON también deben tratarse como errores en el borde que invoca al cliente; no se convierten en productos retirados ni en ofertas gratuitas.
La lectura no sigue redirecciones y no reutiliza una caché del cliente HTTP. Eso no promete frescura absoluta: el proveedor puede mantener sus propias cachés y la oferta puede cambiar después.
En producción también hay que concretar autenticación, límites de tamaño de respuesta, observabilidad y cómo presentar un error recuperable. El título recibido se muestra como texto, no como HTML confiable. Conformist no permite omitir controles de seguridad.
Utilizar el concepto adoptado
Sales puede decidir si presenta la oferta utilizando directamente el estado externo:
La función no traduce ACTIVE a un concepto de dominio diferente. Aplica el significado publicado en el alcance donde Sales decidió aceptarlo.
Su nombre es deliberadamente acotado. canDisplayCatalogOffer no significa «puede cobrarse» o «hay unidades». Una condición de catálogo no debe crecer silenciosamente hasta convertirse en autorización para todas las operaciones comerciales.
Comprobaciones que protegen la decisión
Estas pruebas propuestas usan APIs integradas de Node.js y presuponen compilación de TypeScript a JavaScript ESM. No se han ejecutado como parte del artículo.
import test from"node:test";
import assert from"node:assert/strict";
import {
parseCatalogProduct,
canDisplayCatalogOffer,
} from"./catalog-client.js";
functionproduct(status: unknown): unknown {
return {
id: "product-100",
title: "Mochila urbana",
status,
price: { currency: "EUR", minorUnits: 3500 },
};
}
test("adopta ACTIVE para presentar la oferta", () => {
const parsed = parseCatalogProduct(product("ACTIVE"), "product-100");
assert.equal(canDisplayCatalogOffer(parsed), true);
assert.equal(parsed.price.minorUnits, 3500);
});
test("una oferta RETIRED no se presenta", () => {
const parsed = parseCatalogProduct(product("RETIRED"), "product-100");
assert.equal(canDisplayCatalogOffer(parsed), false);
});
test("un estado nuevo no se interpreta automáticamente como activo", () => {
assert.throws(() =>parseCatalogProduct(product("PREVIEW"), "product-100"),
);
});
test("rechaza una respuesta que cambia la unidad por una cadena", () => {
assert.throws(() =>parseCatalogProduct({
id: "product-100",
title: "Mochila urbana",
status: "ACTIVE",
price: { currency: "EUR", minorUnits: "35.00" },
}, "product-100"));
});
Estas comprobaciones dejan visible qué interpreta Sales. No garantizan compatibilidad con el proveedor ni la vigencia de sus reglas. Tampoco sustituyen pruebas del cliente HTTP para 404, errores de red y respuestas inválidas.
Su utilidad para Conformist es hacer explícita una dependencia que, sin ejemplos, podría quedar oculta detrás de una interfaz.
Cómo se propaga un cambio externo
Imagina que el proveedor incorpora PREVIEW: una oferta visible solo para ciertos compradores.
Nuestro parser la rechaza. Eso evita mostrarla con una interpretación inventada, pero también significa que la consulta no puede utilizarse hasta revisar ese estado. Rechazar de forma segura sigue teniendo un coste de disponibilidad y mantenimiento.
El equipo debe averiguar:
Qué significa el estado y qué operaciones permite.
Si Sales puede seguir adoptando esa distinción.
Qué consumidores locales utilizan el tipo y sus estados.
Cómo actualizar ejemplos, validación y comportamiento.
Si la nueva necesidad justifica pasar a una traducción propia.
Un cambio de nombre afecta referencias. Un cambio semántico puede afectar decisiones incluso sin modificar el esquema.
Por ejemplo, el proveedor podría mantener minorUnits: 3500 pero pasar de precio final a precio antes de impuestos. El parser no detectaría la diferencia. Sales tendría que revisar el acuerdo documental que decidió adoptar.
El precio histórico no debe depender de la consulta actual
Una oferta actual puede cambiar. Si Sales guarda un pedido, debe conservar las condiciones aceptadas según sus propias reglas. Consultar mañana el catálogo para reconstruir el importe cobrado ayer confunde dos responsabilidades.
Adoptar el modelo de oferta no obliga a usarlo como modelo histórico del pedido. Esta distinción permite que Conformist siga acotado y no arrastre todo el negocio hacia las decisiones del proveedor.
Cuándo una ACL empieza a aportar valor
Supongamos ahora que Sales tiene una política propia: una oferta puede mostrarse, pero necesita evaluación comercial antes de habilitar la compra. El proveedor solo ofrece ACTIVE y RETIRED.
Renombrar ACTIVE como APPROVED_FOR_SALE sería peligroso: el proveedor nunca hizo esa promesa.
Una alternativa es definir una representación local más precisa:
Este es solo el comienzo de una traducción. Sales decidiría por separado la aprobación comercial; la función no la inventa ni consulta un sistema que no hemos mostrado.
Una ACL completa debería proteger el modelo local y centralizar cómo se interpreta la interacción. No aparece por envolver fetch en una clase: nuestro cliente Conformist ya lo hace y sigue adoptando el modelo externo.
La comparación decisiva es:
Conformist: aceptamos los conceptos del proveedor porque encajan en este alcance.
ACL: preservamos conceptos propios y traducimos lo necesario para colaborar.
Una traducción puramente cosmética puede añadir trabajo sin autonomía real. Una traducción que expresa una diferencia del negocio sí puede justificar su coste.
Qué beneficios y costes estás aceptando
Menos traducción inicial. Reutilizar el lenguaje externo evita equivalencias que solo cambian nombres. La documentación del proveedor resulta directamente útil para el equipo consumidor.
Más dependencia semántica. Cuando el proveedor cambia sus conceptos, el consumidor debe revisar las decisiones que los adoptaron.
Menor autonomía de evolución. Sales puede necesitar trabajo de adaptación para seguir usando una capacidad cuyo calendario no controla.
Mayor coste potencial de sustitución. Cambiar de catálogo exige revisar dónde se adoptaron sus identificadores, estados y reglas. Localizar el acceso HTTP ayuda, pero no elimina esa adopción semántica.
Ajuste imperfecto. El modelo externo puede ser suficiente para una ficha sencilla y malo para reglas comerciales más ricas. Aceptarlo hoy no implica aceptarlo indefinidamente.
No necesitas una ACL por principio. Tampoco conviene presentar Conformist como integración gratuita: elimina parte de la traducción, no el mantenimiento, la validación o el riesgo de dependencia.
Una ficha para recordar la decisión
Para la tienda del ejemplo:
Relación: catálogo externo Upstream; Sales Downstream. Influencia: Sales no puede negociar cambios específicos del proveedor. Alcance adoptado: consulta y presentación de ofertas actuales. Conceptos: Product, ACTIVE, RETIRED y precio en céntimos de EUR. Responsabilidad local: validación, errores y condiciones aceptadas en pedidos. Excluido: interpretar el estado como garantía de stock o autorización de cobro. Revisión: nuevos estados, cambios monetarios o aparición de políticas comerciales propias.
Asigna una persona o equipo que revise los avisos del proveedor. Si no sabes quién atiende cambios, el ahorro inicial puede terminar en fallos que nadie reconoce como propios.
No confundas una versión fijada de un SDK con estabilidad del modelo remoto. El servidor puede cambiar aunque el paquete local no se actualice.
Diferencias con los patrones anteriores
Customer/Supplier incorpora necesidades Downstream a la planificación Upstream. Conformist no presupone esa capacidad de negociación.
Shared Kernel implica una parte de modelo compartida con evolución coordinada. Consumir unilateralmente el modelo publicado por un tercero no equivale a gobernarlo conjuntamente.
Anti-Corruption Layer mantiene una interpretación propia mediante traducción. Conformist evita esa diferencia cuando el modelo externo resulta aceptable.
No describas toda la empresa con una sola etiqueta. Sales puede adoptar el catálogo y protegerse mediante una ACL frente a otro sistema con vocabulario incompatible.
Dos decisiones fuera del comercio electrónico
Logística: mostrar el seguimiento publicado
Una página informativa reproduce el estado de un envío según el transportista. Si su necesidad es mostrar lo que el proveedor declara, adoptar sus estados puede ser una opción pragmática.
Si atención al cliente comienza a decidir compensaciones, debe revisar esa elección. «Incidencia» del transportista no necesariamente significa «pérdida confirmada» para la política de la tienda.
La misma fuente externa puede servir a dos consumidores con decisiones arquitectónicas distintas.
Educación: una categoría externa no es una acreditación
Un portal agrega cursos de otra plataforma. Para explorar el catálogo, puede adoptar sus categorías y niveles.
Un sistema académico que concede convalidaciones tiene otras responsabilidades. El nivel «avanzado» del proveedor no demuestra equivalencia curricular. Mantener un modelo propio y traducir puede ser necesario.
Aquí la coincidencia de nombres no sustituye la evidencia que exige la decisión académica. Una ficha descriptiva y una evaluación institucional no necesitan compartir modelo.
Evitar que la IA extienda la conformidad sin permiso
Cursor, Claude o Codex pueden reproducir DTOs externos por todo el código porque parecen una fuente cómoda de tipos. Proporciona el alcance de la decisión, no solo la documentación de la API.
Tres prompts específicos:
Para evaluar la adopción:
Compara nuestras necesidades de presentación con este catálogo externo. Identifica qué significados podemos adoptar y cuáles requieren reglas propias. No supongas influencia sobre el proveedor. Entrega costes, límites y condiciones para revisar Conformist.
Para implementar el cliente:
Implementa una lectura HTTP con origen administrado, tiempo máximo y validación de datos unknown. Conserva los términos ACTIVE y RETIRED según la documentación. Rechaza estados desconocidos y distingue 404 de fallo técnico. No uses esos estados para autorizar pedidos, reservas o pagos.
Para revisar un cambio:
El proveedor añadió PREVIEW y cambió la definición del precio. Busca decisiones afectadas por tipos y significado, no solo errores de compilación. Propón la adaptación mínima o justifica introducir una traducción propia. Distingue hechos documentados de preguntas pendientes.
Una persona debe confirmar que el significado externo sigue siendo apropiado. Que el código generado compile no demuestra que la decisión comercial sea correcta.
Ejercicios para decidir el alcance
1. El renombrado que promete demasiado
Sales traduce ACTIVE a READY_TO_CHARGE. El proveedor solo promete publicación. ¿Se ha protegido el modelo local?
2. El estado desconocido
El catálogo añade RESTRICTED. Un desarrollador propone aceptar cualquier cadena y mostrar todos los productos salvo RETIRED. ¿Qué riesgo introduce?
3. La necesidad que cambia
Sales empieza a aprobar ofertas mediante una revisión local, independiente del catálogo. ¿Debe abandonar toda la integración?
Soluciones razonadas
1. No. El renombrado introduce una garantía de cobro que no existe en el contrato. Hay que preservar el significado de publicación y modelar por separado las decisiones necesarias para cobrar.
2. Un estado nuevo podría significar que no se permite presentar la oferta a ese usuario. La propuesta convierte desconocimiento en autorización. Debe revisarse su semántica y decidir explícitamente la respuesta local, sin interpretar un fallo como producto válido.
3. No necesariamente. Puede conservar el acceso al catálogo y adoptar un modelo local para la aprobación. Una traducción puede separar presentación externa y decisión comercial sin reescribir capacidades que siguen siendo útiles.
Elegir conformidad con un límite verificable
Conformist puede ser razonable si el modelo externo encaja suficientemente, no puedes influir en él y mantener otro significado no aporta un beneficio proporcional.
Comprueba que puedes explicar qué adoptas, dónde termina esa adopción y quién revisará sus cambios. Si necesitas reinterpretar continuamente estados o completar reglas que el proveedor no conoce, vuelve a evaluar la decisión.
La conformidad consciente reduce traducción innecesaria. Su valor depende de aceptar la dependencia con criterio y de reconocer cuándo deja de ser adecuada.
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 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.