Con los principios de diseño como lista de comprobación, el equipo de Luis se enfrenta a la pregunta práctica: ¿por dónde se corta el monolito? Descomponer no es dibujar seis cajas con los nombres de las carpetas del repositorio. Es encontrar las costuras reales del sistema (los lugares donde el código y los datos ya están, o pueden estar, poco acoplados), decidir una estrategia de corte coherente, elegir un orden de extracción que minimice el riesgo y aplicar técnicas que permitan extraer piezas sin parar la tienda.
En esta lección recorremos las estrategias de descomposición (por capacidad de negocio, por subdominio, por casos de uso frente a recursos, por equipos), las técnicas para descubrir las costuras (event storming introductorio, análisis de dependencias del código y análisis de acoplamiento de las tablas del esquema SQL de 01-05), las dos técnicas de extracción segura más usadas (strangler fig y branch by abstraction), los criterios para ordenar la extracción y qué hacer con las utilidades compartidas como bd/conexion.js o servicios/correo.js. Terminamos con el ejemplo guiado: el monolito techcorp-shop descompuesto en los seis servicios del mapa objetivo, con el diagrama de dependencias antes y después. Cómo se reparten físicamente los datos (02-04) y cómo se sustituye la transacción única (02-05) quedan para las lecciones siguientes.
Contenido
- Qué significa descomponer (y qué no)
- Estrategias de descomposición
- Técnicas para descubrir las costuras
- Extraer sin romper: strangler fig y branch by abstraction
- En qué orden extraer: riesgo, valor y dependencias
- Qué hacer con las utilidades compartidas
- Ejemplo guiado:
techcorp-shopen seis servicios
- Qué significa descomponer (y qué no)
Descomponer un monolito es redistribuir responsabilidades, código y datos en unidades autónomas que cumplan los principios de 02-01. Tres aclaraciones evitan malentendidos frecuentes:
- No es "un servicio por carpeta". Que el monolito tenga
controladores/pedidosControlador.jsno garantiza que "pedidos" sea una buena frontera: como vimos, esa función toca cinco áreas. Las carpetas son una pista, no la respuesta. - No es "un servicio por tabla". Sería el servicio de entidad de 01-04. Un servicio agrupa las tablas que cambian juntas bajo una capacidad.
- No es un acto único. Es un proceso incremental en el que cada extracción deja el sistema funcionando y aporta valor por sí misma. Si una extracción solo tiene sentido "cuando estén todas", es un big bang disfrazado.
El resultado de la descomposición es un plan: qué servicios habrá, qué se lleva cada uno del monolito, en qué orden se extraen y con qué técnica. Ese plan es el entregable de esta lección para TechCorp.
- Estrategias de descomposición
Hay cuatro formas habituales de decidir las fronteras. No son excluyentes: en la práctica se combinan, y una sirve para validar a otra.
2.1 Por capacidad de negocio
Se pregunta "¿qué hace la empresa?" y se responde con una lista de capacidades estables en el tiempo: gestionar el catálogo, controlar el inventario, tramitar pedidos, cobrar, atender clientes, comunicarse con ellos. Cada capacidad tiene un responsable de negocio, un vocabulario y un ritmo de cambio propios (principio 3 de 02-01).
Es la estrategia más usada porque las capacidades cambian mucho menos que la tecnología o la organización: TechCorp cobraba pedidos hace diez años y los cobrará dentro de diez, aunque cambie de pasarela tres veces.
2.2 Por subdominio (DDD)
Domain-Driven Design parte del dominio (el negocio completo) y lo divide en subdominios: partes del problema con su propio modelo y su propio lenguaje. Suele producir fronteras muy parecidas a las capacidades de negocio, pero con dos aportaciones adicionales:
- Distingue subdominios core (donde la empresa compite), de soporte y genéricos, lo que ayuda a decidir dónde invertir esfuerzo de diseño.
- Detecta que una misma palabra ("producto", "cliente") significa cosas distintas en subdominios distintos, y que esa diferencia de significado es una costura.
En esta lección usamos DDD solo como estrategia de corte; la definición rigurosa de los bounded contexts de TechCorp, el lenguaje ubicuo y el mapa de contextos son el contenido de 02-03.
2.3 Por "verbos / casos de uso" frente a "sustantivos / recursos"
Es una decisión de granularidad dentro de cualquiera de las anteriores:
| Enfoque | Se organiza en torno a... | Ejemplo | Cuándo funciona | Riesgo |
|---|---|---|---|---|
| Sustantivos / recursos | Entidades del dominio: producto, pedido, pago. | servicio-pedidos es dueño de la entidad Pedido y de todo su ciclo de vida. |
Cuando la entidad tiene un ciclo de vida rico y las operaciones sobre ella están cohesionadas. | Degenerar en servicio de entidad (CRUD sin negocio). |
| Verbos / casos de uso | Procesos: hacer un pedido, devolver un pedido, reponer stock. | servicio-checkout que orquesta la compra de principio a fin. |
Cuando un proceso cruza muchas entidades y merece un dueño propio. | Que el servicio "verbo" acabe conociendo los datos de todos (vuelve el crearPedido de siempre, pero por HTTP). |
Para TechCorp la elección es sustantivos como base (pedidos, productos, stock, pagos, clientes) y el flujo "hacer un pedido" repartido entre ellos mediante eventos, sin un servicio "checkout" separado. La razón se verá con detalle en 02-05: el flujo se coordina por coreografía y servicio-pedidos es dueño del estado del pedido, no de las operaciones de los demás. Si el flujo se complicase (devoluciones parciales, pedidos con varios envíos), un servicio orquestador sería el momento de reconsiderarlo.
2.4 Por equipos
A veces se corta según quién va a mantener qué. Es la Ley de Conway usada a propósito (02-01, apartado 9): si Pagos y Notificaciones van a ser del mismo equipo, ¿deben ser un servicio o dos? La respuesta de TechCorp es dos, porque tienen ritmos de cambio y perfiles de riesgo distintos (Pagos toca dinero y una pasarela externa; Notificaciones toca plantillas y proveedores de correo), pero es legítimo que la organización influya en la granularidad final.
| Estrategia | Pregunta que responde | Fortaleza | Debilidad | Uso en TechCorp |
|---|---|---|---|---|
| Capacidad de negocio | ¿Qué hace la empresa? | Estable en el tiempo; entendible por negocio. | Puede ignorar acoplamientos técnicos reales. | Base del corte. |
| Subdominio (DDD) | ¿Dónde cambia el significado de las palabras? | Detecta costuras sutiles; prioriza el core. | Requiere formación del equipo. | Refinamiento en 02-03. |
| Verbos vs. sustantivos | ¿Entidades o procesos? | Ajusta granularidad. | Los "verbos" tienden a acaparar datos. | Sustantivos + eventos. |
| Por equipos | ¿Quién lo mantiene? | Alinea organización y sistema. | Fosiliza la organización actual. | Ajuste final de granularidad. |
- Técnicas para descubrir las costuras
Las estrategias dicen cómo pensar; las técnicas dicen dónde mirar. TechCorp usa tres, de más conversacional a más mecánica.
3.1 Event storming (nivel introductorio)
Es un taller en el que negocio y técnicos reconstruyen el dominio en una pared con notas adhesivas. En su versión mínima:
- Eventos de dominio (naranja): hechos relevantes en pasado. "Pedido creado", "Stock reservado", "Pago confirmado", "Correo enviado", "Producto publicado", "Precio cambiado", "Cliente registrado", "Stock repuesto".
- Comandos (azul): la intención que provoca el evento. "Crear pedido", "Reservar stock", "Cobrar".
- Actores y sistemas externos (amarillo / rosa): cliente, operario de almacén, pasarela de pago, proveedor de correo.
- Se ordenan en una línea temporal y se buscan eventos pivote: aquellos tras los cuales cambia el vocabulario y cambian las personas que hablan.
Lo que se descubre para TechCorp, resumido:
flowchart LR
subgraph Catalogo[Catálogo]
E1[Producto publicado] --> E2[Precio cambiado]
end
subgraph Clientes
E3[Cliente registrado]
end
subgraph Pedidos
E4[Pedido creado] --> E7[Pedido confirmado]
E4 --> E8[Pedido cancelado]
end
subgraph Inventario
E5[Stock reservado]
E9[Stock repuesto]
E10[Stock liberado]
end
subgraph Pagos
E6[Pago confirmado]
E11[Pago reembolsado]
end
subgraph Notificaciones
E12[Correo enviado]
end
E4 -.-> E5 -.-> E6 -.-> E7 -.-> E12
Los grupos de eventos que comparten vocabulario y actor son los candidatos a servicio; las flechas discontinuas entre grupos son los eventos que cruzarán fronteras (los pedido.creado, stock.reservado, pago.confirmado, pedido.confirmado y pedido.cancelado de 01-05 salen literalmente de esta pared). Fíjate en que aparecen dos eventos que aún no habíamos nombrado, "Stock liberado" y "Pago reembolsado": son las compensaciones que necesitará la saga de 02-05. El taller también revela que "Precio cambiado" no interesa a Pedidos (un pedido guarda el precio del momento de la compra), lo que anticipa una decisión de datos de 02-03.
3.2 Análisis de dependencias del código
La segunda técnica es mecánica: quién importa a quién dentro del repositorio del monolito. Un grafo de require/import (herramientas como madge o dependency-cruiser lo generan en segundos) muestra los módulos con muchas dependencias entrantes (difíciles de extraer porque todos los usan) y salientes (difíciles de extraer porque usan a todos).
# En la raíz del monolito techcorp-shop: grafo de dependencias entre módulos
npx madge --extensions js --json ./ > dependencias.json
# Módulos con más dependencias entrantes (¿de quién depende todo el mundo?)
npx madge --extensions js --summary ./Resumen de lo que ve el equipo de Luis en techcorp-shop:
| Módulo | Depende de (salientes) | Lo usan (entrantes) | Lectura |
|---|---|---|---|
bd/conexion.js |
— | Todos los controladores | Utilidad técnica compartida; tratar según el apartado 6. |
controladores/pedidosControlador.js |
bd, pasarelaPago, correo |
rutas/pedidos.js |
Pocas dependencias de módulo, pero muchísimas de tablas (ver 3.3): el acoplamiento está en el SQL, no en los require. |
controladores/catalogoControlador.js |
bd |
rutas/catalogo.js |
Autónomo a nivel de código. Buen primer candidato. |
servicios/correo.js |
— | pedidosControlador, clientesControlador, enviosControlador |
Utilidad con lógica de negocio dentro (plantillas). Candidata a servicio. |
servicios/pasarelaPago.js |
— | pedidosControlador, devolucionesControlador |
Adaptador externo; se lo llevará servicio-pagos. |
La lección de esta tabla: en un monolito con base de datos compartida, el grafo de require engaña. Los módulos parecen independientes porque el acoplamiento real pasa por las tablas. De ahí la tercera técnica.
3.3 Análisis de acoplamiento de tablas
Se construye una matriz tabla × área marcando quién lee (L) y quién escribe (E) cada tabla, a partir del código SQL del monolito. Para el esquema de 01-05:
| Tabla | Clientes | Catálogo | Inventario | Pedidos | Pagos | Notificaciones | Dueño natural |
|---|---|---|---|---|---|---|---|
clientes |
E | — | — | L (crearPedido) |
— | L (email, nombre) | Clientes |
productos |
— | E | L (FK) | L (nombre, precio) | — | — | Catálogo |
atributos_producto |
— | E | — | — | — | — | Catálogo |
stock |
— | — | E | E (crearPedido reserva y descuenta) |
— | — | Inventario |
pedidos |
— | — | — | E | L (pedido_id) |
L (nuevo correo) | Pedidos |
lineas_pedido |
— | L (informe "más vendidos") | — | E | — | — | Pedidos |
pagos |
— | — | — | E (crearPedido inserta) |
E | — | Pagos |
notificaciones |
— | — | — | — | — | E | Notificaciones |
Cómo se lee:
- Una tabla con una sola E y ninguna L ajena (
atributos_producto,notificaciones) es una costura limpia: se extrae con su área sin tocar nada más. - Una tabla con una E propia y varias L ajenas (
clientes,productos,pedidos) se extrae con su dueño; las lecturas ajenas se convierten en llamadas al contrato o en copias de solo lectura (02-04). - Una tabla con dos E (
stock,pagos) es un problema de diseño, no solo de extracción: hoycrearPedidoescribe en tablas de Inventario y de Pagos. Antes de extraer hay que devolver la escritura a su dueño (Pedidos pedirá a Inventario que reserve, no reservará él). Esa es la primera refactorización que hará el equipo de Luis dentro del monolito, antes de mover nada.
Esta matriz es la herramienta más valiosa de toda la lección: convierte "creo que pedidos está muy acoplado" en "pedidos escribe en dos tablas ajenas y lee otras dos", que es algo sobre lo que se puede planificar.
- Extraer sin romper: strangler fig y branch by abstraction
Una vez decidido qué extraer, hace falta cómo hacerlo con la tienda funcionando. Dos técnicas cubren la mayoría de casos. Aquí las presentamos como herramientas de descomposición; el relato completo de la migración de TechCorp, paso a paso, está en 08-01.
4.1 Strangler fig (higuera estranguladora)
Se coloca una fachada (en TechCorp, el API Gateway del puerto 8080) delante del monolito. Al principio, todo el tráfico va al monolito. Cuando un servicio nuevo está listo, la fachada redirige solo sus rutas al servicio; el resto sigue yendo al monolito. Poco a poco, el monolito recibe menos rutas hasta quedar vacío y apagarse.
flowchart LR
C[Cliente] --> GW[API Gateway :8080]
GW -- "/productos/* (ya extraído)" --> CAT[servicio-catalogo :3001]
GW -- "/pedidos/*, /clientes/*, /pagos/* (todavía)" --> MONO[Monolito techcorp-shop]
CAT --> M1[(MongoDB catálogo)]
MONO --> PG[(PostgreSQL techcorp)]
Ventajas: reversible (si el servicio nuevo falla, la fachada vuelve a apuntar al monolito), incremental y visible para el negocio (cada ruta migrada es un hito). Requisito: la fachada debe existir antes de la primera extracción, lo que la convierte en el primer entregable técnico del plan.
4.2 Branch by abstraction
Sirve para las dependencias internas que la fachada no ve: por ejemplo, crearPedido obtiene precios leyendo la tabla productos. No se puede redirigir "por ruta"; hay que cambiar el código del monolito. La técnica:
- Introducir una abstracción delante de la funcionalidad (una interfaz
RepositorioProductos). - Hacer que el código existente la use (la implementación inicial sigue leyendo la tabla).
- Escribir una segunda implementación que use el nuevo servicio (cliente HTTP de
servicio-catalogo). - Conmutar entre ambas con configuración (una feature flag), primero en pruebas, luego en producción, con vuelta atrás inmediata si algo falla.
- Borrar la implementación antigua.
// monolito/pedidos/repositorioProductos.js (branch by abstraction, esqueleto)
// Paso 1-2: la abstracción. crearPedido deja de hacer SQL directo y llama a esto.
class RepositorioProductosSQL { // implementación antigua: tabla local
async obtenerPorIds(ids) {
const { rows } = await bd.query(
'SELECT id AS "productoId", nombre, precio FROM productos WHERE id = ANY($1) AND activo', [ids]);
return rows;
}
}
class RepositorioProductosHTTP { // implementación nueva: contrato del servicio
async obtenerPorIds(ids) {
// El detalle del cliente HTTP (timeouts, reintentos) se ve en 04-04; aquí solo el contrato.
return await catalogoCliente.get('/productos', { ids: ids.join(',') });
}
}
// Paso 4: la conmutación por configuración. Mismo contrato, dos orígenes.
function crearRepositorioProductos(config) {
return config.CATALOGO_REMOTO === 'true'
? new RepositorioProductosHTTP()
: new RepositorioProductosSQL();
}
module.exports = { crearRepositorioProductos };Fíjate en que ambas implementaciones devuelven la misma forma (productoId, nombre, precio): esa forma es el embrión del contrato del catálogo, y hacerla explícita dentro del monolito es ya un paso de descomposición aunque no se haya movido una sola línea de servidor.
| Técnica | Actúa sobre | Reversible | Sirve para |
|---|---|---|---|
| Strangler fig | Tráfico de entrada (rutas HTTP) | Sí (cambio de enrutado) | Extraer funcionalidades expuestas al exterior. |
| Branch by abstraction | Dependencias internas del código | Sí (feature flag) | Sustituir un colaborador interno por un servicio remoto. |
- En qué orden extraer: riesgo, valor y dependencias
No todos los servicios se extraen a la vez ni en cualquier orden. TechCorp puntúa cada candidato en tres ejes:
- Valor: cuánto alivia un problema real de los cinco de 01-05 (escalado en campañas, despliegues, incidentes que se propagan, modelo de datos forzado, equipos que se pisan).
- Riesgo: cuánto negocio se pone en juego si la extracción sale mal, y cuánta lógica delicada (dinero, identidad) se toca.
- Dependencias: cuántas áreas dependen de él (entrantes) y de cuántas depende (salientes). Pocas dependencias, extracción fácil.
| Servicio | Valor | Riesgo | Dependencias entrantes / salientes | Puntuación y orden |
|---|---|---|---|---|
servicio-catalogo |
Muy alto: es lo que se satura ×20 en campañas y lo que sufre el modelo clave-valor. | Bajo: es de lectura mayoritaria; si falla, la tienda muestra un error de búsqueda, no pierde cobros. | Entrantes: Pedidos (nombre y precio). Salientes: ninguna. | 1.º |
servicio-inventario |
Alto: escala con las campañas y es el primer paso de la saga. | Medio: un error de stock vende lo que no hay. | Entrantes: Pedidos. Salientes: ninguna (solo necesita productoId). |
2.º |
servicio-notificaciones |
Alto: es el origen de la fuga de memoria que tumbó los cobros; sacarlo aísla el incidente. | Bajo: si falla, se retrasa un correo. | Entrantes: Pedidos, Clientes. Salientes: lee clientes y pedidos (se sustituye por datos en el evento). |
3.º |
servicio-pagos |
Alto: aísla la pasarela externa y su latencia; permite acotar el alcance PCI. | Alto: dinero. | Entrantes: Pedidos. Salientes: pasarela externa. | 4.º |
servicio-pedidos |
Muy alto: es el core, el que más cambia y el que Luis quiere desplegar a diario. | Alto: es el flujo de venta. | Entrantes: Notificaciones, Pagos, informes. Salientes: todas (clientes, catálogo, inventario, pagos). | 5.º: se extrae cuando sus colaboradores ya existen como servicios y la saga está diseñada. |
servicio-clientes |
Medio: la autenticación se delega en Keycloak, así que el monolito ya se aligera sin extraerlo. | Alto: identidad y datos personales; cliente_id está en todas partes. |
Entrantes: todas las áreas. Salientes: ninguna. | 6.º: último; mientras tanto, el monolito residual sirve /clientes. |
Tres razones resumen por qué el catálogo va primero: es donde está el problema más caro (las caídas de campaña), es la extracción con menos riesgo (no toca dinero ni el flujo de pedido en su camino crítico) y no depende de nadie: solo Pedidos depende de él para dos campos, y eso se resuelve con la abstracción del apartado 4.2. Extraerlo primero da además una victoria temprana que justifica la inversión ante Marta y entrena al equipo de plataforma en el pipeline completo (Docker, Kubernetes, canary) con el servicio menos peligroso.
Y una razón por la que pedidos no va primero aunque sea el core: extraer primero el servicio que depende de todos obligaría a construir seis contratos a la vez o a que servicio-pedidos siguiera leyendo la base de datos del monolito, es decir, a nacer como monolito distribuido.
- Qué hacer con las utilidades compartidas
Todo monolito tiene código que "usa todo el mundo". En techcorp-shop: bd/conexion.js, servicios/correo.js, servicios/pasarelaPago.js, además de utilidades menores (registro de logs, validación de peticiones, formato de errores). Hay cuatro destinos posibles, y elegir mal crea acoplamiento nuevo:
| Utilidad | Naturaleza | Destino recomendado | Por qué |
|---|---|---|---|
bd/conexion.js (pool de PostgreSQL) |
Técnica, sin negocio, 30 líneas | Copiar en cada servicio (o plantilla de proyecto). | Cada servicio tiene su propia BD y su propia configuración; una librería compartida para 30 líneas acopla los despliegues por nada. |
| Registro de logs, formato de errores HTTP, validación de JSON | Técnica, sin negocio | Librería interna versionada (@techcorp/comun-http), publicada en un registro privado, con versiones semánticas. |
Se acepta librería compartida solo para código sin lógica de negocio; cada servicio elige cuándo actualizar la versión, así que no hay acoplamiento de despliegue. |
servicios/correo.js (plantillas, envío SMTP) |
Contiene negocio (qué correos existen, qué dicen) | Se convierte en el núcleo de servicio-notificaciones. No se comparte. |
Si fuera librería, cada servicio enviaría sus correos y la fuga de memoria seguiría dentro de todos ellos. Como servicio, reacciona a eventos y aísla el fallo. |
servicios/pasarelaPago.js (adaptador de la pasarela) |
Adaptador externo con reglas de cobro | Se lo lleva servicio-pagos en exclusiva. |
Solo un servicio debe hablar con la pasarela; el resto pide "cobrar" por contrato. |
Modelos / DTOs de negocio (modelos/Pedido.js, modelos/Producto.js) |
Negocio | No se comparten nunca. Cada servicio define los suyos. | Compartir modelos de dominio es la "capa compartida" de 01-04: un cambio en Producto redespliega a todos. Cada contexto tiene su propio "producto" (02-03). |
La regla que TechCorp adopta: se comparte código técnico como librería versionada; el código de negocio no se comparte, se convierte en un servicio o se duplica conscientemente. Duplicar un DTO de 5 campos en dos servicios es barato; compartirlo cuesta la autonomía de ambos.
- Ejemplo guiado:
techcorp-shop en seis servicios
techcorp-shop en seis serviciosAplicamos todo lo anterior. El plan de descomposición de TechCorp:
Paso 1. Inventario del monolito. Carpetas, módulos y tablas (01-05), y matriz tabla × área (3.3).
Paso 2. Corte por capacidad de negocio, validado con los grupos de eventos del event storming (3.1) y con la matriz de tablas: seis capacidades, seis servicios. Coincide con las seis áreas funcionales, con dos matices que la matriz ha destapado: la reserva de stock la hará Inventario (hoy la hace crearPedido) y el registro del pago lo hará Pagos (hoy lo hace crearPedido).
Paso 3. Reparto de código y datos (los datos se detallan en 02-04; aquí, la asignación):
| Servicio | Se lleva del monolito | Tablas de las que será dueño | Deja de hacer |
|---|---|---|---|
servicio-catalogo (3001) |
controladores/catalogoControlador.js, rutas/catalogo.js, búsqueda |
productos, atributos_producto (que en 02-04 se convertirán en documentos de MongoDB) |
— |
servicio-inventario (3006) |
Lógica de reserva/liberación hoy repartida en crearPedido y en almacenControlador.js |
stock (+ nueva tabla de reservas) |
— |
servicio-pedidos (3002) |
pedidosControlador.js, rutas/pedidos.js |
pedidos, lineas_pedido |
Leer clientes y productos; escribir stock y pagos; enviar correo. |
servicio-pagos (3003) |
servicios/pasarelaPago.js, devolucionesControlador.js |
pagos |
— |
servicio-notificaciones (3005) |
servicios/correo.js, plantillas |
Registro mínimo de envíos (notificaciones) |
Leer clientes y pedidos (recibirá lo que necesita en los eventos). |
servicio-clientes (3004) |
clientesControlador.js, perfil, direcciones |
clientes |
Autenticar (se delega en Keycloak, 07-01). |
Paso 4. Diagrama de dependencias antes y después.
Antes: todos los módulos dependen de todas las tablas a través de un único pool de conexión. El grafo es, en la práctica, completo.
flowchart TB
subgraph MONO[Monolito techcorp-shop - un proceso, un despliegue]
PC[pedidosControlador]
CC[catalogoControlador]
CLC[clientesControlador]
AC[almacenControlador]
DC[devolucionesControlador]
CO[servicios/correo]
PP[servicios/pasarelaPago]
PC --> CO
PC --> PP
DC --> PP
CLC --> CO
end
subgraph PG[(PostgreSQL techcorp - un esquema)]
T1[clientes]
T2[productos]
T3[stock]
T4[pedidos / lineas_pedido]
T5[pagos]
T6[notificaciones]
end
PC --> T1
PC --> T2
PC --> T3
PC --> T4
PC --> T5
CC --> T2
CC --> T4
CLC --> T1
AC --> T3
AC --> T2
DC --> T5
DC --> T4
CO --> T6
CO --> T1
Después: cada servicio depende solo de su almacenamiento; entre servicios solo hay contratos (flechas continuas: llamadas síncronas a la API) y eventos (flechas discontinuas: publicación/suscripción). El cómo de esas flechas es el módulo 3.
flowchart TB
GW[API Gateway :8080] --> CAT[servicio-catalogo :3001]
GW --> CLI[servicio-clientes :3004]
GW --> PED[servicio-pedidos :3002]
CAT --> BDC[(MongoDB catálogo)]
CLI --> BDL[(PG clientes)]
PED --> BDP[(PG pedidos)]
INV[servicio-inventario :3006] --> BDI[(PG inventario)]
PAG[servicio-pagos :3003] --> BDG[(PG pagos)]
NOT[servicio-notificaciones :3005]
PED -- "GET /productos (nombre, precio)" --> CAT
PED -- "GET /clientes/{id} (existe, email)" --> CLI
PED -. "pedido.creado / pedido.confirmado / pedido.cancelado" .-> MQ[(RabbitMQ)]
MQ -. "pedido.creado" .-> INV
INV -. "stock.reservado" .-> MQ
MQ -. "stock.reservado" .-> PAG
PAG -. "pago.confirmado" .-> MQ
MQ -. "pago.confirmado" .-> PED
MQ -. "pedido.confirmado / pedido.cancelado" .-> NOT
PAG --> EXT1[Pasarela externa]
NOT --> EXT2[Correo / SMS]
Compara los dos grafos con la tabla de acoplamientos de 02-01: en el primero, todo es acoplamiento de datos y de despliegue; en el segundo, solo hay acoplamiento de contrato (dos llamadas síncronas de Pedidos) y de eventos. Queda un acoplamiento temporal deliberado (Pedidos consulta Catálogo y Clientes de forma síncrona al crear el pedido); en 02-04 veremos la alternativa de replicar esos datos y en 06-03 cómo proteger esas llamadas.
Paso 5. Orden y técnica de extracción. Catálogo (strangler por rutas /productos + branch by abstraction para los precios en crearPedido), Inventario (branch by abstraction para la reserva), Notificaciones (el monolito empieza a publicar eventos y el servicio los consume), Pagos (branch by abstraction para pasarelaPago), Pedidos (strangler de /pedidos, ya con la saga de 02-05) y Clientes (strangler de /clientes; el monolito se apaga).
Errores Comunes y Consejos
- Cortar por carpetas del repositorio. Las carpetas reflejan cómo se organizó el código hace años, no las costuras de negocio. Usa la matriz de tablas: no miente.
- Fiarse solo del grafo de
require. En un monolito con BD compartida, el acoplamiento está en el SQL. Un módulo sin dependencias de código puede escribir en cuatro tablas ajenas. - Extraer el core primero "porque es lo importante". Es lo que depende de todos; extraerlo primero obliga a nacer acoplado. Empieza por lo que no depende de nadie y aporta valor visible.
- Convertir las utilidades en una gran librería compartida. Cada actualización de la librería redespliega a todos: es acoplamiento de despliegue en estado puro. Solo código técnico, versionado, y cada servicio decide cuándo actualizar.
- Extraer sin fachada. Sin gateway no hay strangler fig ni marcha atrás barata. El gateway es la primera pieza, no la última.
- Consejo: antes de mover código a un servicio nuevo, arregla el acoplamiento dentro del monolito (devuelve las escrituras a su dueño, introduce las abstracciones). Un monolito bien modularizado se descompone en semanas; uno enredado, en años.
Ejercicios
Ejercicio 1: Leer la matriz
Con la matriz tabla × área del apartado 3.3: (a) ¿qué dos tablas exigen una refactorización dentro del monolito antes de poder extraer su servicio, y en qué consiste? (b) ¿Qué tabla es la de extracción más limpia y por qué? (c) Notificaciones lee clientes y pedidos; ¿cómo obtendrá esos datos cuando sea un servicio, sin leer tablas ajenas?
Ejercicio 2: Un séptimo servicio
Marta quiere añadir "promociones" (cupones de descuento, el caso de 01-03) durante la migración. Aplica los criterios de valor, riesgo y dependencias y decide: ¿se construye directamente como servicio-promociones o se añade primero al monolito? ¿En qué posición del orden de extracción encajaría? Justifica en 5-8 líneas.
Ejercicio 3: Branch by abstraction para la reserva de stock
Escribe el esqueleto (sin implementar detalles) de la abstracción que permitiría a crearPedido dejar de escribir en stock y pasar a pedir la reserva a servicio-inventario, con conmutación por configuración. Define el contrato de la abstracción (nombre del método, entrada, salida) e indica qué devolvería cada implementación cuando no hay stock.
Soluciones
Ejercicio 1
(a) stock y pagos: ambas tienen dos escritores. crearPedido reserva y descuenta stock, e inserta en pagos. Antes de extraer Inventario y Pagos, hay que mover esa lógica a sus módulos dueños dentro del monolito (por ejemplo, almacen.reservar(...) y pagos.registrarCobro(...)), de modo que crearPedido solo invoque y no escriba. Después, esas invocaciones se sustituyen por llamadas al servicio con branch by abstraction.
(b) atributos_producto (y notificaciones): un solo escritor, ningún lector ajeno. Se extrae con Catálogo sin que nadie más se entere; además, su modelo clave-valor es la razón para llevar el catálogo a MongoDB.
(c) Recibiéndolos en el evento: pedido.confirmado incluirá el pedidoId, el total y los datos mínimos del cliente necesarios para el correo (email, nombre) que servicio-pedidos habrá obtenido por contrato de Clientes. Alternativamente, Notificaciones podría consultar GET /clientes/{id} al recibir el evento; ambas opciones se comparan en 02-04. Lo que no hará nunca es SELECT sobre clientes.
Ejercicio 2
Valor: medio-alto para negocio (campañas), pero no resuelve ninguno de los cinco problemas de 01-05. Riesgo: bajo en sí mismo, pero alto por el momento: añadir un servicio nuevo mientras se migra el core duplica el frente de trabajo. Dependencias: promociones necesitaría conocer productos (a qué se aplica) y pedidos (dónde se aplica), es decir, depende de dos servicios que aún no están extraídos. Decisión razonable: construirlo dentro del monolito como un módulo bien aislado (su propia tabla cupones, una abstracción Promociones.calcularDescuento(lineas) que use crearPedido), respetando desde el primer día los principios de 02-01 para que su extracción posterior sea trivial. Encajaría en el orden entre Pagos y Pedidos (posición 4-5): después de que existan Catálogo e Inventario (sus dependencias) y antes de que Pedidos se extraiga con la saga definitiva, para que el campo descuentoAplicado de pedido.confirmado nazca ya en el contrato. Si el negocio no lo necesita con urgencia, mejor aún: después de Pedidos, con el sistema estable.
Ejercicio 3
// Contrato de la abstracción: reservar(pedidoId, lineas) -> { ok: true, reservaId }
// | { ok: false, motivo: 'SIN_STOCK', productoId }
class ReservaStockSQL { // implementación antigua: tabla local del monolito
async reservar(pedidoId, lineas) {
// UPDATE stock SET reservado = reservado + cantidad WHERE ... AND cantidad - reservado >= cantidad
// Si algún UPDATE afecta a 0 filas -> devolver { ok: false, motivo: 'SIN_STOCK', productoId }
// Si todo va bien -> { ok: true, reservaId: `local-${pedidoId}` }
}
async liberar(reservaId) { /* UPDATE stock SET reservado = reservado - cantidad ... */ }
}
class ReservaStockHTTP { // implementación nueva: contrato de servicio-inventario
async reservar(pedidoId, lineas) {
// POST /reservas { pedidoId, lineas } -> 201 { reservaId } o 409 { motivo: 'SIN_STOCK', productoId }
// Devuelve la misma forma que la implementación SQL.
}
async liberar(reservaId) { /* DELETE /reservas/{reservaId} */ }
}
function crearReservaStock(config) {
return config.INVENTARIO_REMOTO === 'true' ? new ReservaStockHTTP() : new ReservaStockSQL();
}Lo esencial: las dos implementaciones devuelven la misma forma en éxito y en fallo ({ ok, reservaId } / { ok: false, motivo }), de modo que crearPedido no distingue de dónde viene la reserva; y la abstracción incluye ya la operación inversa (liberar), porque en cuanto la reserva salga de la transacción única, harán falta compensaciones (02-05).
Conclusión
Descomponer un monolito es un trabajo de análisis antes que de código. Hemos visto las estrategias (capacidad de negocio como base, subdominios de DDD como refinamiento, sustantivos frente a verbos para ajustar granularidad, equipos como último ajuste), las técnicas para descubrir costuras (event storming, grafo de dependencias del código y, la más reveladora en un monolito con BD compartida, la matriz de acoplamiento tabla × área, que ha destapado que crearPedido escribe en stock y pagos sin ser su dueño), las técnicas de extracción segura (strangler fig tras el gateway y branch by abstraction con conmutación por configuración) y los criterios de orden (valor, riesgo, dependencias) que colocan al catálogo primero y a pedidos y clientes al final. También hemos decidido el destino de las utilidades compartidas: código técnico como librería versionada; código de negocio como servicio (correo.js → servicio-notificaciones, pasarelaPago.js → servicio-pagos) o duplicado a conciencia. El resultado es el plan de TechCorp: seis servicios, cada uno con sus módulos y sus tablas, y un grafo de dependencias que ha pasado de "todos contra todas las tablas" a "contratos y eventos".
Ese plan tiene todavía fronteras dibujadas a trazo grueso. La siguiente lección las afina con DDD estratégico: definiremos los bounded contexts de TechCorp, veremos por qué "producto" y "cliente" no significan lo mismo en cada contexto, dibujaremos el mapa de contextos con sus patrones de relación y decidiremos qué es un agregado (y por qué el stock no forma parte del agregado Pedido).
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
