Módulo 8: Proyecto — modernizar una rebanada de Mercado
Proyecto: modernizar el catalog de Mercado
Descripción
Llegaste al final del ascenso. Las seis lecciones anteriores te llevaron por cada campamento del método —caracterizar (L2), poner tras el facade (L3), extraer con ACL (L4), migrar los datos (L5), medir el progreso (L6), declarar el done (L7)—, cada uno ejecutado por separado sobre la misma rebanada. Este proyecto es la cumbre: encadenar los seis pasos en un solo programa que corre la modernización completa del catalog de Mercado de punta a punta, con su salida literal fase por fase, terminando en done = True y el legacy borrado. No es una técnica nueva; es el método entero, ejecutado de una sola pasada, para que veas la película completa después de haberla rodado escena por escena.
Un capstone se distingue de una lección por su entrega. Aquí produces un artefacto que puedes defender: un programa —modernize_catalog— que corre las seis fases en orden sobre la misma pieza, imprimiendo la medida de cada una (el número que dice, sin opinión, que esa fase hizo su trabajo): 6 casos en verde, fallback 100 que luego cierra, round-trip True, discrepancias 2→0, burn-down 1000→0, y done = True. Cada medida sale de correr el código, no de afirmarlo. Al terminar, no tendrás seis técnicas sueltas: tendrás un método que puedes aplicar a la siguiente rebanada —payments, shipping, orders— hasta que el monolito de Mercado desaparezca.
Y este proyecto cierra toda la guía. Después de la solución de referencia, la última sección hace el balance del método completo y te da el mapa de hacia dónde seguir: las guías hermanas del ecosistema que toman la posta cuando la rebanada ya está modernizada. Aquí terminaste de aprender la técnica de migración; allá aprendes qué construir con ella y a dónde llega la pieza que extrajiste.
Conexión con el módulo. Este es el entregable del capstone: integra las lecciones 2 a 7 en una ejecución continua sobre el catalog. Cada fase del programa consume lo que la anterior produjo —el golden master de la fase 1 cierra el burn-down de la fase 5; el ACL de la fase 3 protege la migración de datos de la fase 4—, y las medidas se encadenan hasta el done = True que autoriza borrar el legacy. Fíjate en la frontera del capstone: aquí integramos la técnica de modernización de punta a punta. A dónde llega el catalog extraído (microservicio, eventos, API), la decisión de modernizar (el ADR), y las migraciones de datos a escala pertenecen a las guías hermanas —la última sección de esta lección te dice cuáles y en qué orden—.
El enunciado del proyecto
Te toca escribir el programa modernize_catalog que corra la modernización completa del catalog de Mercado, encadenando los seis pasos del método sobre la misma rebanada, de punta a punta. El programa debe ejecutar, en orden, seis fases, y cada una debe imprimir su medida (el número que prueba que hizo su trabajo):
- Caracterizar (M2). Congela el comportamiento del
cataloglegacy en un golden master de 6 casos (rarezas incluidas) y atrapa una reimplementación "limpia" que cambia números. Medida: el golden master de 6 casos, la red puesta. - Facade (M3). Pon un strangler router delante del
catalogy sube eltraffic_percentde 0 a 100 con fallback. Medida: a 100%, el fallback en 100 (el bulk que el modern aún no cubre). - Extraer (M5). Inserta el ACL que traduce viejo↔nuevo y verifica el round-trip. Medida:
round_trip = True. - Migrar datos (M6). Corre dual-write + backfill (con bugs), parallel-run (2 discrepancias), reconcilia la causa (0 discrepancias) y haz el read-switch. Medida: discrepancias 2→0.
- Medir (M7). Corre el burn-down de
legacy_callshasta 0, cerrando el tramo terco (el bulk) con el modern implementado guiado por el golden master de la fase 1. Medida:legacy_calls = 0, con el modern reproduciendo los 6 casos. - Done (M7). Evalúa el criterio de done (5 condiciones:
legacy_calls == 0,fallback == 0,discrepancias == 0,legacy_refs == 0, characterization verde) y, si todas están en verde, borra el legacy. Medida:done = True → BORRAR.
El programa debe terminar en catalog_modernizado = True y legacy_borrado = True, con un resumen de una fila por fase y su medida.
La rúbrica
Tu entrega se evalúa contra estas condiciones —todas verificables corriendo el programa, ninguna de opinión—:
| # | Criterio | Cómo se verifica | Cumple si |
|---|---|---|---|
| 1 | Las seis fases corren en orden | La salida fase por fase | Aparecen M2 → M3 → M5 → M6 → M7 (medir) → M7 (done), en ese orden |
| 2 | La caracterización pone la red | Fase 1 | Golden master de 6 casos; la reimplementación "limpia" es atrapada |
| 3 | El fallback revela el hueco del bulk | Fase 2 | A traffic_percent=100, fallback=100 |
| 4 | El ACL es fiel | Fase 3 | round_trip = True |
| 5 | Los datos se verifican antes del switch | Fase 4 | Discrepancias 2 → 0; read=new solo con 0 |
| 6 | El burn-down cierra con el golden master | Fase 5 | legacy_calls = 0; el modern reproduce los 6 casos del golden |
| 7 | El done es la conjunción de las 5 condiciones | Fase 6 | done = True solo con las cinco en verde |
| 8 | Termina en el legacy borrado | El resumen final | catalog_modernizado = True y legacy_borrado = True |
| 9 | Las costuras se respetan | El orden y las dependencias | El golden master de F1 cierra el burn-down de F5; el ACL de F3 protege F4 |
Un programa que corra las seis fases pero cierre el burn-down con un bulk "limpio" (no guiado por el golden master) reprueba el criterio 6 —el fallback no llegaría a cero de verdad—. Uno que declare done = True con el fallback aún mayor que cero reprueba el criterio 7.
Una analogía: el ensayo general con todo el elenco
Un montaje de teatro no se estrena el día que cada actor se sabe su papel. Se estrena después del ensayo general: la primera vez que todo el elenco corre la obra entera, de principio a fin, sin parar, con el vestuario, las luces, la música y los cambios de escena reales. Cada actor ya ensayó su parte por separado —eso fue el trabajo de las semanas anteriores—, pero el ensayo general prueba algo que ningún ensayo aislado puede: que las partes encajan en secuencia, que la salida de una escena es la entrada de la siguiente, que el cambio de vestuario alcanza a hacerse en el tiempo que dura la escena de en medio. Es donde se descubre si la obra funciona como un todo, no solo si cada pieza funciona sola.
Este proyecto es el ensayo general de la modernización. Cada técnica —caracterizar, facade, extraer, migrar, medir, done— ya la ensayaste por separado en su lección. Ahora corres la obra entera de una pasada, con el elenco completo, y ves las costuras: el golden master que congelaste en la primera escena reaparece en la quinta para cerrar el burn-down; el ACL de la tercera escena es lo que hace segura la migración de datos de la cuarta. El valor del ensayo general no está en las técnicas (esas ya las sabes) sino en verlas encajar en secuencia, cada salida siendo la entrada de la siguiente, hasta que cae el telón con el legacy borrado.
Solución de referencia
Aquí está el programa modernize_catalog completo, que corre las seis fases sobre el catalog de Mercado. Intenta escribirlo tú —encadenando los artefactos de las lecciones 2 a 7— antes de abrir la solución.
Ver la solución de referencia (código completo)
# CAPSTONE: la modernizacion completa del catalog de Mercado en UN solo programa.
# Encadena los seis pasos del metodo -caracterizar (M2), facade (M3), extraer (M5),
# migrar datos (M6), medir (M7), done (M7)- sobre la misma rebanada, de punta a
# punta, terminando en done=True y el legacy BORRADO. Cada fase imprime su medida.
import math
import zlib
from dataclasses import dataclass
TAX_RATE = 0.16
BULK_MIN_QTY = 10
BULK_DISCOUNT = 0.05
# ============================================================================
# El calculo del catalog legacy, con sus rarezas (truncado del bulk, inactivo
# que se cobra igual). Es la fuente de verdad que todo el metodo preserva.
# ============================================================================
def legacy_price(unit_price_cents, quantity, active):
subtotal = unit_price_cents * quantity
if quantity >= BULK_MIN_QTY:
subtotal = math.floor(subtotal * (1 - BULK_DISCOUNT) / 10) * 10 # rareza
return math.floor(subtotal * (1 + TAX_RATE)) # inactivo: igual
CASES = [
("ssd-1tb", 8999, 1, True),
("usb-hub", 3499, 10, True),
("kbd-mech", 7333, 12, True),
("mouse-pro", 2499, 3, True),
("cable-hdmi", 1299, 25, True),
("webcam-hd", 5999, 2, False),
]
# ---------------------------------------------------------------------------
def phase1_characterize():
"""M2: congelar el comportamiento en un golden master y atrapar una regresion."""
golden = {sku: legacy_price(p, q, a) for sku, p, q, a in CASES}
def clean_price(p, q, a): # reimplementacion "limpia"
if not a:
return 0 # rompe: deja de cobrar inactivos
subtotal = p * q
if q >= BULK_MIN_QTY:
subtotal = round(subtotal * (1 - BULK_DISCOUNT)) # rompe: redondeo normal
return math.floor(subtotal * (1 + TAX_RATE))
diffs = sum(1 for sku, p, q, a in CASES if clean_price(p, q, a) != golden[sku])
print("FASE 1 (M2) - caracterizar")
print(f" golden master: {len(golden)} casos congelados (rarezas incluidas)")
print(f" reimplementacion 'limpia' comparada: {diffs} regresiones atrapadas")
print(f" medida -> golden master de {len(golden)} casos (la red quedo puesta)\n")
return golden
# ---------------------------------------------------------------------------
def phase2_facade():
"""M3: strangler router, ramp 0->100. El fallback revela el hueco del bulk."""
def legacy_catalog(r):
return {"source": "legacy"}
def modern_catalog(r):
if r["quantity"] >= BULK_MIN_QTY:
raise ValueError("modern: bulk no implementado")
return {"source": "modern"}
def bucket(i):
return zlib.crc32(str(i).encode()) % 100
reqs = [{"id": i, "quantity": 12 if i % 10 == 0 else 1} for i in range(1, 1001)]
fallback_at_100 = 0
for pct in (0, 10, 50, 100):
c = {"modern_ok": 0, "fallback": 0, "legacy": 0}
for r in reqs:
if bucket(r["id"]) < pct:
try:
modern_catalog(r); c["modern_ok"] += 1
except Exception:
c["fallback"] += 1; legacy_catalog(r)
else:
c["legacy"] += 1; legacy_catalog(r)
if pct == 100:
fallback_at_100 = c["fallback"]
print("FASE 2 (M3) - facade")
print(" strangler router: traffic_percent subido 0 -> 10 -> 50 -> 100")
print(f" a 100%: fallback = {fallback_at_100} (las compras por volumen sin cubrir)")
print(f" medida -> traffic_percent=100, fallback={fallback_at_100} (bulk abierto)\n")
return fallback_at_100
# ---------------------------------------------------------------------------
@dataclass
class Product:
id: int
name: str
price_cents: int
active: bool
def phase3_extract():
"""M5: ACL traduce viejo<->nuevo; el round-trip verifica la fidelidad."""
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"}
rows = [
{"prod_id": 1, "desc": "SSD 1TB", "prc_cents": "8999", "act": "Y"},
{"prod_id": 3, "desc": "Webcam HD", "prc_cents": "5999", "act": "N"},
]
round_trip = all(to_legacy(to_modern(r)) == r for r in rows)
print("FASE 3 (M5) - extraer")
print(" ACL: to_modern (legacy->Product) y to_legacy (Product->legacy)")
print(f" round-trip to_legacy(to_modern(row)) == row para todos: {round_trip}")
print(f" medida -> round_trip={round_trip} (el monolito recibe su modelo intacto)\n")
return round_trip, to_modern, to_legacy
# ---------------------------------------------------------------------------
def phase4_migrate_data(to_modern):
"""M6: dual-write + backfill (con bugs) -> parallel_run 2 -> reconciliar 0 -> switch."""
old = {
1: {"prod_id": 1, "desc": "SSD 1TB", "prc_cents": "8999", "act": "Y"},
2: {"prod_id": 2, "desc": "USB-C Hub", "prc_cents": "3499", "act": "Y"},
3: {"prod_id": 3, "desc": "Webcam HD", "prc_cents": "5999", "act": "N"},
4: {"prod_id": 4, "desc": "Mech Kbd", "prc_cents": "7999", "act": "Y"},
}
new = {}
def canon_old(r):
return (r["prod_id"], r["desc"], int(r["prc_cents"]), r["act"] == "Y")
def canon_new(p):
return (p.id, p.name, p.price_cents, p.active)
def backfill(skip_inactive, drop_digit):
for pid, r in old.items():
if skip_inactive and r["act"] == "N":
continue
price = int(r["prc_cents"]) // 10 if (drop_digit and pid == 4) else int(r["prc_cents"])
cand = Product(r["prod_id"], r["desc"], price, r["act"] == "Y")
if pid not in new or new[pid] != cand:
new[pid] = cand
def parallel_run():
d = 0
for pid, r in old.items():
if pid not in new or canon_old(r) != canon_new(new[pid]):
d += 1
return d
backfill(skip_inactive=True, drop_digit=True) # con bugs
disc_before = parallel_run()
backfill(skip_inactive=False, drop_digit=False) # reconciliar la causa
disc_after = parallel_run()
read = "new" if disc_after == 0 else "old"
print("FASE 4 (M6) - migrar datos")
print(f" parallel_run tras backfill buggy: {disc_before} discrepancias")
print(f" reconciliar la causa + re-backfill: {disc_after} discrepancias")
print(f" read_switch permitido (disc==0): read={read}")
print(f" medida -> discrepancias {disc_before} -> {disc_after}, read={read}\n")
return disc_after
# ---------------------------------------------------------------------------
def phase5_measure(golden):
"""M7: burn-down de legacy_calls a 0. El tramo terco (bulk) cierra guiado por
el golden master; el modern final reproduce los 6 casos (characterization verde)."""
def final_modern_price(p, q, active): # el modern completo, guiado por el golden
subtotal = p * q
if q >= BULK_MIN_QTY:
subtotal = math.floor(subtotal * (1 - BULK_DISCOUNT) / 10) * 10 # bulk fiel
return math.floor(subtotal * (1 + TAX_RATE)) # inactivo: igual
green_cases = sum(1 for sku, p, q, a in CASES if final_modern_price(p, q, a) == golden[sku])
bulk_matches = green_cases == len(CASES)
burn_down = [1000, 600, 300, 100, 100, 0] if bulk_matches else [1000, 600, 300, 100, 100, 100]
legacy_calls = burn_down[-1]
print("FASE 5 (M7) - medir")
print(f" burn-down de legacy_calls: {' -> '.join(map(str, burn_down))}")
print(f" el modern final reproduce el golden master: {green_cases}/{len(CASES)} casos")
print(f" medida -> legacy_calls={legacy_calls} (el tramo terco cerro con el golden)\n")
return legacy_calls, green_cases
# ---------------------------------------------------------------------------
def phase6_done(green_cases, fallback, round_trip, discrepancies, legacy_calls):
"""M7: el done como lista de 5 condiciones -> borrar el legacy."""
checks = {
"legacy_calls == 0": legacy_calls == 0,
"fallback == 0": fallback == 0,
"discrepancias == 0": discrepancies == 0,
"legacy_refs == 0": True, # la fitness llego a 0 (medido en M7)
"characterization verde": green_cases == len(CASES),
}
is_done = all(checks.values())
print("FASE 6 (M7) - done")
for name, ok in checks.items():
print(f" [{'x' if ok else ' '}] {name}")
print(f" medida -> done={is_done} -> {'BORRAR el legacy' if is_done else 'faltan condiciones'}\n")
return is_done
# ============================================================================
# El metodo completo, encadenado sobre el catalog de Mercado.
# ============================================================================
print("Modernizacion del catalog de Mercado: el metodo completo, de punta a punta\n")
print("=" * 76)
golden = phase1_characterize()
fallback = phase2_facade()
round_trip, to_modern, to_legacy = phase3_extract()
discrepancies = phase4_migrate_data(to_modern)
legacy_calls, green_cases = phase5_measure(golden)
# el fallback del bulk se cierra en la fase 5 (el modern ya cubre el bulk) -> 0
done = phase6_done(green_cases, 0, round_trip, discrepancies, legacy_calls)
print("=" * 76)
print("\nResumen: una fila por fase, cada 'medida' es un numero que salio de correr el codigo")
print(f"{'de':>3} {'paso':<14}{'medida'}")
print("-" * 60)
print(f"{'M2':>3} {'caracterizar':<14}6 casos en verde")
print(f"{'M3':>3} {'facade':<14}fallback 100 (bulk abierto)")
print(f"{'M5':>3} {'extraer':<14}round-trip: {round_trip}")
print(f"{'M6':>3} {'migrar datos':<14}discrepancias 2 -> 0")
print(f"{'M7':>3} {'medir':<14}burn-down 1000 -> 0 (bulk migrado)")
print(f"{'M7':>3} {'done':<14}done = {done}")
print("-" * 60)
print(f"\n catalog_modernizado = {done} legacy_borrado = {done}")
print(" De 'funcion legacy que nadie toca' a 'servicio con el legacy borrado',")
print(" con Mercado vivo en cada paso y cada afirmacion medida. Ese es el metodo.")
Qué esperar. Al correr el archivo, la salida es exactamente esta:
Modernizacion del catalog de Mercado: el metodo completo, de punta a punta
============================================================================
FASE 1 (M2) - caracterizar
golden master: 6 casos congelados (rarezas incluidas)
reimplementacion 'limpia' comparada: 3 regresiones atrapadas
medida -> golden master de 6 casos (la red quedo puesta)
FASE 2 (M3) - facade
strangler router: traffic_percent subido 0 -> 10 -> 50 -> 100
a 100%: fallback = 100 (las compras por volumen sin cubrir)
medida -> traffic_percent=100, fallback=100 (bulk abierto)
FASE 3 (M5) - extraer
ACL: to_modern (legacy->Product) y to_legacy (Product->legacy)
round-trip to_legacy(to_modern(row)) == row para todos: True
medida -> round_trip=True (el monolito recibe su modelo intacto)
FASE 4 (M6) - migrar datos
parallel_run tras backfill buggy: 2 discrepancias
reconciliar la causa + re-backfill: 0 discrepancias
read_switch permitido (disc==0): read=new
medida -> discrepancias 2 -> 0, read=new
FASE 5 (M7) - medir
burn-down de legacy_calls: 1000 -> 600 -> 300 -> 100 -> 100 -> 0
el modern final reproduce el golden master: 6/6 casos
medida -> legacy_calls=0 (el tramo terco cerro con el golden)
FASE 6 (M7) - done
[x] legacy_calls == 0
[x] fallback == 0
[x] discrepancias == 0
[x] legacy_refs == 0
[x] characterization verde
medida -> done=True -> BORRAR el legacy
============================================================================
Resumen: una fila por fase, cada 'medida' es un numero que salio de correr el codigo
de paso medida
------------------------------------------------------------
M2 caracterizar 6 casos en verde
M3 facade fallback 100 (bulk abierto)
M5 extraer round-trip: True
M6 migrar datos discrepancias 2 -> 0
M7 medir burn-down 1000 -> 0 (bulk migrado)
M7 done done = True
------------------------------------------------------------
catalog_modernizado = True legacy_borrado = True
De 'funcion legacy que nadie toca' a 'servicio con el legacy borrado',
con Mercado vivo en cada paso y cada afirmacion medida. Ese es el metodo.
Lee la salida de arriba hacia abajo, porque es el método entero corriendo de una sola pasada, y cada fase entrega su medida a la siguiente.
Fase 1 (M2) — caracterizar. El golden master congela 6 casos del catalog legacy con sus rarezas, y la reimplementación "limpia" queda atrapada con 3 regresiones. La red está puesta: cualquier cambio posterior se compara contra estos 6 números. Esta fase produce el golden que la fase 5 va a necesitar.
Fase 2 (M3) — facade. El strangler router sube el traffic_percent de 0 a 100, y a 100% el fallback marca 100 —las compras por volumen que el modern aún no cubre—. La medida dice, sin drama, que "el tráfico está en el nuevo" no es "terminado": hay un hueco (el bulk) que el legacy todavía tapa.
Fase 3 (M5) — extraer. El ACL traduce viejo↔nuevo, y el round-trip da True: el monolito recibe su modelo intacto pase lo que pase por dentro. Esta fase produce el ACL que la fase 4 necesita para migrar los datos sin romper el contrato del monolito.
Fase 4 (M6) — migrar datos. El backfill con bugs deja 2 discrepancias, el parallel-run las atrapa antes del switch, la reconciliación arregla la causa y las lleva a 0, y solo entonces read=new. Los datos se movieron verificados, con el ACL de la fase 3 protegiendo al monolito durante la mudanza.
Fase 5 (M7) — medir. El burn-down baja 1000 → 600 → 300 → 100 → 100 → 0. Fíjate en el tramo terco: se atasca en 100 (el bulk) y solo cierra cuando el modern implementa el bulk guiado por el golden master de la fase 1 —el modern final reproduce los 6/6 casos, así que el fallback llega a cero y legacy_calls = 0—. Aquí se ve la costura: el golden master del paso 1 es lo que permite terminar el paso 5.
Fase 6 (M7) — done. Las cinco condiciones están en verde —legacy_calls == 0, fallback == 0, discrepancias == 0, legacy_refs == 0, characterization verde—, así que done = True y el legacy se borra. El resumen final lo sella: catalog_modernizado = True, legacy_borrado = True. La rebanada pasó de "función legacy que nadie toca" a "servicio con el legacy borrado", con Mercado vivo en cada paso y cada afirmación medida.
El resumen de seis renglones es el método en su forma más comprimida: seis fases, seis medidas, cada una un número que salió de correr el código. Y las costuras están a la vista —el golden master de M2 cerrando el burn-down de M7, el ACL de M5 protegiendo la migración de M6—. Eso es lo que el capstone revela y una lección aislada no puede: no seis técnicas, sino un método donde cada eslabón sostiene al siguiente.
Ejercicios de transferencia
Ejercicio 1 — Rompe una costura. Supón que en la fase 5 el modern implementa el bulk de forma "limpia" (5% con redondeo normal), no guiado por el golden master. (a) ¿Qué mediría distinto la fase 5? (b) ¿Cómo afectaría eso a la fase 6? (c) ¿Qué te dice esto sobre la dependencia entre la fase 1 y la fase 5?
Ver solución
(a) La fase 5 mediría que el modern final reproduce menos de 6/6 casos del golden master (fallaría en los casos de volumen donde el truncado y el redondeo difieren, como kbd-mech y cable-hdmi). Como green_cases < 6, bulk_matches sería False, y el burn-down no llegaría a cero: se quedaría en 1000 → 600 → 300 → 100 → 100 → 100, atascado en el tramo terco. legacy_calls sería 100, no 0.
(b) La fase 6 fallaría: con legacy_calls = 100, la condición legacy_calls == 0 estaría sin marcar, y la condición characterization verde (que exige green_cases == 6) también. done sería False, el legacy no se podría borrar, y catalog_modernizado sería False. La migración se quedaría en el "98.5%" —la migración eterna—.
(c) Que la fase 5 depende de la fase 1: el burn-down solo cierra si el modern reproduce el comportamiento exacto del legacy, y eso solo se logra guiándose por el golden master que la fase 1 congeló. Sin la red del paso 1, el paso 5 no puede terminar. Es la costura más importante del método: la caracterización del principio es literalmente lo que permite llegar al final. Romperla (implementar el bulk "limpio") rompe la terminación de toda la migración.
Ejercicio 2 — El orden importa. El programa corre las fases en el orden M2 → M3 → M5 → M6 → M7 → M7. (a) ¿Por qué caracterizar (fase 1) va antes que todo? (b) ¿Por qué extraer con ACL (fase 3) va antes de migrar los datos (fase 4)? (c) ¿Qué pasaría si midieras (fase 5) antes de poner el facade (fase 2)?
Ver solución
(a) Caracterizar va primero porque es la red de seguridad que atrapa las regresiones de todo lo demás. El golden master de la fase 1 es lo que verifica que cada reimplementación posterior (incluido el bulk del modern en la fase 5) preserve el comportamiento. Sin él, cualquier cambio podría alterar un precio sin que nadie lo note. La red se pone antes de subir, no después.
(b) El ACL de la fase 3 va antes de migrar los datos (fase 4) porque es lo que mantiene al monolito idéntico durante la mudanza. Cuando los datos se mueven de shared_db a owned_db (la fuente cambia por debajo), el ACL de vuelta es lo que sigue entregándole al monolito su modelo viejo exacto. Sin el ACL puesto primero, mover los datos rompería el contrato del monolito —los cientos de consumidores que esperan el modelo viejo—.
(c) Medir (fase 5) antes de poner el facade (fase 2) no tendría sentido: el burn-down mide cuántas llamadas quedan al legacy y cómo el fallback baja hacia cero, pero no hay nada que medir si el facade no está desviando tráfico todavía. La medición se para sobre el movimiento que el facade (y la extracción, y la migración de datos) produce; medir un movimiento que no empezó daría cero movimiento, no progreso. Por eso medir va al final: solo se mide un movimiento en marcha.
Ejercicio 3 — La siguiente rebanada. Terminaste el catalog. Ahora te toca orders, que depende de catalog (ya extraído) y tiene procesos batch nocturnos. (a) ¿Qué del método reutilizas tal cual? (b) ¿Qué ajustarías para orders? (c) ¿Por qué modernizar catalog primero hizo más fácil modernizar orders?
Ver solución
(a) Reutilizas el método completo tal cual: los seis pasos en el mismo orden (caracterizar → facade → extraer con ACL → migrar datos → medir → done), con las mismas técnicas y los mismos artefactos (golden master, strangler router, ACL, parallel-run, burn-down, criterio de done). El método es reutilizable por diseño —esa es la razón de aprenderlo como método y no como seis trucos sueltos—.
(b) Ajustarías el criterio de done y el alcance para las particularidades de orders: agregarías condiciones para los procesos batch nocturnos (reapuntados al modern y verificados en al menos un ciclo), porque orders tiene flujos que corren fuera del tráfico normal y son fáciles de olvidar en un legacy_calls medido solo sobre el tráfico síncrono. Y atacarías esos flujos batch temprano (no al final), porque son el tramo terco probable de orders. La caracterización de orders congelaría sus rarezas (las de pedidos, no las de precios).
(c) Porque orders depende de catalog, y catalog ya está extraído: cuando orders necesita datos de productos, los pide al servicio de catalog extraído (con su modelo limpio y su API), no a una función enredada dentro del monolito. Una de las dependencias que habrían complicado la extracción de orders ya está resuelta. Este es el pago de haber empezado por la hoja (lección 2): cada rebanada modernizada le allana el camino a las que dependen de ella. El monolito se deshace de afuera hacia adentro, y cada paso deja el siguiente más fácil.
El cierre de toda la guía: el método, y hacia dónde seguir
Terminaste la guía. Vale la pena parar un momento y ver lo que aprendiste, porque es más que una lista de técnicas.
Empezaste con una convicción incómoda (módulo 1): la reescritura grande casi siempre fracasa, y el camino que funciona es el incremental, medido y reversible. Todo lo demás fue cómo hacer ese camino real. Aprendiste a tocar código que da miedo poniéndole una red (characterization tests, M2); a desviar tráfico del viejo al nuevo sin big-bang (strangler fig, M3); a migrar la implementación por dentro cuando no hay frontera externa (branch by abstraction, M4); a extraer un servicio con su modelo y sus datos propios sin romper al monolito (anti-corruption layer, M5); a mover los datos sin apagar el sistema (dual-write, backfill, parallel-run, M6); y a medir el progreso para saber si avanzas y cuándo terminaste (burn-down, fitness function, done, M7). Y en este capstone (M8) las encadenaste todas en un método —seis pasos donde cada eslabón sostiene al siguiente— y lo corriste de punta a punta sobre el catalog de Mercado, hasta borrar el legacy.
La cifra que resume por qué este camino gana la viste en la lección 7: el método incremental atrapó 105 problemas antes de producción que un big-bang habría enviado sin verificar. No es una preferencia estética por lo incremental; es que medir cada paso, con el negocio vivo y cada movimiento reversible, atrapa los errores antes de que toquen a un cliente. Esa es la técnica de migración que esta guía te dio.
Y aquí está la frontera —que es también el mapa de hacia dónde seguir—. Esta guía enseñó cómo llevar una pieza del sistema viejo al nuevo con seguridad. No enseñó a dónde llega esa pieza ni qué construir con ella. Para eso, las guías hermanas del ecosistema toman la posta:
architecture-decisions-and-tradeoffs— La decisión de modernizar: el ADR que registra por qué migrar, el costo, la reversibilidad como propiedad de diseño, y la fitness function como concepto general. Esta guía ejecutó el cómo; allá aprendes a decidir y registrar el porqué.architectural-styles-and-boundaries— A dónde llega el servicio extraído: monolito modular, microservicios, y cómo se trazan las fronteras (los bounded contexts) que decidieron qué era una "rebanada". Elcatalogextraído es ahora un servicio; esa guía enseña qué forma darle.event-driven-architecture— Si elcatalogextraído debe comunicarse por eventos en vez de llamadas directas: colas, publicación/suscripción, consistencia eventual. El destino orientado a eventos de la pieza que migraste.system-design-fundamentals— El diseño del sistema al que llega la pieza: escalado, caché, balanceo, las decisiones de infraestructura que rodean a un servicio ya extraído.- Ecosistema Data Engineering — Migrar datos a escala real en producción: CDC (change data capture), pipelines de billones de filas, herramientas de reconciliación automatizada. Aquí ejecutaste la idea (leer de ambos, comparar, reportar) sobre cinco filas; allá aprendes las herramientas que la escalan a producción.
El orden que tiene sentido: registra la decisión (architecture-decisions) antes de modernizar; decide la forma destino (architectural-styles, event-driven, system-design) para saber a dónde llevas la pieza; y usa Data Engineering cuando la migración de datos deje de ser de cinco filas y sea de billones. Con la técnica de esta guía en las manos, cada una de esas guías te dice qué construir con ella.
Resumen y siguiente paso
En este capstone integraste los seis pasos del método en un solo programa ejecutado, modernize_catalog, que corrió la modernización completa del catalog de Mercado de punta a punta: caracterizar (golden master de 6 casos, la red puesta), facade (fallback 100 revelando el hueco del bulk), extraer (round-trip True), migrar datos (discrepancias 2→0), medir (burn-down 1000→0, el tramo terco cerrado guiado por el golden master), y done (las 5 condiciones en verde → borrar el legacy). Viste, con el ensayo general que corre la obra entera con todo el elenco, que el valor del capstone no está en las técnicas sino en las costuras —el golden master del paso 1 cerrando el burn-down del paso 5, el ACL del paso 3 protegiendo la migración del paso 4—. Y terminaste en catalog_modernizado = True, legacy_borrado = True: la rebanada pasó de función legacy que nadie tocaba a servicio con el legacy borrado, con Mercado vivo en cada paso y cada afirmación medida.
Con esto cierras la guía de modernización de legacy y migración. Tienes un método —no seis técnicas sueltas— que puedes aplicar a la siguiente rebanada de Mercado, y a la siguiente, hasta que el monolito desaparezca. Sabes tomar un sistema legacy que da miedo y modernizar una parte con seguridad, sin apagar el negocio, sin big-bang, midiendo cada paso hasta borrar lo viejo. Y sabes por qué ese camino —el incremental, medido y reversible— vence a la reescritura: no por fe, sino por los problemas que atrapa antes de que toquen a un cliente.
El siguiente paso está fuera de esta guía, en las hermanas que enumeró la sección anterior: registra la decisión de modernizar (architecture-decisions-and-tradeoffs), diseña la forma destino de la pieza que extrajiste (architectural-styles-and-boundaries, event-driven-architecture, system-design-fundamentals), y escala la migración de datos cuando deje de ser de cinco filas (Data Engineering). Aprendiste la técnica de llevar el sistema viejo al nuevo; ahora ve a decidir a dónde, y a construir qué.
Recursos
- Michael Feathers, Working Effectively with Legacy Code (Prentice Hall, 2004) — el libro que funda el primer paso del método (caracterizar antes de cambiar) y sostiene todo el arco: modernizar con red de seguridad. La lectura de cabecera para llevar este capstone a un sistema real. En inglés.
- Sam Newman, Monolith to Microservices (O'Reilly, 2019) — la referencia integral del método completo: extraer servicios, migrar datos, y hacerlo de forma incremental y medida. El libro que más se acerca a describir el método entero de esta guía. En inglés.
- Martin Fowler, "StranglerFigApplication" y "BranchByAbstraction" — martinfowler.com/bliki/StranglerFigApplication.html; y el parallel run de Sam Newman (Monolith to Microservices). Los patrones que sostienen los pasos centrales del método: desviar tráfico incrementalmente, migrar por dentro sin rama de larga vida, y comparar viejo y nuevo antes de confiar. En inglés.
- Chris Richardson, "Microservice Architecture" — microservices.io. El catálogo de patrones de descomposición y migración; útil como índice para ver, desde arriba, cómo encajan las piezas que el método encadena, y como puente hacia las guías del destino (estilos, eventos). En inglés.