El plan de descomposición de la lección anterior deja seis servicios con fronteras dibujadas a trazo grueso. Falta lo que separa un diseño aceptable de uno bueno: decidir con precisión dónde termina el significado de cada palabra. En el monolito de TechCorp, "producto" es una fila de la tabla productos y todo el mundo la usa; pero lo que el catálogo entiende por producto (una ficha con descripción, imágenes y atributos) no es lo que entiende inventario (un identificador con cantidades) ni lo que entiende pedidos (un nombre y un precio congelados en el momento de la compra). Mientras las tres áreas compartían tabla, la ambigüedad se toleraba. En cuanto son servicios, la ambigüedad se convierte en acoplamiento.
Domain-Driven Design (DDD) llama a esto DDD estratégico y ofrece las herramientas para hacerlo bien: subdominios, lenguaje ubicuo, bounded contexts, mapa de contextos y agregados. Esta lección las aplica una a una a TechCorp: clasificaremos los subdominios, veremos ejemplos concretos de ambigüedad, definiremos los seis contextos y su relación con los servicios, dibujaremos el mapa de contextos con sus patrones de relación, definiremos el agregado Pedido (y por qué el stock no forma parte de él) y concretaremos qué campos de "producto" y de "cliente" guarda cada contexto. Cómo se materializa eso en bases de datos físicas separadas es el tema de 02-04.
Contenido
- DDD estratégico en pocas palabras
- Los subdominios de TechCorp: core, soporte y genéricos
- Lenguaje ubicuo: cuando "producto" y "cliente" no significan lo mismo
- Qué es un bounded context y cómo se relaciona con un microservicio
- El mapa de contextos y sus patrones de relación
- Agregados y raíces de agregado: la frontera transaccional
- El modelo de datos de cada contexto
- DDD estratégico en pocas palabras
DDD (Eric Evans, 2003) tiene dos mitades. La táctica habla de cómo escribir el código de un modelo (entidades, objetos de valor, repositorios, servicios de dominio). La estratégica habla de cómo dividir un problema grande en modelos que puedan evolucionar por separado. Para diseñar microservicios, la mitad estratégica es la que importa, y se apoya en cinco ideas:
| Idea | Qué es | Pregunta que responde |
|---|---|---|
| Dominio | El área de conocimiento y actividad de la empresa. Para TechCorp, "vender electrónica de consumo por internet". | ¿De qué va el negocio? |
| Subdominio | Una parte del dominio con su propia problemática. Catálogo, inventario, pedidos... | ¿En qué partes se divide el problema? |
| Lenguaje ubicuo | El vocabulario preciso, compartido por negocio y técnicos, dentro de un contexto. | ¿Qué significa exactamente cada palabra aquí? |
| Bounded context | El límite dentro del cual un modelo y su lenguaje son válidos y coherentes. | ¿Hasta dónde llega este modelo? |
| Mapa de contextos | El diagrama de cómo se relacionan los contextos y quién manda en cada relación. | ¿Cómo hablan entre sí y quién se adapta a quién? |
La distinción que más confusión genera: subdominio es un trozo del problema (existe aunque no haya software); bounded context es un trozo de la solución (un modelo que construimos). Lo ideal es que coincidan uno a uno, y en TechCorp así será, pero conviene tener claro que son cosas distintas.
- Los subdominios de TechCorp: core, soporte y genéricos
DDD clasifica los subdominios según su valor competitivo, y esa clasificación dicta cuánto esfuerzo de diseño merece cada uno:
- Core (núcleo): donde la empresa se diferencia. Es lo que hay que diseñar con más cuidado y con el mejor equipo, y lo que nunca se compra hecho.
- De soporte: necesario para el negocio, específico de la empresa, pero no diferenciador. Se diseña bien, sin obsesión.
- Genérico: un problema resuelto igual en todas las empresas. Se compra, se usa un servicio externo o se implementa de la forma más sencilla posible.
| Subdominio de TechCorp | Tipo | Justificación | Consecuencia de diseño |
|---|---|---|---|
| Pedidos (ciclo de vida del pedido, flujo de compra) | Core | Es donde TechCorp gana o pierde ventas; la experiencia de compra y la fiabilidad del flujo la diferencian de la competencia. | Equipo de Luis; modelo más rico; saga diseñada con cuidado (02-05); nunca un servicio de entidad. |
| Catálogo (fichas, búsqueda, precios) | Core | La forma de presentar y buscar productos de electrónica con atributos muy variables es parte de la propuesta de valor. | Merece tecnología propia (MongoDB) y ser el primero en escalar. |
| Inventario (stock, reservas) | Soporte | Imprescindible y específico (reglas de reserva, almacenes), pero no diferencia a TechCorp. | Modelo sencillo y correcto; su invariante (no vender lo que no hay) es sagrado. |
| Clientes (perfil, direcciones) | Soporte | Necesario, específico en pequeños detalles (direcciones, preferencias). | Modelo simple; la autenticación, que sí es genérica, se delega. |
| Pagos (cobros, devoluciones) | Soporte, con núcleo genérico | Cobrar es genérico (lo hace la pasarela externa); cuándo y cómo cobrar según el estado del pedido es específico. | Adaptador a la pasarela aislado tras una capa anticorrupción (apartado 5). |
| Notificaciones (correo, SMS) | Genérico | Enviar un correo es igual en todas partes. | Implementación mínima; proveedor externo; nada de lógica de negocio dentro. |
| Identidad (autenticación) | Genérico | Login, contraseñas, tokens: problema resuelto. | Se delega por completo en Keycloak (07-01); no hay servicio de TechCorp para ello. |
Esta tabla justifica decisiones que ya tomamos por intuición: por qué el catálogo y los pedidos concentran la inversión, por qué la identidad no aparece en el mapa de servicios y por qué Notificaciones debe ser aburrido.
- Lenguaje ubicuo: cuando "producto" y "cliente" no significan lo mismo
El lenguaje ubicuo es el vocabulario que negocio y desarrollo usan sin traducción, y que aparece tal cual en el código (por eso los identificadores del curso están en español: crearPedido, PedidoRepositorio, clienteId). La observación clave de DDD es que el lenguaje ubicuo solo es coherente dentro de un contexto: la misma palabra cambia de significado al cruzar la frontera, y pretender un único modelo "producto" para toda la empresa produce un modelo gigante que no sirve bien a nadie (es la tabla productos + atributos_producto de TechCorp).
3.1 "Producto"
| Contexto | Qué es un "producto" | Atributos que le importan | Qué le da igual |
|---|---|---|---|
| Catálogo | Una ficha comercial que se muestra y se busca. | Nombre, descripción larga, categoría, imágenes, atributos variables (voltaje, compatibilidad, color), precio de venta actual, si está publicado. | Cuánto stock hay; en qué pedidos aparece. |
| Inventario | Una referencia almacenable de la que hay unidades. | productoId, cantidad física, cantidad reservada, ubicación en almacén, umbral de reposición. |
La descripción, las imágenes, el precio. |
| Pedidos | Una línea de pedido: lo que el cliente compró, al precio al que lo compró. | productoId, nombre en el momento de la compra, precio unitario en el momento de la compra, cantidad. |
Que el precio haya cambiado después; el stock actual; los atributos. |
La consecuencia práctica más importante está en la tercera fila: si el catálogo cambia el precio de p-501 mañana, el pedido ped-88213 de hoy no debe cambiar. Por eso pedidos guarda una copia congelada de nombre y precio en lineas_pedido, y por eso el evento "Precio cambiado" del event storming de 02-02 no le interesa. No es duplicación accidental: es que son dos conceptos distintos que comparten identificador.
3.2 "Cliente"
| Contexto | Qué es un "cliente" | Atributos que le importan |
|---|---|---|
| Clientes | Una persona registrada con perfil y direcciones. | clienteId, email, nombre, direcciones guardadas, preferencias de contacto, fecha de alta, identificador en Keycloak. |
| Pedidos | Quien hace el pedido y a dónde se envía. | clienteId (referencia), dirección de envío de este pedido (copia; si el cliente cambia su dirección después, el pedido en curso no debe moverse). |
| Notificaciones | Un destinatario. | Email y nombre para saludar. Nada más. Y los recibe en cada evento, no los almacena como maestro. |
| Pagos | Casi no existe: un pago se asocia a un pedido, no a un cliente. | pedidoId, importe. El "titular" lo conoce la pasarela, no TechCorp. |
3.3 Otras palabras con doble sentido
- "Estado": en Pedidos, PENDIENTE / CONFIRMADO / CANCELADO; en Pagos, AUTORIZADO / CAPTURADO / REEMBOLSADO; en Inventario, una reserva está ACTIVA / CONSUMIDA / LIBERADA. Tres máquinas de estados distintas que hoy conviven en columnas
estado TEXTsin relación. - "Cancelar": para Pedidos es cerrar el pedido sin completarlo; para Pagos es no capturar un cobro autorizado; para Inventario es liberar una reserva. La saga de 02-05 encadenará las tres, pero cada contexto usa su verbo.
- "Disponible": en Catálogo significa publicado; en Inventario significa
cantidad - reservado > 0. Un producto puede estar disponible en catálogo y no en inventario, y viceversa.
Detectar estas colisiones es, en la práctica, la forma más fiable de encontrar fronteras: allí donde una palabra cambia de significado, hay una costura.
- Qué es un bounded context y cómo se relaciona con un microservicio
Un bounded context es el límite explícito dentro del cual un modelo de dominio y su lenguaje ubicuo son coherentes. Dentro, "producto" significa una sola cosa; fuera, puede significar otra. Cada contexto tiene su propio modelo, su propio código y (como veremos en 02-04) sus propios datos.
La relación con los microservicios:
| Relación | Cuándo | Ejemplo | Valoración |
|---|---|---|---|
| 1 contexto = 1 servicio | Lo habitual y lo recomendable de partida. | Los seis contextos de TechCorp → los seis servicios del mapa objetivo. | Máxima claridad: la frontera del modelo es la frontera de despliegue y de datos. |
| 1 contexto = varios servicios | Cuando dentro de un contexto hay partes con escalado o ritmo de cambio muy distinto. | Si la búsqueda del catálogo necesitara un motor de indexación separado de la gestión de fichas, ambos seguirían hablando el mismo lenguaje ("ficha", "atributo") pero serían dos despliegues. | Aceptable; los dos servicios comparten lenguaje pero no base de datos ni código de negocio. |
| Varios contextos = 1 servicio | En etapas tempranas o en dominios pequeños. | El monolito modular de 01-03; o el "monolito residual" de TechCorp mientras Pedidos y Clientes aún no se han extraído. | Válido como paso intermedio si los contextos están separados como módulos y no comparten tablas por diseño. |
| 1 servicio abarca medio contexto | Nunca. | Un servicio-lineas-pedido separado de servicio-pedidos. |
Rompe el modelo por la mitad: los invariantes del pedido (apartado 6) quedan repartidos en dos procesos. |
La regla de TechCorp: un contexto, un servicio, un equipo dueño. Y la excepción admitida: durante la migración, el monolito residual contiene varios contextos, cada uno en su módulo, con las abstracciones de 02-02 marcando dónde están las fronteras.
- El mapa de contextos y sus patrones de relación
Los contextos no viven aislados: Pedidos necesita precios de Catálogo, Notificaciones necesita saber que un pedido se ha confirmado. El mapa de contextos dibuja esas relaciones y, sobre todo, quién manda en cada una: en cualquier relación hay un contexto upstream (aguas arriba: su modelo influye) y uno downstream (aguas abajo: se adapta o se protege). DDD cataloga los patrones de relación:
| Patrón | Qué significa | Quién manda | Cuándo se usa |
|---|---|---|---|
| Customer-Supplier (cliente-proveedor) | El upstream (proveedor) atiende necesidades del downstream (cliente); hay negociación: el cliente puede pedir campos, el proveedor planifica. | Upstream, pero escucha. | Equipos que colaboran de buena fe. |
| Conformist (conformista) | El downstream acepta el modelo del upstream tal cual, sin traducirlo ni influir en él. | Upstream por completo. | Cuando el upstream no va a cambiar por ti (o no merece la pena traducir). |
| Anticorruption Layer, ACL (capa anticorrupción) | El downstream pone una capa que traduce el modelo del upstream a su propio lenguaje, para que el modelo ajeno no "contamine" el suyo. | Upstream, pero el downstream se protege. | Sistemas externos, sistemas heredados, modelos muy distintos. |
| Shared Kernel (núcleo compartido) | Dos contextos comparten un trozo de modelo (código o esquema) y acuerdan cambiarlo juntos. | Ambos, de mutuo acuerdo. | Solo entre equipos muy cercanos; es acoplamiento asumido. |
| Open Host Service, OHS (servicio de host abierto) | El upstream ofrece un protocolo público y estable para que cualquiera lo consuma, en vez de integraciones a medida. | Upstream define el protocolo. | Contextos con muchos consumidores. |
| Published Language, PL (lenguaje publicado) | El formato de intercambio está documentado y versionado (esquema JSON, contrato de eventos). Suele acompañar a OHS. | Upstream publica; todos lo entienden. | Casi siempre, junto a OHS o a eventos. |
| Partnership (asociación) | Dos contextos evolucionan juntos por necesidad mutua, coordinando planes. | Ninguno; iguales. | Contextos del mismo equipo o con dependencia bidireccional fuerte. |
| Separate Ways (caminos separados) | No hay relación; si hiciera falta algo, se duplica. | — | Cuando integrar cuesta más que no integrar. |
Aplicado a TechCorp:
flowchart LR
CAT[Catálogo<br/>OHS + PL]
CLI[Clientes<br/>OHS + PL]
PED[Pedidos<br/>core]
INV[Inventario]
PAG[Pagos]
NOT[Notificaciones]
KC[Keycloak<br/>externo]
PAS[Pasarela de pago<br/>externa]
CAT -- "U: precios y nombres<br/>D: ACL 'traductorProducto'" --> PED
CLI -- "U: existencia, email, dirección<br/>D: Customer-Supplier" --> PED
PED <-->|"Partnership<br/>eventos pedido.creado / stock.reservado"| INV
PED -- "U: eventos (PL)<br/>D: Customer-Supplier" --> PAG
PAG -- "U: pago.confirmado (PL)" --> PED
PED -- "U: pedido.confirmado / cancelado (PL)<br/>D: Conformist" --> NOT
KC -- "U: tokens OIDC<br/>D: Conformist" --> CLI
PAS -- "U: API propietaria<br/>D: ACL" --> PAG
Lectura relación por relación (U = upstream, D = downstream):
- Catálogo → Pedidos: OHS + PL con ACL en Pedidos. Catálogo publica un contrato abierto (
GET /productos?ids=..., formato JSON documentado) para todos los consumidores (Pedidos, la web, futuros servicios). Pedidos no incorpora el modelo "ficha de catálogo" a su dominio: una pequeña capa (traductorProducto) convierte{ productoId, nombre, precio, descripcion, atributos... }en lo único que Pedidos entiende, unaLineaPedidocon nombre y precio congelados. Así, si Catálogo cambia sus atributos, Pedidos no se entera. - Clientes → Pedidos: Customer-Supplier. Pedidos necesita saber que el cliente existe y obtener email y dirección de envío. Es una relación negociada: el equipo de Pedidos puede pedir a Clientes que exponga un
GET /clientes/{id}con exactamente esos campos. - Pedidos ↔ Inventario: Partnership. Ambos son del equipo de Luis y evolucionan juntos: Pedidos publica
pedido.creado, Inventario responde constock.reservado(o su negativa). Los contratos de esos eventos se diseñan en la misma mesa. Nótese que Partnership no significa base de datos compartida: significa planificación compartida. - Pedidos → Pagos → Pedidos: Customer-Supplier por eventos, con PL. Pagos consume
stock.reservadoy publicapago.confirmado(o su rechazo). El lenguaje publicado son los esquemas de esos eventos. - Pedidos → Notificaciones: Conformist. Notificaciones acepta los eventos de Pedidos tal cual, sin pedir cambios: es un subdominio genérico y su trabajo es reaccionar. Si
pedido.confirmadotraeemailynombre, los usa; no tiene modelo propio de cliente que proteger. - Keycloak → Clientes: Conformist. Keycloak es externo y estándar (OpenID Connect); Clientes se adapta a sus tokens y guarda el identificador de Keycloak (
sub) junto alclienteId. - Pasarela → Pagos: ACL. La pasarela tiene su propia API, sus propios estados y sus propios errores.
servicio-pagosla envuelve en un adaptador (servicios/pasarelaPago.js, heredado del monolito y ahora en exclusiva) que traduce a los conceptos de Pagos: AUTORIZADO, CAPTURADO, RECHAZADO, REEMBOLSADO. Cambiar de pasarela solo toca esa capa. - Shared Kernel: ninguno, por decisión. La tentación sería compartir "identificadores", "dinero" o "el sobre de los eventos" como librería común de dominio. TechCorp lo evita: los identificadores son cadenas opacas y el formato del sobre de eventos es lenguaje publicado (documentado), no código compartido. Es la misma regla de 02-02 sobre utilidades compartidas.
- Separate Ways: Catálogo y Notificaciones, Inventario y Clientes. No se hablan. Si Notificaciones algún día necesitara el nombre de un producto para un correo, lo recibiría en el evento antes que integrarse con Catálogo.
- Agregados y raíces de agregado: la frontera transaccional
Bajamos un nivel. Dentro de un contexto, el modelo se organiza en agregados: grupos de objetos que se tratan como una unidad para cambios de estado. Cada agregado tiene una raíz (la única entidad a la que se accede desde fuera) y define una frontera de consistencia: los invariantes (reglas que siempre deben cumplirse) se garantizan dentro del agregado, en una única transacción; entre agregados, la consistencia es eventual.
Reglas prácticas de diseño de agregados:
- Una transacción modifica un solo agregado. Si necesitas modificar dos, o el diseño está mal o toca aceptar consistencia eventual entre ellos (02-05).
- Los agregados se referencian por identificador, nunca por objeto: un Pedido guarda
clienteId, no un objeto Cliente. - Agregados pequeños. Un agregado grande bloquea mucho y escala mal.
- La raíz protege los invariantes. Nadie modifica una línea de pedido "por fuera"; se pide al Pedido que la añada, y el Pedido comprueba que puede.
6.1 El agregado Pedido
En el contexto Pedidos, el agregado es Pedido (raíz) con sus LíneaPedido (entidades internas) y su DirecciónEnvío (objeto de valor, copia). Sus invariantes:
- El total es siempre la suma de
precioUnitario × cantidadde sus líneas. - Un pedido tiene al menos una línea y ninguna línea tiene cantidad ≤ 0.
- Un pedido CONFIRMADO o CANCELADO no admite cambios de líneas.
- Las transiciones de estado siguen la máquina de estados que veremos en 02-05.
// dominio/Pedido.js (contexto Pedidos) - esqueleto ilustrativo del agregado, sin persistencia
class Pedido {
#lineas = []; // solo la raíz toca las líneas
constructor({ pedidoId, clienteId, direccionEnvio }) {
this.pedidoId = pedidoId; // 'ped-88213'
this.clienteId = clienteId; // referencia por id: NO un objeto Cliente
this.direccionEnvio = { ...direccionEnvio }; // copia congelada (objeto de valor)
this.estado = 'PENDIENTE';
}
anadirLinea({ productoId, nombre, precioUnitario, cantidad }) {
if (this.estado !== 'PENDIENTE') throw new Error('PEDIDO_NO_MODIFICABLE');
if (cantidad <= 0) throw new Error('CANTIDAD_INVALIDA');
// nombre y precioUnitario llegan ya traducidos desde Catálogo (ACL) y se congelan aquí
this.#lineas.push({ productoId, nombre, precioUnitario, cantidad });
}
get total() { // invariante: siempre derivado de las líneas
return this.#lineas.reduce((suma, l) => suma + l.precioUnitario * l.cantidad, 0);
}
get lineas() { return this.#lineas.map(l => ({ ...l })); } // copia: nadie modifica por fuera
}
module.exports = { Pedido };Qué no hay en este agregado, y por qué:
- No hay stock. El invariante "no reservar más de lo que hay" (
cantidad - reservado >= 0) pertenece al contexto Inventario y a su agregado (unaReservaasociada a unpedidoId, o el propioStockpor producto). Meter el stock en el Pedido tendría tres problemas: (1) el Pedido debería conocer un invariante que no es suyo; (2) una fila de stock la tocan cientos de pedidos concurrentes, así que el agregado Pedido "abarcaría" filas compartidas por todos los pedidos y las transacciones se bloquearían entre sí (exactamente lo que pasaba encrearPedidocon la pasarela dentro de la transacción); (3) el stock cambia por motivos ajenos al pedido (entradas de almacén, mermas). Por eso, en el diseño objetivo, Pedidos pide la reserva y la respuesta llega como evento. - No hay objeto Cliente ni objeto Producto. Solo
clienteIdyproductoId, más las copias de valor (dirección, nombre, precio) que el pedido necesita para ser autosuficiente. - No hay pago. El Pago es agregado de otro contexto; el Pedido solo conoce que su estado pasó a PAGADO cuando llegó el evento.
Y en los otros contextos, los agregados principales:
| Contexto | Agregado (raíz) | Contiene | Invariante principal |
|---|---|---|---|
| Pedidos | Pedido | Líneas, dirección de envío, estado, total | Total = suma de líneas; transiciones válidas. |
| Inventario | Stock por producto y Reserva | Cantidad, reservado; reserva: pedidoId, líneas, estado | cantidad - reservado >= 0; una reserva no se consume dos veces. |
| Catálogo | Producto (ficha) | Descripción, atributos, imágenes, precio actual, publicado | Un producto publicado tiene nombre y precio. |
| Pagos | Pago | pedidoId, importe, estado, referencia de la pasarela | No capturar dos veces el mismo pedido. |
| Clientes | Cliente | Perfil, direcciones | Email único. |
| Notificaciones | Envío (mínimo) | Destinatario, plantilla, estado | No reenviar el mismo aviso. |
- El modelo de datos de cada contexto
Concretemos qué guarda cada contexto de los conceptos compartidos. Todavía no hablamos de bases de datos físicas (02-04), sino de qué campos existen en cada modelo.
7.1 "Producto" en los tres contextos
| Campo | Catálogo | Inventario | Pedidos (lineas_pedido) |
|---|---|---|---|
productoId |
Sí (lo genera) | Sí (referencia) | Sí (referencia) |
nombre |
Sí (maestro) | No | Sí, copia congelada |
descripcion, imagenes, categoria |
Sí | No | No |
atributos variables (voltaje, color...) |
Sí (documento flexible) | No | No |
precio |
Sí (precio actual) | No | Sí, copia congelada como precio_unitario |
publicado / activo |
Sí | No | No |
cantidad, reservado |
No | Sí (maestro) | No |
ubicacion, umbral_reposicion |
No | Sí | No |
cantidad pedida |
No | En la reserva | Sí |
Ejemplo del mismo producto visto desde cada contexto (formato ilustrativo; el esquema físico llega en 02-04):
// Catálogo: la ficha (documento flexible)
{ "productoId": "p-501", "nombre": "Auriculares BT X200", "precio": 59.90,
"categoria": "audio", "publicado": true,
"atributos": { "color": "negro", "autonomiaHoras": 30, "bluetooth": "5.3" },
"imagenes": ["x200-frontal.webp", "x200-lateral.webp"] }
// Inventario: la referencia almacenable
{ "productoId": "p-501", "cantidad": 120, "reservado": 7, "ubicacion": "A-03-2", "umbralReposicion": 20 }
// Pedidos: la línea del pedido ped-88213 (precio del día de la compra, aunque mañana cambie)
{ "productoId": "p-501", "nombre": "Auriculares BT X200", "precioUnitario": 59.90, "cantidad": 1 }7.2 "Cliente" en los contextos que lo usan
| Campo | Clientes | Pedidos | Notificaciones |
|---|---|---|---|
clienteId |
Sí (lo genera) | Sí (referencia) | Solo si viene en el evento |
email, nombre |
Sí (maestro) | No los almacena como maestro; los obtiene por contrato al crear el pedido y los incluye en los eventos | Los recibe en cada evento; no los guarda como maestro |
direcciones[] guardadas |
Sí | No | No |
direccionEnvio del pedido |
No | Sí, copia congelada por pedido | No |
keycloakSub, preferencias, fecha de alta |
Sí | No | No |
7.3 El pedido completo tal como lo ve su contexto
{
"pedidoId": "ped-88213",
"clienteId": "c-1024",
"estado": "PENDIENTE",
"direccionEnvio": { "calle": "Gran Vía 12", "cp": "28013", "ciudad": "Madrid" },
"lineas": [
{ "productoId": "p-501", "nombre": "Auriculares BT X200", "precioUnitario": 59.90, "cantidad": 1 },
{ "productoId": "p-777", "nombre": "Cable USB-C 2 m", "precioUnitario": 9.90, "cantidad": 2 }
],
"total": 79.70,
"creadoEn": "2026-08-15T10:42:00Z"
}Es la misma estructura que devolvía GET /pedidos/{id} en 01-01, ahora explicada: todo lo que hay dentro es del contexto Pedidos (referencias por id + copias de valor); nada de lo que hay dentro obliga a consultar otro contexto para leer el pedido.
Errores Comunes y Consejos
- Buscar el "modelo canónico" de producto para toda la empresa. Es el camino de vuelta a la tabla
productos+atributos_producto. Cada contexto tiene su producto; comparten identificador, no modelo. - Confundir bounded context con módulo técnico. "Contexto de persistencia" o "contexto de API" no son bounded contexts: no tienen lenguaje de negocio propio.
- Agregados enormes. Un Pedido que contiene el Cliente, los Productos y el Stock "para tenerlo todo a mano" bloquea a media empresa en cada transacción. Referencias por id y copias de valor.
- Copiar datos "por si acaso" en lugar de por semántica. Pedidos copia nombre y precio porque el negocio dice que el pedido conserva el precio de la compra, no por rendimiento. Si copias algo que semánticamente debe estar siempre actualizado (por ejemplo, el email para contactar), tendrás datos obsoletos; ahí toca consultar o suscribirse a cambios (02-04).
- Ignorar el ACL con sistemas externos. Dejar que los estados de la pasarela de pago se filtren en el modelo de Pagos (y de ahí a los eventos) hace que cambiar de pasarela sea un cambio en toda la empresa.
- Consejo: mantén un glosario por contexto (una tabla: término, significado en este contexto, campo en el código). Cuando dos glosarios discrepen sobre una palabra, no lo resuelvas: es una frontera confirmada.
Ejercicios
Ejercicio 1: Clasificar subdominios y elegir patrón
TechCorp estudia añadir dos capacidades: (a) valoraciones de productos por los clientes (estrellas y comentarios en la ficha) y (b) facturación (emisión de facturas legales por cada pedido confirmado). Para cada una: clasifícala como core, soporte o genérica; indica de qué contextos existentes sería downstream y qué patrón de relación usarías con cada uno.
Ejercicio 2: Detectar la palabra ambigua
En una reunión, el equipo de Inventario dice "el producto p-501 no está disponible" y el equipo de Catálogo responde "sí que lo está, lo publiqué ayer". Explica la ambigüedad con el vocabulario de esta lección, indica cómo la resolverías en los contratos (qué campo expone cada contexto) y qué debería mostrar la web al cliente en ese caso.
Ejercicio 3: ¿Dentro o fuera del agregado?
Para cada elemento, decide si forma parte del agregado Pedido, es un agregado propio del contexto Pedidos, o pertenece a otro contexto, y justifica con la regla de invariantes: (1) el cupón de descuento aplicado a un pedido; (2) el historial de cambios de estado del pedido; (3) la valoración que el cliente deja del pedido tras recibirlo; (4) la reserva de stock del pedido.
Soluciones
Ejercicio 1
(a) Valoraciones: subdominio de soporte (aporta a la experiencia, pero no es el núcleo de venta ni un problema genérico). Sería downstream de Catálogo (necesita saber que el producto existe: OHS + PL, consumiendo el contrato público) y de Pedidos o Clientes para verificar que quien valora compró el producto (Customer-Supplier: pediría un GET /pedidos?clienteId=...&productoId=... o un evento pedido.entregado). Su modelo de "producto" sería mínimo: productoId y nombre para mostrar. Podría vivir dentro del contexto Catálogo si es muy simple; como servicio propio si el volumen de escritura o el equipo lo justifican.
(b) Facturación: subdominio de soporte con fuerte componente genérico (las reglas fiscales son iguales para todos; muchas empresas usan un proveedor). Downstream de Pedidos (Conformist o Customer-Supplier sobre pedido.confirmado, del que necesita líneas, importes e impuestos) y de Clientes (datos fiscales: Customer-Supplier, pidiendo un campo datosFiscales). Si se usa un proveedor externo de facturación, ACL delante de su API. Nunca compartiría tablas con Pedidos aunque "casi todo lo que necesita está en pedidos".
Ejercicio 2
"Disponible" tiene dos significados: en Catálogo, publicado (la ficha se muestra); en Inventario, con unidades libres (cantidad - reservado > 0). Ambos tienen razón en su contexto. En los contratos, cada uno expone su concepto con su nombre: Catálogo, publicado: true/false; Inventario, disponible: 113 (unidades libres) o hayStock: true/false. La web (o un BFF, 03-04) compone ambos: muestra la ficha porque está publicada y el botón "comprar" desactivado con el texto "sin stock" porque Inventario dice 0. Lo que no debe hacerse es añadir a la ficha del catálogo un campo stock mantenido a mano: es un dato de otro contexto (si se muestra, se obtiene por contrato o por réplica de eventos, 02-04).
Ejercicio 3
- Cupón aplicado: dentro del agregado Pedido como dato de valor (
descuentoAplicado, código y cantidad), porque afecta al invariante del total; la definición del cupón (validez, condiciones) es de otro contexto (Promociones), referenciado por código. - Historial de estados: puede ser parte del agregado (lista de transiciones con fecha) si es pequeño y se usa para validar transiciones; si crece o solo sirve para auditoría, agregado propio o simplemente un registro de eventos del contexto Pedidos. En ningún caso otro contexto.
- Valoración del pedido: fuera del agregado y probablemente fuera del contexto: no afecta a ningún invariante del pedido y tiene ciclo de vida propio (llega días después, se puede editar). Referencia por
pedidoId. - Reserva de stock: otro contexto (Inventario), agregado Reserva con
pedidoId. Su invariante (cantidad - reservado >= 0) no es del Pedido, y meterla dentro haría que cada pedido bloquease filas compartidas por todos.
Conclusión
Con DDD estratégico hemos convertido las seis áreas de TechCorp en seis bounded contexts bien definidos: hemos clasificado sus subdominios (Pedidos y Catálogo como core; Inventario, Clientes y Pagos de soporte; Notificaciones e Identidad genéricos, esta última delegada en Keycloak), hemos comprobado que "producto", "cliente", "estado", "cancelar" y "disponible" significan cosas distintas en cada contexto y que esa diferencia marca las costuras, hemos dibujado el mapa de contextos con sus patrones (Catálogo como OHS + PL con ACL en Pedidos; Clientes → Pedidos como Customer-Supplier; Pedidos ↔ Inventario como Partnership; Pedidos → Notificaciones como Conformist; ACL frente a la pasarela; sin shared kernel por decisión) y hemos definido el agregado Pedido con sus líneas y su dirección congelada, dejando fuera el stock, el cliente y el pago por razones de invariantes y contención. Finalmente, hemos fijado qué campos guarda cada contexto de los conceptos compartidos: el catálogo tiene la ficha completa; inventario, productoId y cantidades; pedidos, copias congeladas de nombre y precio en sus líneas.
Todo esto sigue siendo modelo. La siguiente lección lo hace físico: una base de datos por servicio. Veremos por qué la base de datos compartida es el antipatrón principal, cómo se rompen las claves foráneas cruzadas del esquema de 01-05 (pedidos.cliente_id, lineas_pedido.producto_id), qué alternativas hay para las consultas que hoy son un JOIN, cómo se migran los datos sin parar la tienda y qué esquema concreto tendrá cada servicio, incluido el catálogo en MongoDB.
Curso de Microservicios
Módulo 1: Introducción a los Microservicios
- Conceptos Básicos de Microservicios
- Ventajas y Desventajas de los Microservicios
- Comparación con la Arquitectura Monolítica
- Cuándo Adoptar Microservicios: Criterios de Decisión
- El Caso Práctico del Curso: la Tienda Online de TechCorp
Módulo 2: Diseño de Microservicios
- Principios de Diseño de Microservicios
- Descomposición de Aplicaciones Monolíticas
- Definición de Bounded Contexts
- Gestión de Datos: una Base de Datos por Servicio
- Consistencia Distribuida: Sagas, CQRS y Event Sourcing
Módulo 3: Comunicación entre Microservicios
- APIs RESTful
- Mensajería Asíncrona
- Protocolos de Comunicación: gRPC, GraphQL
- API Gateway y Backend for Frontend
- Descubrimiento de Servicios y Balanceo de Carga
- Contratos y Versionado de APIs
Módulo 4: Implementación de Microservicios
- Elección de Tecnologías y Herramientas
- Desarrollo de un Microservicio Simple
- Gestión de Configuración
- Integración Práctica: Consumir APIs y Publicar Eventos
- Pruebas en Microservicios: Unitarias, de Integración y de Contrato
Módulo 5: Despliegue y Orquestación
- Contenedores y Docker
- Orquestación con Kubernetes
- CI/CD para Microservicios
- Estrategias de Despliegue: Rolling, Blue-Green y Canary
- Service Mesh: Istio y Linkerd
Módulo 6: Monitoreo y Mantenimiento
- Monitoreo y Logging
- Trazabilidad Distribuida con OpenTelemetry
- Gestión de Errores y Recuperación
- Escalabilidad y Rendimiento
- SLOs, Alertas y Gestión de Incidentes
Módulo 7: Seguridad en Microservicios
- Autenticación y Autorización
- Seguridad en la Comunicación
- Prácticas de Seguridad
- Seguridad en Contenedores y Kubernetes
