Módulo 5: Extraer un servicio
El orden de extracción
Descripción
Las lecciones 2 a 6 te enseñaron a extraer un servicio de punta a punta: encontrar el bounded context, ponerle el ACL bidireccional, darle la propiedad de sus datos, cortar la BD compartida. Pero un monolito no tiene un solo bounded context —Mercado tiene cuatro: catalog, orders, payments, shipping—. Y el orden en que los extraes decide si la migración fluye o se atasca. Esta lección enseña a elegir el orden de extracción: cuál sale primero, cuál al final, y por qué.
El principio es corto y contraintuitivo para quien tiene prisa: se extrae primero el contexto más independiente, no el más importante ni el más doloroso. El contexto más independiente es la hoja —el que no depende de nadie más para funcionar, el que otros leen pero que no lee a nadie—. Extraer la hoja primero corta una dependencia limpia y sola: al sacarla, no arrastras nada consigo. Extraer el más acoplado primero —el hub del que todo cuelga— es lo contrario: para sacarlo, tendrías que desenredar sus dependencias con todos los demás, arrastrando medio sistema en la primera extracción.
Esta lección lo hace medible, extendiendo la medición de seam de la lección 2. Para cada uno de los cuatro módulos de Mercado, puntuamos su independencia —cuántas salidas tiene (de cuántos depende), cuántas entradas (quién lo lee), y si escribe estado compartido— y ordenamos de más a menos independiente. El resultado es un plan de extracción: catalog primero (hoja, 0 salidas), orders último (el hub, 3 salidas). Y vas a ver que extraer de las hojas hacia el hub tiene un efecto acumulativo: cada extracción de un contexto independiente simplifica el seam de los que dependían de él.
Conexión con el módulo. La lección 2 midió el seam para encontrar un bounded context; esta usa la misma medición, extendida a todos, para ordenarlos. Las lecciones 3 a 6 son la técnica que aplicarás a cada contexto en el orden que aquí decidas. La lección 8 (el capstone) ejecuta la extracción completa del primero de la lista, catalog. Fíjate en la frontera: aquí decidimos el orden de extracción por acoplamiento. La secuencia también puede pesar otros factores —el valor de negocio, el riesgo (el dinero de payments)— que se registran en el ADR de architecture-decisions-and-tradeoffs; este módulo aporta el eje de la independencia estructural, que es el que hace la extracción técnicamente limpia.
Una analogía: desenredar una madeja de estambre
Tienes una madeja de estambre enredada —un nudo de hilos cruzados— y quieres desarmarla sin hacer el nudo más apretado. ¿Por dónde empiezas?
Si jalas desde el centro del nudo, donde los hilos están más cruzados, el nudo se aprieta: cada hilo que mueves tira de otros tres, y en vez de soltar, enredas más. Es el peor lugar por donde empezar. Cualquiera que haya desenredado audífonos o luces navideñas lo sabe: atacar el enredo por el punto más enredado es garantía de frustración.
Lo que sí funciona es buscar el cabo suelto —el extremo del hilo que cuelga libre, que no está cruzado con nada— y jalar desde ahí. El cabo suelto sale limpio, sin tirar de nada, y al salir afloja un poco el resto del nudo. Entonces buscas el siguiente cabo, ahora un poco más accesible porque el primero ya salió, y jalas. Poco a poco, empezando siempre por lo más suelto, el nudo se deshace. Nunca atacas el centro; dejas que el centro se afloje solo a medida que quitas lo de las orillas.
En la extracción del monolito: el nudo es el sistema acoplado; los cabos sueltos son los bounded contexts hoja (los que no dependen de nadie, como catalog); y el centro del nudo es el hub (orders, del que todo cuelga). Empiezas por el cabo suelto —extraes la hoja—, que sale limpio y afloja el nudo: al sacar catalog, los módulos que dependían de él ahora dependen de un servicio limpio en vez de un pedazo del monolito, así que están un poco menos enredados. Sigues con el siguiente más suelto. Y el hub, orders, lo dejas para el final, cuando ya casi todos sus hilos —catalog, payments, shipping— salieron y el centro se aflojó solo. Jalar el hub primero apretaría el nudo; jalar las hojas primero lo deshace.
Ejemplo trabajado: puntuar la independencia y ordenar
No vamos a opinar el orden: lo vamos a calcular. Para cada módulo de Mercado definimos su perfil —de qué otros depende (depends_on, el seam de salida), quién lo lee (derivado), y si escribe estado compartido (write_heavy)—. Con eso puntuamos la independencia: menor puntaje = más independiente = se extrae primero. El puntaje domina el seam de salida, porque un módulo que no depende de nadie corre solo al extraerse.
# Perfil de cada modulo del monolito Mercado:
# depends_on -> a que otros modulos LLAMA (efferent / seam de salida)
# depended_by -> quien lo LLAMA a el (afferent / seam de entrada)
# write_heavy -> escribe mucho estado propio compartido (mas dificil de duenar)
modules = {
"catalog": {"depends_on": [], "write_heavy": False},
"payments": {"depends_on": ["orders"], "write_heavy": True},
"shipping": {"depends_on": ["catalog", "orders"], "write_heavy": True},
"orders": {"depends_on": ["catalog", "payments", "shipping"], "write_heavy": True},
}
# depended_by se deriva de los depends_on de los demas.
for m in modules:
modules[m]["depended_by"] = [o for o in modules if m in modules[o]["depends_on"]]
def extract_score(info):
# Menor = mas independiente = se extrae primero.
# Domina el seam de SALIDA: si no depende de nadie, corre solo al extraerse.
return len(info["depends_on"]) * 2 + (1 if info["write_heavy"] else 0)
ranked = sorted(modules.items(), key=lambda kv: extract_score(kv[1]))
print("Orden de extraccion: primero el bounded context mas independiente\n")
print(f"{'#':>6} {'modulo':<12}{'salidas':>7}{'entradas':>10}{'write?':>8}{'score':>7}")
print("-" * 52)
for i, (name, info) in enumerate(ranked, start=1):
print(f"{i:>6} {name:<12}"
f"{len(info['depends_on']):>7}{len(info['depended_by']):>10}"
f"{('si' if info['write_heavy'] else 'no'):>8}{extract_score(info):>7}")
print("-" * 52)
print("\nPlan de extraccion:", " -> ".join(name for name, _ in ranked))
print("\n catalog: 0 salidas (hoja), muchas entradas (lo leen), no escribe -> PRIMERO.")
print(" orders: 3 salidas (el hub del que todo cuelga) -> ULTIMO.")
print(" Extraer el mas acoplado primero (orders) arrastraria a los otros tres")
print(" con el; extraer la hoja primero corta una dependencia limpia y sola.")
Qué esperar. Al correr el archivo, la salida es exactamente esta:
Orden de extraccion: primero el bounded context mas independiente
# modulo salidas entradas write? score
----------------------------------------------------
1 catalog 0 2 no 0
2 payments 1 1 si 3
3 shipping 2 1 si 5
4 orders 3 2 si 7
----------------------------------------------------
Plan de extraccion: catalog -> payments -> shipping -> orders
catalog: 0 salidas (hoja), muchas entradas (lo leen), no escribe -> PRIMERO.
orders: 3 salidas (el hub del que todo cuelga) -> ULTIMO.
Extraer el mas acoplado primero (orders) arrastraria a los otros tres
con el; extraer la hoja primero corta una dependencia limpia y sola.
Lee la tabla de arriba hacia abajo, porque el orden de los renglones es el plan de extracción, y la columna score lo justifica.
catalog (score 0) va primero. Tiene 0 salidas —no depende de ningún otro módulo—, muchas entradas (lo leen), y no escribe estado compartido (write: no). Es el cabo suelto perfecto: puedes jalarlo y sale limpio, sin arrastrar nada. Cuando lo extraes, no necesita llamar de vuelta al monolito, porque no dependía de nadie. Score cero: la independencia máxima.
orders (score 7) va último. Tiene 3 salidas —depende de catalog, payments y shipping—: es el centro del nudo, el hub del que todo cuelga. Extraerlo primero significaría, para que funcione como servicio, tener que llamar de vuelta al monolito a los otros tres módulos en cada operación —cada llamada ahora cruzando la red—. Es el hilo más cruzado; jalarlo primero aprieta el nudo. Se deja para el final, cuando los otros tres ya salieron y sus dependencias apuntan a servicios limpios.
En medio, payments (score 3) y shipping (score 5), ordenados por su acoplamiento: payments depende de uno (orders), shipping de dos (catalog, orders). El puntaje los coloca según cuántas ataduras tienen hacia adentro del monolito.
El Plan de extracción: catalog -> payments -> shipping -> orders es la lectura directa de la tabla. Y fíjate en el efecto acumulativo que menciona el cierre: al extraer catalog primero, shipping —que dependía de catalog— ahora depende de un servicio limpio en vez de un pedazo del monolito. Cada extracción de una hoja afloja el nudo para las siguientes: el seam de los que quedan se simplifica a medida que sus dependencias salen del monolito. Por eso se extrae de las hojas hacia el hub, nunca al revés.
Profundización: la independencia estructural y los otros factores
El puntaje de este ejemplo mide una cosa —la independencia estructural, cuántas ataduras tiene cada contexto hacia adentro del monolito— y es el eje que hace la extracción técnicamente limpia. Pero conviene ser honesto sobre dos matices: cómo se combina con otros factores, y qué pasa con el efecto acumulativo.
El acoplamiento no es el único factor, pero es el que hace la extracción posible. Una migración real también pesa el valor de negocio (¿qué contexto, extraído, entrega más beneficio?) y el riesgo (¿cuál duele más si sale mal?). Fíjate en payments: por acoplamiento estructural sale segundo (score 3, poca dependencia), pero maneja dinero —un fallo en su extracción es mucho más caro que uno en el catálogo—. Un equipo prudente podría, por riesgo, moverlo más tarde en la secuencia aunque su independencia lo permita antes, y practicar primero con contextos donde equivocarse es barato. El puntaje de independencia dice qué es técnicamente fácil de sacar; la secuencia final combina eso con valor y riesgo, que es una decisión que se registra en el ADR (architecture-decisions-and-tradeoffs). Lo que no cambia por ningún factor es que catalog —la hoja de bajo riesgo y alto uso— va primero, y orders —el hub— va al final. Ese esqueleto lo fija el acoplamiento; los factores de negocio afinan el medio.
El efecto acumulativo: extraer aflojan el nudo. Los puntajes del ejemplo son una foto del estado inicial. Pero el acoplamiento cambia a medida que extraes: cuando catalog sale, deja de ser un pedazo del monolito y pasa a ser un servicio con API limpia. Los módulos que dependían de catalog —shipping, orders— ahora tienen una dependencia hacia un servicio, no hacia adentro del monolito, lo que es más fácil de manejar. Si recalcularas los puntajes después de cada extracción, verías el nudo aflojarse: contextos antes enredados se vuelven extraíbles a medida que sus dependencias salen. Por eso el orden hojas→hub no es solo "lo fácil primero"; es una estrategia donde cada paso habilita el siguiente.
Extraer de las hojas hacia el hub afloja el nudo:
paso 0 (inicial): catalog <- shipping <- orders -> payments
(todos dentro del monolito, enredados)
paso 1 (catalog fuera): [catalog svc] <- shipping <- orders
shipping ahora depende de un SERVICIO, no del monolito
paso 2, 3, ...: cada hoja que sale simplifica el seam de los que quedan,
hasta que orders (el hub) queda casi solo -> extraible
Y una advertencia sobre el hub prematuro. La tentación de extraer orders primero es real: suele ser el módulo más central, el más valioso, el que "todo el mundo quiere que sea un servicio". Pero es el centro del nudo. Extraerlo primero te obliga a decidir, de golpe, cómo se comunica con catalog, payments y shipping —que todavía están dentro del monolito—, creando un servicio que llama de vuelta al monolito por todos lados: un "servicio distribuido" que es más lento y más frágil que el monolito que tenías. El hub se extrae cuando sus dependencias ya son servicios, no antes.
Errores comunes
Extraer el contexto más acoplado primero. Qué pasa: el equipo empieza por orders (o cualquier hub) porque es el más importante o el más central. Por qué pasa: el hub es el módulo estrella; parece el que más se beneficia de ser un servicio, y la prisa empuja a atacarlo primero. Cómo detectarlo: el "servicio" extraído llama de vuelta al monolito constantemente —a catalog, a payments, a shipping— porque sus dependencias siguen adentro; cada operación cruza la red varias veces. Cómo corregirlo: extrae de las hojas hacia el hub, nunca al revés. Empieza por el contexto con menos salidas (catalog, score 0), que sale limpio y afloja el nudo. El hub se extrae al final, cuando sus dependencias ya son servicios. Jalar el centro del nudo primero lo aprieta; jalar el cabo suelto lo deshace.
Elegir el orden por importancia o dolor en vez de por acoplamiento. Qué pasa: el orden se decide por "cuál nos importa más" o "cuál nos duele más", ignorando el seam. Por qué pasa: el valor y el dolor son visibles y urgentes; el acoplamiento es abstracto. Cómo detectarlo: la primera extracción se atasca desenredando dependencias que nadie midió, en vez de avanzar. Cómo corregirlo: el acoplamiento es el eje que decide qué es técnicamente extraíble primero, y ese eje pone la hoja al frente. El valor y el riesgo afinan el medio de la lista (y pueden diferir el dinero de payments), pero no cambian el esqueleto: hoja primero, hub al final. Mide el seam, ordena por independencia, y luego ajusta por valor y riesgo —no al revés—. Empezar por lo importante sin mirar el acoplamiento es empezar por el centro del nudo con los ojos vendados.
No recalcular el orden a medida que avanza la migración. Qué pasa: el equipo hace el plan una vez, al inicio, y lo sigue al pie de la letra sin notar que cada extracción cambió el mapa de acoplamiento. Por qué pasa: el plan inicial se siente definitivo. Cómo detectarlo: un contexto que en el plan iba "difícil" ya es fácil (sus dependencias ya salieron), pero el equipo sigue posponiéndolo por inercia. Cómo corregirlo: el orden de extracción es dinámico. Después de cada extracción, el nudo se aflojó: contextos antes enredados pueden haberse vuelto extraíbles. Revisa el seam cada tanto y deja que el orden se adapte —el efecto acumulativo de sacar las hojas es justamente que el hub, imposible al inicio, se vuelve alcanzable al final—. Un plan de extracción es una brújula que se recalibra, no un mapa congelado.
Ejercicios
Ejercicio 1 — El cabo suelto. Con la analogía de la madeja de estambre, explica: (a) por qué jalar desde el centro del nudo lo aprieta; (b) qué representa el cabo suelto en la extracción; (c) cómo "aflojar el nudo" se traduce al efecto de extraer una hoja.
Ver solución
(a) Jalar desde el centro del nudo lo aprieta porque ahí los hilos están más cruzados: cada hilo que mueves tira de otros tres, así que en vez de soltar, enredas más. En la extracción, el centro del nudo es el hub (orders, con 3 salidas): extraerlo primero obliga a desenredar sus dependencias con catalog, payments y shipping de golpe, creando un servicio que llama de vuelta al monolito por todos lados —el nudo apretado—.
(b) El cabo suelto representa el bounded context hoja: el que no está cruzado con nada, el que no depende de nadie (0 salidas), como catalog. Es el extremo libre del hilo: puedes jalarlo y sale limpio, sin tirar de nada más. Es por donde se empieza a deshacer el monolito.
(c) "Aflojar el nudo" se traduce en que, al extraer una hoja, los módulos que dependían de ella pasan a depender de un servicio con API limpia en vez de un pedazo del monolito. Cuando sacas catalog, shipping —que lo leía— ahora tiene una dependencia hacia un servicio, más fácil de manejar. El seam de los que quedan se simplifica: el nudo, un poco más flojo. Cada hoja extraída hace más accesible el siguiente cabo, hasta que el hub, imposible al inicio, queda casi solo y se vuelve extraíble.
Ejercicio 2 — Lee el puntaje. En la salida, catalog tuvo score 0 (0 salidas) y orders score 7 (3 salidas). (a) ¿Por qué la columna salidas domina el puntaje y no entradas? (b) ¿Qué problema concreto tendría orders extraído primero? (c) ¿Por qué payments (score 3) y shipping (score 5) van en medio, en ese orden?
Ver solución
(a) Porque las salidas cuentan de cuántos otros módulos depende este para funcionar, y esa es la medida de qué tanto se arrastra al extraerlo. Un módulo con 0 salidas corre solo; uno con 3 salidas, extraído, tiene que llamar de vuelta al monolito a esos 3. Las entradas (quién lo lee) miden qué tan útil es extraerlo, pero no qué tan difícil —leer desde otros no crea ataduras hacia otros—. El puntaje domina las salidas porque el orden de extracción se trata de minimizar lo que arrastras, y lo que arrastras son las dependencias de salida.
(b) orders extraído primero tendría que llamar de vuelta al monolito a catalog, payments y shipping —que siguen adentro— en cada operación, cada llamada ahora cruzando la red con su latencia y su posibilidad de fallar. Sería un "servicio" más lento y más frágil que el módulo que era: un servicio distribuido con todas las desventajas de la red y ninguna de las ventajas de la independencia, porque sus dependencias siguen atrapadas en el monolito. Es el centro del nudo jalado primero.
(c) Van en medio porque su acoplamiento de salida está entre el de la hoja y el del hub: payments depende de uno (orders, 1 salida, score 3), shipping depende de dos (catalog y orders, 2 salidas, score 5). El puntaje los ordena por cuántas ataduras hacia adentro del monolito tienen: menos ataduras, más temprano. payments antes que shipping porque tiene menos dependencias de salida. (En una migración real, el riesgo del dinero de payments podría diferirlo, pero por acoplamiento puro sale antes que shipping.)
Ejercicio 3 — El orden dinámico. Después de extraer catalog (paso 1), el equipo se dispone a extraer shipping, que dependía de catalog y de orders. (a) ¿Cómo cambió el acoplamiento de shipping al salir catalog? (b) ¿Por qué esto ilustra que el orden es dinámico? (c) ¿Qué contexto sigue siendo el más difícil y por qué?
Ver solución
(a) Antes, shipping dependía de catalog y orders, ambos dentro del monolito (dos ataduras hacia adentro). Al salir catalog, una de esas dependencias ahora apunta a un servicio con API limpia en vez de a un pedazo del monolito. shipping sigue teniendo dos dependencias, pero una de ellas es ahora hacia un servicio bien definido —más fácil de manejar—: su seam se simplificó. El nudo se aflojó un poco para shipping.
(b) Ilustra que el orden es dinámico porque el mapa de acoplamiento cambia con cada extracción. El puntaje inicial era una foto del estado de partida; después de sacar catalog, el estado es distinto, y si recalcularas los puntajes, shipping estaría un poco más "suelto" que al inicio. Un plan de extracción no es un mapa congelado que se sigue ciegamente, sino una brújula que se recalibra: cada hoja extraída puede volver extraíble a un contexto que antes estaba enredado.
(c) orders sigue siendo el más difícil, porque es el hub: depende de catalog, payments y shipping. Aunque catalog ya salió (una de sus dependencias es ahora un servicio), todavía depende de payments y shipping, que siguen dentro del monolito. orders se vuelve verdaderamente extraíble solo cuando sus tres dependencias son servicios —es decir, al final—. Ese es el sentido de extraer de las hojas hacia el hub: el hub se deja para cuando el nudo, aflojado por todas las hojas que salieron, lo suelta casi solo.
Resumen y siguiente paso
En esta lección subiste la mirada del árbol al bosque: el orden de extracción. Viste, con la madeja de estambre, que se empieza por el cabo suelto (la hoja) y no por el centro del nudo (el hub), porque jalar el centro aprieta y jalar el cabo afloja. Y lo calculaste: puntuaste la independencia de los cuatro módulos de Mercado y obtuviste el plan catalog → payments → shipping → orders, con catalog primero (hoja, 0 salidas, score 0) y orders último (hub, 3 salidas, score 7). Aprendiste que la independencia estructural es el eje que hace la extracción técnicamente limpia, que el valor y el riesgo afinan el medio de la lista (y pueden diferir el dinero de payments), y que el orden es dinámico: cada hoja extraída afloja el nudo para las siguientes.
Antes de avanzar deberías poder: enunciar el principio del orden (la hoja primero, el hub al final) y por qué; leer un puntaje de independencia y producir el plan de extracción; explicar por qué extraer el hub primero crea un servicio distribuido frágil; y describir el efecto acumulativo de extraer de las hojas hacia el hub.
Con esto tienes el módulo completo: sabes encontrar el bounded context (L2), ponerle el ACL bidireccional (L3-L4), darle la propiedad de sus datos (L5), cortar la BD compartida (L6), y en qué orden extraer todos los contextos (L7). La lección 8 —el capstone— junta todo en una sola extracción ejecutada de punta a punta: sacar el catalog de Mercado (el primero de tu plan) a su propio servicio, corrido como un diario de extracción por fases, con el monolito respondiendo idéntico en cada una. Sabes cada pieza por separado; ahora las verás trabajar juntas en una migración real.
Recursos
- Sam Newman, Monolith to Microservices (O'Reilly, 2019), cap. 2, "Planning a Migration" — el capítulo sobre cómo priorizar y secuenciar la descomposición: empezar por lo que es fácil de extraer y entrega beneficio, y dejar lo más acoplado para cuando el equipo domina el proceso. La referencia directa de esta lección. En inglés.
- Chris Richardson, "Refactoring a monolith into microservices" — microservices.io/refactoring/. La guía de descomposición incremental: extraer servicios uno a la vez, empezando por los de menor acoplamiento, para reducir el riesgo de cada paso. En inglés.
- Martin Fowler, "MonolithFirst" — martinfowler.com/bliki/MonolithFirst.html. El contexto sobre cuándo y cómo descomponer: descomponer un monolito que ya existe (con sus fronteras aprendidas) por las costuras más limpias, no por las más deseadas. En inglés.
- Chris Richardson, "Pattern: Decompose by subdomain" — microservices.io/patterns/decomposition/decompose-by-subdomain.html. La ficha del patrón de descomponer por subdominio, con la idea de que el orden de descomposición sigue las fronteras naturales del dominio y su acoplamiento. En inglés.