Módulo 5: Extraer un servicio
Proyecto: extraer el catalog de Mercado a un servicio
Descripción
Llegaste al capstone del módulo. En las lecciones anteriores construiste la extracción pieza por pieza: encontrar el bounded context midiendo el seam (L2), el anti-corruption layer que traduce el modelo viejo al limpio (L3), el ACL en ambas direcciones (L4), la propiedad de los datos (L5), el corte de la BD compartida por fases (L6) y el orden de extracción (L7). En este proyecto juntas todo en una sola simulación ejecutada: extraes el catalog de Mercado —el primero de tu plan— de punta a punta, corrido como un diario de extracción por fases.
El diario es la forma más honesta de ver una extracción completa, porque muestra lo que las lecciones aisladas no: cómo las piezas trabajan juntas a lo largo del tiempo, y —sobre todo— cómo el monolito responde idéntico en cada fase mientras sus tripas cambian por completo. Vas a ver el catalog pasar de ser una función interna del monolito (que lee la tabla directo, en el modelo viejo) a ser un servicio con su modelo limpio (Product), su ACL en el borde, y su base de datos propia (owned_db) —y en las cuatro fases, el monolito recibe exactamente lo mismo—. Es la película entera de una extracción, no las fotos sueltas.
Y este proyecto tiene una entrega, como todo capstone: (1) el plan de extracción por bounded contexts —por qué el catálogo primero, y en qué orden lo demás—, (2) el código ejecutado —el diario de extracción completo corrido con su salida literal—, y (3) la justificación de por qué extraer con un ACL y datos propios venció a la alternativa de compartir el modelo y las tablas. Al terminar, tendrás en las manos una extracción de un bounded context real, de principio a fin, que puedes defender.
Conexión con el módulo. Este proyecto integra las lecciones 2 a 7 en una ejecución continua. Cierra el módulo 5 y prepara el que sigue: el módulo 6 (migrar datos sin downtime) toma la copia de datos que aquí resolvemos en una línea (shared_db → owned_db) y la ejecuta de verdad, con dual-write, backfill y parallel-run, para millones de registros y con escrituras llegando durante la mudanza. Fíjate en la frontera del capstone: aquí extraemos el catalog con la técnica de extracción (bounded context, ACL, propiedad, corte de BD). A dónde llega el servicio (microservicio, eventos, API) es de las guías de estilos; migrar sus datos sin downtime a fondo es el módulo 6; y medir el progreso de la migración completa de Mercado (todos los contextos) es el módulo 7. Este capstone es una extracción, ejecutada entera.
El plan de extracción por bounded contexts
Antes del código, el plan —porque una extracción sin plan es solo mover código—. Mercado es un monolito con cuatro bounded contexts: catalog, orders, payments, shipping. No los extraemos todos a la vez; elegimos el más independiente y lo llevamos de punta a punta antes de tocar el siguiente. La lección 7 ya hizo esta elección con números; aquí la ejecutamos.
Contexto Por que en este orden Estado en este proyecto
────────── ───────────────────────────────────────────── ──────────────────────
catalog hoja: 0 salidas, muchas entradas, no escribe <- ESTE contexto,
estado compartido; el cabo suelto del nudo de punta a punta
payments poco acoplado (1 salida), pero maneja dinero: siguiente (riesgo
el riesgo puede diferirlo en la secuencia puede diferirlo)
shipping 2 salidas; se simplifica cuando catalog salga mas adelante
orders el hub (3 salidas): se extrae al final, cuando ultimo
sus dependencias ya son servicios
El principio del plan: de las hojas hacia el hub, una a la vez y de punta a punta. No abras la extracción de orders mientras la de catalog está a medias —terminarías con cuatro extracciones incompletas, ningún servicio duenando sus datos—. Llevas el catalog hasta que duene su owned_db y corte la shared_db, cobras el premio (un bounded context menos de monolito), y entonces empiezas el siguiente. Cada extracción completa reduce el monolito y afloja el nudo para la siguiente.
Una analogía: el hijo mayor se independiza por completo
Vuelve a la familia del módulo, pero ahora sigue a un solo hijo —el mayor, el más independiente— en su viaje completo de la casa a la autosuficiencia. No lo mudas de golpe; lo acompañas por las etapas, y en cada una es un poco más dueño de su vida, mientras la familia (el monolito) sigue funcionando igual.
Al principio vive en casa: su cuarto es parte de la casa, sus gastos van en el recibo familiar, y cuando le hablas, le hablas en el idioma de siempre. Es el catalog como función interna del monolito: dentro del código, sobre la tabla compartida, en el modelo viejo.
Luego se muda y aprende otro idioma, pero pones un traductor: renta su departamento, empieza a hablar su propio idioma limpio, pero para que la familia siga entendiéndose con él contratas un intérprete que traduce en ambos sentidos. La familia le habla como siempre; el intérprete traduce; el hijo responde en su idioma; el intérprete traduce de vuelta. Nadie tuvo que aprender el idioma del otro. Es insertar el ACL bidireccional: el servicio ya habla Product, pero el monolito sigue recibiendo su modelo viejo.
Después abre su propia cuenta bancaria: deja de usar la tarjeta familiar y maneja su propio dinero. Es darle la propiedad de los datos: el servicio deja de leer la tabla del monolito y empieza a duenar su store.
Y al final compra su casa y corta el último lazo: ya no depende de nada de la casa familiar; si coordinan algo, lo hacen por una transferencia explícita. Es el corte de la BD compartida: shared_db → owned_db, la tabla del monolito retirada.
En las cuatro etapas, la familia funcionó igual: nadie más se quedó sin comer ni sin techo porque el hijo mayor se independizara. Ese "la familia funciona igual" es la columna que el diario verifica: resp. monolito igual: SI en cada fase. Este capstone acompaña al hijo mayor —el catalog— en su viaje completo, de vivir en casa a tener su propia casa y sus propias cuentas.
Ejemplo trabajado: el diario de extracción del catalog, ejecutado
Aquí está la extracción completa del catalog, integrando las piezas del módulo, corrida como un diario por fases. La SharedDB está instrumentada (registra quién la toca); el CatalogService cambia su fuente de datos según la fase; el ACL (to_modern/to_legacy) traduce en el borde; y en cada fase el monolito pide el producto 1 y comparamos su respuesta contra la línea base —lo que recibía antes de empezar la extracción—.
from dataclasses import dataclass
@dataclass
class Product:
id: int
name: str
price_cents: int
active: bool
# --- La tabla legacy compartida. Instrumentada: registra accesos del monolito. ---
class SharedDB:
def __init__(self):
self.products = [
{"prod_id": 1, "desc": "SSD 1TB", "prc_cents": "8999", "act": "Y"},
{"prod_id": 2, "desc": "USB-C Hub", "prc_cents": "3200", "act": "Y"},
]
def read(self, prod_id):
return next(r for r in self.products if r["prod_id"] == prod_id)
shared_db = SharedDB()
# --- El ACL: traduce en ambos sentidos en el borde del servicio. ---
def to_modern(row):
return Product(row["prod_id"], row["desc"], int(row["prc_cents"]), row["act"] == "Y")
def to_legacy(p):
return {"prod_id": p.id, "desc": p.name, "prc_cents": str(p.price_cents),
"act": "Y" if p.active else "N"}
# --- El servicio de catalogo. Su comportamiento depende de la fase de extraccion. ---
class CatalogService:
def __init__(self, phase):
self.phase = phase
self.owned_db = {p.id: p for p in (to_modern(r) for r in shared_db.products)}
self.reads_monolith_table = 0 # acoplamiento: veces que toco shared_db
def get(self, prod_id):
if self.phase in (1, 2):
self.reads_monolith_table += 1
return to_modern(shared_db.read(prod_id)) # aun via shared_db
return self.owned_db[prod_id] # fase 3+: BD propia
# --- Fase 0: baseline. El catalog es una funcion INTERNA del monolito (legacy). ---
def legacy_catalog_internal(prod_id):
return shared_db.read(prod_id) # el monolito lee la tabla directo
def monolith_response(prod_id, phase):
if phase == 0:
return legacy_catalog_internal(prod_id) # modelo viejo, tabla directa
svc = CatalogService(phase)
resp = to_legacy(svc.get(prod_id)) # el ACL le devuelve legacy
return resp, svc.reads_monolith_table
BASELINE = legacy_catalog_internal(1) # lo que el monolito veia antes de extraer
phases = [
(0, "monolito interno", "legacy", "monolito", "extrae?"),
(1, "ACL + shared_db", "Product","servicio", "extrae?"),
(2, "ACL + shared_db", "Product","servicio", "extrae?"),
(3, "ACL + owned_db", "Product","servicio", "extrae?"),
]
print("Diario de extraccion del catalog de Mercado (monolito identico en cada fase)\n")
print(f"{'fase':>4} {'que se hizo':<30}{'modelo interno':<15}"
f"{'toca shared_db':>15}{'resp. igual?':>13}")
print("-" * 79)
descs = {
0: "baseline: funcion interna",
1: "bounded context + ACL",
2: "el servicio duena sus datos",
3: "corte: shared_db -> owned_db",
}
for phase, _a, model, _b, _c in phases:
if phase == 0:
resp = monolith_response(1, 0)
touches = "si"
same = (resp == BASELINE)
else:
resp, reads = monolith_response(1, phase)
touches = "si" if reads > 0 else "no"
same = (resp == BASELINE)
print(f"{phase:>4} {descs[phase]:<30}{model:<15}{touches:>15}{('SI' if same else 'NO'):>13}")
print("-" * 79)
print(f"\nRespuesta que el monolito recibio en las 4 fases: {BASELINE}")
# --- Estado final: el servicio esta extraido de verdad. ---
final = CatalogService(3)
p = final.get(1)
final_legacy = to_legacy(p)
print("\nEstado final del catalog extraido:")
print(f" modelo que habla el servicio : {p} (limpio: Product)")
print(f" fuente de datos : owned_db (propia)")
print(f" accesos a la tabla del monolito: {final.reads_monolith_table}")
print(f" lo que el monolito sigue viendo: {final_legacy} (via ACL, sin cambiar)")
print("\n Catalog quedo fuera del monolito: bounded context propio, modelo limpio,")
print(" datos propios, y el ACL en el borde traduciendo. Una rebanada menos de monolito.")
Qué esperar. Al correr el archivo, la salida es exactamente esta:
Diario de extraccion del catalog de Mercado (monolito identico en cada fase)
fase que se hizo modelo interno toca shared_db resp. igual?
-------------------------------------------------------------------------------
0 baseline: funcion interna legacy si SI
1 bounded context + ACL Product si SI
2 el servicio duena sus datos Product si SI
3 corte: shared_db -> owned_db Product no SI
-------------------------------------------------------------------------------
Respuesta que el monolito recibio en las 4 fases: {'prod_id': 1, 'desc': 'SSD 1TB', 'prc_cents': '8999', 'act': 'Y'}
Estado final del catalog extraido:
modelo que habla el servicio : Product(id=1, name='SSD 1TB', price_cents=8999, active=True) (limpio: Product)
fuente de datos : owned_db (propia)
accesos a la tabla del monolito: 0
lo que el monolito sigue viendo: {'prod_id': 1, 'desc': 'SSD 1TB', 'prc_cents': '8999', 'act': 'Y'} (via ACL, sin cambiar)
Catalog quedo fuera del monolito: bounded context propio, modelo limpio,
datos propios, y el ACL en el borde traduciendo. Una rebanada menos de monolito.
Lee el diario fase por fase, porque cada renglón es un paso de la extracción y cada columna una de las piezas del módulo trabajando junta.
Fase 0 — El punto de partida: catalog es una función interna del monolito. El monolito lee la tabla directo, en el modelo viejo (modelo interno: legacy), y toca la shared_db (si). No hay servicio, no hay ACL. Es el estado antes de tocar nada, la línea base contra la que se compara todo lo demás. resp. igual: SI es trivialmente cierto aquí (es la propia línea base).
Fase 1 — Se aplica lo de las lecciones 2 a 4: catalog se reconoce como bounded context y se le pone el ACL. El modelo interno del servicio pasa a Product —del ACL hacia adentro, el servicio habla limpio—. Pero fíjate: toca shared_db: si. El servicio ya existe con su modelo limpio, pero sus datos todavía están en la tabla compartida (fase transitoria de la lección 6). Y lo crucial: resp. igual: SI. El monolito recibe exactamente lo mismo que en la fase 0, aunque por dentro ahora hay un servicio con otro modelo y un traductor de por medio. El ACL de vuelta mantuvo el contrato.
Fase 2 — La lección 5: el servicio empieza a duenar sus datos, conceptualmente —el modelo interno es Product, el servicio es el que responde—, pero en el diario todavía toca shared_db: si, porque la copia a la BD propia aún no ocurre. Es la etapa en que el servicio ya es el dueño lógico del catálogo (nadie más debería escribir sus productos), aunque físicamente sus datos sigan en la tabla compartida. Otra vez, resp. igual: SI: el monolito no nota nada.
Fase 3 — El corte final de la lección 6: shared_db → owned_db. El servicio ahora lee de su base de datos propia (toca shared_db: no), la copia de datos se hizo, y la tabla compartida se retira. Y —el sello— resp. igual: SI. Después de mover el modelo, la propiedad y los datos, el monolito sigue recibiendo idéntico lo que recibía en la fase 0.
La línea de abajo lo confirma en una sola verificación: Respuesta que el monolito recibio en las 4 fases: {'prod_id': 1, 'desc': 'SSD 1TB', 'prc_cents': '8999', 'act': 'Y'}. Una sola respuesta, para las cuatro fases. Por dentro, catalog viajó de función interna a servicio autosuficiente; por fuera, el monolito no pudo notar la diferencia.
Y el estado final cierra la historia: el servicio habla Product(id=1, name='SSD 1TB', price_cents=8999, active=True) (modelo limpio), su fuente de datos es owned_db (propia), tocó la tabla del monolito 0 veces (extraído de verdad, según el detector de la lección 5), y el monolito sigue viendo {'prod_id': 1, 'desc': 'SSD 1TB', ...} (su modelo viejo, vía ACL, sin cambiar). catalog quedó fuera del monolito: bounded context propio, modelo limpio, datos propios, ACL en el borde. Una rebanada menos de monolito.
La entrega del capstone
Un capstone entrega artefactos, no solo comprensión. Aquí están los tres:
1. El plan de extracción por bounded contexts. El monolito de Mercado se extrae contexto por contexto, empezando por catalog (la hoja: 0 salidas, muchas entradas, no escribe estado compartido) y siguiendo de las hojas hacia el hub —payments, shipping, orders—, con el riesgo del dinero de payments como factor que puede diferirlo en la secuencia. Cada contexto recorre las cuatro fases de la extracción hasta duenar su owned_db antes de empezar el siguiente. (La tabla del plan está arriba.)
2. El código ejecutado. El diario de extracción completo —bounded context + ACL bidireccional + propiedad de los datos + corte de la BD compartida— corrido con su salida literal, las cuatro fases. No es pseudocódigo ni una descripción: es la extracción simulada, reproducible, que puedes correr y modificar (cambia el modelo, agrega un campo, rompe el ACL de vuelta a propósito y observa cómo resp. igual cae a NO).
3. La justificación: por qué extraer con ACL venció a compartir el modelo y las tablas. La alternativa tentadora era hacer que el "servicio" hablara el modelo viejo y leyera las tablas del monolito —parecía más rápido—. Este proyecto muestra por qué la extracción de verdad gana: el servicio quedó con un modelo limpio (su lógica se lee sola, price_cents == 0, no int(row["prc_cents"]) == 0), con datos propios (puede evolucionar su esquema sin romper a nadie, 0 accesos a la tabla del viejo), y con el monolito intacto (respondió idéntico en las cuatro fases, sin reescribir a sus cientos de consumidores). Donde compartir el modelo y las tablas habría dado un "servicio" que nace con la deuda del monolito y encadenado a su BD, la extracción con ACL dio una pieza autosuficiente que puede vivir su propia vida. Esa es la justificación, y ahora la puedes respaldar con un diario ejecutado.
Errores comunes
Declarar la extracción terminada en la fase 1 (hay servicio) sin llegar a la fase 3 (datos propios). Qué pasa: al ver el servicio funcionando con su ACL y su modelo limpio (fase 1), el equipo da la extracción por hecha —pero el servicio sigue leyendo la tabla compartida—. Por qué pasa: la fase 1 se siente como el logro (¡ya hay servicio!), y el corte de datos (fases 2-3) es trabajo sin recompensa visible. Cómo detectarlo: el servicio lleva meses en producción pero todavía toca shared_db: si; nunca se creó su owned_db. Cómo corregirlo: la extracción —como el strangler— termina cuando el servicio duena sus datos (fase 3), no cuando existe (fase 1). Un servicio con ACL pero sin datos propios es el hijo con departamento propio pero con la tarjeta familiar: parece independiente, sigue encadenado. La rebanada está extraída cuando corta la shared_db, no cuando aparece el servicio.
Extraer varios contextos a la vez en vez de terminar uno. Qué pasa: entusiasmado, el equipo abre la extracción de catalog, orders y payments al mismo tiempo. Por qué pasa: parece más rápido avanzar en todo a la vez, y la fase 1 de cada uno se siente barata. Cómo detectarlo: hay tres o cuatro extracciones "en progreso", ninguna cerca de duenar sus datos; el monolito no encogió porque ningún contexto cortó su shared_db. Cómo corregirlo: un contexto a la vez, de punta a punta, de las hojas hacia el hub. Termina el catalog —hasta que duene su owned_db— antes de tocar el siguiente. El progreso de una extracción no se mide en "cuántos contextos empezaste" sino en "cuántos duenan sus datos". Cuatro extracciones en fase 1 valen cero contextos extraídos; una en fase 3 vale uno. Y terminar uno afloja el nudo para el siguiente (el efecto acumulativo de la lección 7).
No verificar resp. igual en cada fase. Qué pasa: el equipo avanza las fases (pone el ACL, mueve los datos) sin comparar, en cada paso, la respuesta que el monolito recibe contra la línea base. Por qué pasa: "el ACL traduce, seguro está bien". Cómo detectarlo: un bug sutil llega a producción —un campo que se perdió al mover los datos, un formato que cambió— porque nadie comparó. Cómo corregirlo: cada fase se verifica con la columna resp. igual del diario: comparar la respuesta del monolito contra la línea base y confirmar que es SI. Es la prueba de transparencia del facade (módulo 3) y el round-trip del ACL (lección 1), aplicada a la extracción completa. El ACL debería mantener el contrato en las cuatro fases, pero "debería" no es "se verificó". La columna resp. igual es lo que convierte la promesa "el monolito no se entera" en un hecho medido, fase por fase.
Ejercicios
Ejercicio 1 — Explica la fase 3. En el diario, la fase 3 es la única donde toca shared_db cae a no. (a) ¿Qué se hizo en esa fase que no se había hecho antes? (b) ¿Por qué resp. igual siguió en SI a pesar del cambio? (c) ¿Por qué esta fase es la que "cobra" la extracción?
Ver solución
(a) En la fase 3 se hizo el corte de la BD compartida: los datos se copiaron a la owned_db del servicio, el servicio empezó a leer de ahí en vez de la shared_db, y la tabla compartida se retiró. Es el paso donde el servicio deja de depender de la tabla del monolito y pasa a duenar sus datos físicamente —lo que las fases 1 y 2 aún no habían hecho, porque ahí el servicio existía con su ACL pero seguía leyendo la tabla compartida (toca shared_db: si)—.
(b) Porque el ACL en el borde mantuvo el contrato del monolito idéntico. La fuente de datos cambió por dentro (de shared_db a owned_db), pero el ACL de vuelta (to_legacy) siguió entregándole al monolito su modelo viejo exacto —{'prod_id': 1, 'desc': 'SSD 1TB', 'prc_cents': '8999', 'act': 'Y'}—. El monolito pide, el ACL traduce, y el monolito recibe lo de siempre, sin importar de qué store salieron los datos. Ese es el trabajo del ACL: absorber el cambio de topología por debajo y mantener el contrato por arriba.
(c) Porque es la fase donde el servicio duena sus datos de verdad —el segundo corte de la extracción, el de datos, que es el que hace al servicio autosuficiente—. Hasta la fase 2, el servicio tenía modelo limpio y ACL, pero seguía encadenado a la tabla del monolito: si el monolito cambiaba esa tabla, el servicio se rompía. En la fase 3, con owned_db y 0 accesos a la tabla vieja, el servicio puede evolucionar su esquema sin romper a nadie y el monolito encogió (una tabla menos que compartir). Sin la fase 3, la extracción está a medias; con ella, se cobró el premio: un bounded context menos de monolito.
Ejercicio 2 — Rompe el ACL y predice. Sin correr el código, predice qué pasaría en el diario si el ACL de vuelta (to_legacy) tuviera un bug: devolviera prc_cents como int (8999) en vez de string ('8999'). (a) ¿Qué columna del diario cambiaría? (b) ¿En qué fases? (c) ¿Qué te dice esto sobre para qué sirve la columna resp. igual?
Ver solución
(a) Cambiaría la columna resp. igual?: caería de SI a NO. La línea base (fase 0) tiene prc_cents como string '8999' (el modelo viejo real del monolito), pero el ACL con bug devolvería 8999 como int. La comparación resp == BASELINE fallaría, porque {'prc_cents': '8999'} no es igual a {'prc_cents': 8999} —tipos distintos—.
(b) En las fases 1, 2 y 3 —todas las que pasan por el ACL—. La fase 0 no usa el ACL (es la función interna directa del monolito, la línea base), así que ahí resp. igual seguiría en SI (se compara contra sí misma). Pero en cuanto el ACL entra en juego (fase 1 en adelante), el bug de to_legacy haría que la respuesta difiera de la línea base, y resp. igual daría NO en las tres fases restantes.
(c) Que la columna resp. igual es la red de seguridad de la extracción: atrapa cualquier momento en que el monolito deje de recibir exactamente lo que recibía antes. Un bug en el ACL de vuelta —tan sutil como un tipo cambiado— rompería a los consumidores del monolito en producción, pero el diario lo atrapa antes, comparando contra la línea base en cada fase. Sin esa columna, el bug se colaría silenciosamente (el servicio "funciona", devuelve un Product correcto) hasta que un consumidor del monolito que esperaba prc_cents como string se rompiera. resp. igual convierte la promesa "el monolito no se entera" en una verificación ejecutada, y por eso se revisa en cada fase, no solo al final.
Ejercicio 3 — El plan de la siguiente extracción. El catalog quedó extraído (fase 3, datos propios). Ahora toca el siguiente contexto. (a) Según el plan, ¿cuál sigue y por qué? (b) ¿Qué de esta extracción del catalog reutilizarías, y qué esperarías que sea distinto? (c) ¿Cómo aflojó el nudo haber extraído catalog primero?
Ver solución
(a) Según el plan (lección 7), el siguiente por acoplamiento es payments (1 salida, score 3), aunque su manejo de dinero podría diferirlo en la secuencia por riesgo —un equipo prudente podría ir por shipping u otro contexto de bajo riesgo antes de tocar el dinero—. El esqueleto no cambia: la hoja (catalog) ya salió, el hub (orders) va al final; el medio se afina por valor y riesgo, y eso se registra en el ADR.
(b) Reutilizarías toda la técnica de extracción: encontrar el bounded context midiendo el seam, el patrón del ACL bidireccional, el detector de propiedad de datos, y el corte por fases de la BD compartida con verificación de resp. igual. La técnica es la misma para cualquier contexto. Esperarías que sea distinto: payments escribe estado compartido (write_heavy), a diferencia del catalog que casi solo se lee —así que la propiedad de sus datos y el corte de la BD son más delicados (hay escrituras que coordinar, no solo lecturas)—; su ACL traducirá un modelo distinto; y su ramp será más cuidadoso por el riesgo del dinero. La coreografía es igual; el contenido y el cuidado cambian.
(c) Extraer catalog primero aflojó el nudo para los contextos que dependían de él —shipping y orders, que lo leían—. Antes, esa dependencia apuntaba hacia adentro del monolito; ahora apunta hacia un servicio con API limpia, más fácil de manejar. Cuando llegue el turno de extraer shipping, una de sus dos dependencias ya es un servicio bien definido, así que su seam está más simple que al inicio. Ese es el efecto acumulativo de la lección 7: cada hoja extraída simplifica el seam de los que quedan, hasta que el hub —imposible al inicio— se vuelve alcanzable al final. Empezar por la hoja no solo fue lo fácil; fue lo que hizo más fácil todo lo que sigue.
Resumen y siguiente paso
En este capstone integraste todo el módulo en una sola extracción ejecutada: sacaste el catalog de Mercado —el primero de tu plan— de función interna del monolito a servicio autosuficiente, corrido como un diario de cuatro fases. Viste el sistema completo trabajar junto: el bounded context reconocido, el ACL bidireccional puesto, la propiedad de los datos tomada, y la BD compartida cortada (shared_db → owned_db), con el monolito respondiendo idéntico en las cuatro fases —una sola respuesta para todas—. Y produjiste la entrega del capstone: el plan de extracción por bounded contexts, el código ejecutado, y la justificación —respaldada por el diario— de por qué extraer con un ACL y datos propios venció a compartir el modelo y las tablas.
Con esto cierras el módulo 5. Dominas la técnica de extracción: sabes encontrar el bounded context midiendo el seam, ponerle un ACL que traduce viejo↔nuevo en ambas direcciones, darle la propiedad de sus datos, cortar la BD compartida por fases sin apagar el sistema, y elegir el orden de extracción de las hojas hacia el hub.
Lo que sigue completa el corte de datos que aquí dejamos apuntado. En este capstone, la copia de shared_db a owned_db fue una línea de código —porque el foco era la propiedad, no la mudanza—. El módulo 6 (migrar datos sin downtime) ejecuta esa mudanza de verdad: cómo mover millones de registros del store viejo al nuevo mientras el sistema sigue leyendo y escribiendo, con expand-contract (agregar lo nuevo, migrar, quitar lo viejo), dual-write (escribir en ambos durante la transición), backfill (llenar lo histórico) y parallel-run (leer de ambos y comparar antes de confiar). La extracción te dio la pieza con sus datos propios; el módulo 6 te da cómo llenar esos datos propios sin que el negocio se detenga.
Recursos
- Sam Newman, Monolith to Microservices (O'Reilly, 2019), caps. 3 y 4 completos — la referencia integral del capstone: extraer un servicio (elegir el contexto, el ACL, mantener el contrato) y descomponer su base de datos (la propiedad de los datos, cortar la BD compartida por fases). El mapa de esta extracción y de las que siguen. En inglés.
- Eric Evans, Domain-Driven Design (Addison-Wesley, 2003), capítulos sobre Bounded Context y Anti-Corruption Layer — los dos conceptos que el capstone ejecuta de principio a fin. Vuelve a leerlos ahora que tienes la mecánica completa: cada frase tendrá un referente concreto. En inglés.
- Chris Richardson, "Pattern: Database per service" — microservices.io/patterns/data/database-per-service.html. El destino de la extracción: cada servicio con su propia base de datos, y el monolito una rebanada más pequeño con cada contexto que sale. En inglés.
- Martin Fowler, "StranglerFigApplication" (2004) — martinfowler.com/bliki/StranglerFigApplication.html. El patrón hermano que desvía el tráfico al servicio extraído; la extracción de este módulo y el strangler del módulo 3 se combinan en una migración real, incremental y reversible. En inglés.