Módulo 7: Medir el progreso de la migración
Proyecto: medir y terminar la migración de Mercado
Descripción
Llegaste al capstone del módulo. En las siete lecciones anteriores construiste el tablero de instrumentos de una migración, pieza por pieza: aprendiste a medir el resultado y no el esfuerzo (L2), construiste el burn-down de llamadas al legacy con su velocity y su proyección (L3), escribiste la fitness function que falla si el legacy crece (L4), la convertiste en un trinquete que solo baja (L5), definiste el done como una lista de condiciones que todas deben cumplirse (L6), y viste el desastre que el tablero existe para evitar: la migración eterna (L7). Sabes usar cada instrumento por separado. En este proyecto los juntas en uno solo: un MigrationTracker que integra el burn-down, la fitness function con trinquete y el criterio de done, corrido como un diario de la migración del catalog de Mercado, sprint a sprint, hasta apagar el legacy.
El diario es la forma más honesta de ver el tablero completo trabajar junto, porque muestra lo que las lecciones aisladas no pueden: cómo los tres instrumentos se apoyan entre sí a lo largo del tiempo. Vas a ver el burn-down bajando de 1000 llamadas a 0; la fitness function atrapando, en el sprint 3, a alguien que agregó código nuevo al legacy —poniendo el sprint en HOLD hasta revertir—; el trinquete apretando el baseline sprint a sprint (4→3→2→1→0) para que el progreso no retroceda; y, al llegar a legacy_calls == 0 con todas las condiciones en verde, el criterio de done disparando el borrado del legacy. Es la película entera del módulo, no las fotos sueltas.
Y este proyecto tiene una entrega, como todo capstone: (1) el tablero de progreso —el diario ejecutado con las tres métricas por sprint—, (2) el código ejecutado —la clase MigrationTracker completa con su salida literal—, y (3) la justificación de por qué medir el resultado es lo que hace que una migración termine en vez de arrastrarse para siempre. Al terminar, tendrás en las manos el instrumento que convierte una migración de "trabajamos mucho" a "aquí están los números, y aquí está el legacy borrado".
Conexión con el módulo. Este proyecto integra las lecciones 2 a 7 en una ejecución continua, y cierra el módulo 7. Toma el burn-down (L3), la fitness function con trinquete (L4-L5) y el criterio de done (L6), y los pone a trabajar juntos sobre una misma migración que sí llega a cero —lo contrario de la migración eterna de la L7—. Fíjate en la frontera: aquí medimos una migración cuyo movimiento real —el strangler que desvía el tráfico, la extracción del servicio, la migración de datos— construyeron los módulos 3, 5 y 6. Este tracker no mueve nada; le pone encima el tablero que dice si el movimiento avanza y cuándo terminó. El módulo 8, el capstone de toda la guía, integrará el movimiento y la medición en una sola modernización de punta a punta.
El enunciado del proyecto
Te toca construir el MigrationTracker de la migración del catalog de Mercado y correrlo como un diario que la lleve hasta el cierre. El tracker recibe, sprint a sprint, el estado de la migración —cuántas llamadas quedan al legacy, cuántas referencias al legacy hay en el código, cuántos endpoints y tablas se cortaron— y debe:
- Medir el avance con el burn-down: por cada sprint, el
traffic_percenten la ruta nueva y la velocity (cuántaslegacy_callsse quemaron respecto al sprint anterior). - Proteger el avance con la fitness function: por cada sprint, verificar que las referencias al legacy (
legacy_refs) no superen el baseline, y apretar el trinquete (bajar el baseline al nuevo mínimo) en cada sprint sano. Silegacy_refssupera el baseline, marcar FAIL y poner el sprint en HOLD (no avanza hasta revertir el código que se agregó al legacy). - Declarar el done con el criterio completo:
legacy_calls == 0, todos los endpoints cortados (6 de 6), todas las tablas cortadas (3 de 3), ylegacy_refs == 0. Solo cuando todas se cumplen,done = True, y la acción es borrar el legacy.
El diario que produzcas debe hacer visible, en un renglón por sprint, las tres cosas a la vez: el burn-down bajando, la fitness vigilando, y el done acercándose. Y debe terminar en legacy_borrado = True.
La rúbrica
Tu entrega se evalúa contra estas condiciones —todas verificables corriendo el código, ninguna de opinión—:
| # | Criterio | Cómo se verifica | Cumple si |
|---|---|---|---|
| 1 | El burn-down llega a cero | La columna legacy_calls del diario | Baja de 1000 a 0, con traffic_percent de 0% a 100% |
| 2 | La velocity se calcula bien | La columna vel | Es el delta de legacy_calls vs el sprint anterior; 0 en el sprint del HOLD |
| 3 | La fitness function atrapa el crecimiento | El sprint donde legacy_refs sube | Marca FAIL y HOLD cuando legacy_refs > baseline |
| 4 | El trinquete solo baja | La columna base | Sigue a legacy_refs hacia abajo (4→3→2→1→0) y nunca sube |
| 5 | El done es la conjunción de todas las condiciones | La columna decision | done = True solo con las cuatro casillas en verde |
| 6 | La acción de done es borrar | El renglón final | legacy_borrado = True |
| 7 | Datos fijos y salida reproducible | Correr dos veces | La salida es idéntica en cada corrida |
Un tracker que baje el burn-down pero no atrape el código agregado al legacy reprueba el criterio 3 —mide el avance por el frente pero no protege la retaguardia—. Uno que declare done con el tráfico en cero pero una tabla sin cortar reprueba el criterio 5 —confunde la casilla más visible con la lista completa—.
Una analogía: el tablero de instrumentos de un avión en aproximación
Un piloto que aterriza no mira una sola aguja. Mira un tablero donde tres instrumentos le dicen, a la vez, tres cosas distintas y complementarias: el altímetro (a qué altura está, bajando hacia cero, hacia la pista), la alarma de configuración (si alguien dejó el tren de aterrizaje arriba o los flaps mal, suena y no lo deja continuar), y la checklist de aterrizaje (tren abajo, flaps, velocidad, autorización: todas las casillas, o no se toca tierra). Ninguno de los tres basta solo. El altímetro dice que bajas, pero no que estás configurado para aterrizar. La alarma dice que algo está mal, pero no cuánto falta. La checklist dice qué se necesita, pero no si vas llegando. Juntos, y solo juntos, llevan el avión a la pista con seguridad.
El MigrationTracker es ese tablero, aplicado a una migración. El burn-down es el altímetro: las legacy_calls bajando hacia cero, hacia la pista, con la velocity diciéndote a qué ritmo desciendes. La fitness function es la alarma de configuración: si alguien agrega código al legacy —el equivalente a subir el tren cuando deberías bajarlo—, suena (FAIL) y no te deja continuar (HOLD) hasta corregirlo. Y el criterio de done es la checklist de aterrizaje: legacy_calls == 0, endpoints cortados, tablas cortadas, legacy_refs == 0 —todas las casillas, o no declaras que llegaste—. Un piloto que solo mirara el altímetro podría aterrizar con el tren arriba; un equipo que solo mirara el burn-down podría "terminar" con el legacy todavía prendido. El tablero completo es lo que evita las dos cosas.
Solución de referencia
Aquí está el MigrationTracker completo, integrando los tres instrumentos del módulo, corrido como el diario de la migración del catalog. Los datos de cada sprint están fijos (para que la salida sea reproducible); las métricas —traffic_percent, velocity, fitness, trinquete y done— se derivan de ellos. Intenta construirlo tú antes de abrir la solución.
Ver la solución de referencia (código completo)
# El MigrationTracker integra los tres instrumentos del modulo -burn-down de
# legacy_calls (+traffic_percent), fitness function con trinquete, y criterio de
# done- corrido como un diario de la migracion del catalog de Mercado.
SAMPLE = 1000 # requests por sprint (base del burn-down y el traffic_percent)
ENDPOINTS_TOTAL = 6 # endpoints del catalog que hay que cortar del monolito
TABLES_TOTAL = 3 # tablas del catalog que hay que migrar
class MigrationTracker:
def __init__(self, baseline):
self.baseline = baseline # el trinquete de la fitness function (solo baja)
self.prev_calls = None # para calcular la velocity
def traffic_percent(self, legacy_calls):
return (SAMPLE - legacy_calls) / SAMPLE * 100
def velocity(self, legacy_calls):
return 0 if self.prev_calls is None else self.prev_calls - legacy_calls
def fitness(self, legacy_refs):
# PASS si las referencias al legacy no superaron el baseline (trinquete).
return legacy_refs <= self.baseline
def ratchet(self, legacy_refs):
# el trinquete aprieta: si bajamos, el baseline baja con nosotros y se queda.
if legacy_refs < self.baseline:
self.baseline = legacy_refs
def done(self, s):
checks = {
"legacy_calls == 0": s["legacy_calls"] == 0,
"endpoints cortados": s["endpoints_cut"] == ENDPOINTS_TOTAL,
"tablas cortadas": s["tables_cut"] == TABLES_TOTAL,
"legacy_refs == 0": s["legacy_refs"] == 0,
}
return checks, all(checks.values())
# El diario de la migracion, sprint a sprint (datos fijos, reproducible).
# En el sprint 3 alguien agrega codigo al legacy (refs suben): la fitness lo ATRAPA
# y el burn-down se queda quieto (HOLD) hasta revertir.
sprints = [
# sprint, legacy_calls, legacy_refs, endpoints_cut, tables_cut
(0, 1000, 4, 0, 0),
(1, 700, 3, 2, 0),
(2, 450, 2, 4, 1),
(3, 450, 3, 4, 1), # alguien agrego promo.py al legacy: refs 2 -> 3
(4, 300, 2, 4, 1), # revertido: refs de vuelta a 2, el burn-down retoma
(5, 150, 1, 5, 2),
(6, 40, 1, 6, 2),
(7, 0, 0, 6, 3), # cierre: todo en cero
]
t = MigrationTracker(baseline=4)
print("Diario de la migracion del catalog de Mercado (medida y terminada)\n")
print(f"{'spr':>3}{'calls':>7}{'traf%':>7}{'vel':>6}{'refs':>6}{'base':>6}"
f"{'fit':>6}{'endp':>6}{'tab':>5} decision")
print("-" * 92)
deleted = False
for sprint, legacy_calls, legacy_refs, endpoints_cut, tables_cut in sprints:
state = {"legacy_calls": legacy_calls, "legacy_refs": legacy_refs,
"endpoints_cut": endpoints_cut, "tables_cut": tables_cut}
tp = t.traffic_percent(legacy_calls)
vel = t.velocity(legacy_calls)
fit_ok = t.fitness(legacy_refs)
checks, is_done = t.done(state)
if not fit_ok:
decision = f"fitness FAIL (refs {legacy_refs}>base {t.baseline}): HOLD, revertir"
elif is_done:
decision = "done = True -> BORRAR el legacy"
deleted = True
else:
pend = [n for n, ok in checks.items() if not ok]
decision = f"avanza; faltan {len(pend)} de done"
# el trinquete solo aprieta sobre estados sanos (fitness PASS)
if fit_ok:
t.ratchet(legacy_refs)
t.prev_calls = legacy_calls
fit = "PASS" if fit_ok else "FAIL"
print(f"{sprint:>3}{legacy_calls:>7}{tp:>6.0f}%{vel:>6}{legacy_refs:>6}"
f"{t.baseline:>6}{fit:>6}{endpoints_cut:>4}/6{tables_cut:>3}/3 {decision}")
print("-" * 92)
print(f"\nEntregable: el burn-down llego de 1000 a 0 llamadas al legacy; la fitness")
print(f"function atrapo el codigo nuevo agregado al legacy en el sprint 3 (HOLD hasta")
print(f"revertir); el trinquete apreto el baseline 4->3->2->1->0; y con las cuatro")
print(f"condiciones de done en verde, el legacy se BORRO. legacy_borrado = {deleted}.")
Qué esperar. Al correr el archivo, la salida es exactamente esta:
Diario de la migracion del catalog de Mercado (medida y terminada)
spr calls traf% vel refs base fit endp tab decision
--------------------------------------------------------------------------------------------
0 1000 0% 0 4 4 PASS 0/6 0/3 avanza; faltan 4 de done
1 700 30% 300 3 3 PASS 2/6 0/3 avanza; faltan 4 de done
2 450 55% 250 2 2 PASS 4/6 1/3 avanza; faltan 4 de done
3 450 55% 0 3 2 FAIL 4/6 1/3 fitness FAIL (refs 3>base 2): HOLD, revertir
4 300 70% 150 2 2 PASS 4/6 1/3 avanza; faltan 4 de done
5 150 85% 150 1 1 PASS 5/6 2/3 avanza; faltan 4 de done
6 40 96% 110 1 1 PASS 6/6 2/3 avanza; faltan 3 de done
7 0 100% 40 0 0 PASS 6/6 3/3 done = True -> BORRAR el legacy
--------------------------------------------------------------------------------------------
Entregable: el burn-down llego de 1000 a 0 llamadas al legacy; la fitness
function atrapo el codigo nuevo agregado al legacy en el sprint 3 (HOLD hasta
revertir); el trinquete apreto el baseline 4->3->2->1->0; y con las cuatro
condiciones de done en verde, el legacy se BORRO. legacy_borrado = True.
Lee el diario sprint por sprint, porque cada renglón es el tablero completo en un instante, y las tres métricas cuentan juntas la historia de una migración que termina.
Sprints 0 a 2 — el descenso sano. El burn-down baja 1000 → 700 → 450, con el traffic% subiendo de 0% a 55% y la velocity marcando 300, 250 —la migración avanza a buen ritmo—. En paralelo, la fitness function da PASS en cada sprint (las referencias al legacy bajan 4 → 3 → 2), y el trinquete aprieta el baseline detrás de ellas: 4 → 3 → 2. Cada avance queda fijado. Las columnas endp y tab también suben (0/6 → 4/6 endpoints, 0/3 → 1/3 tablas): los otros frentes de la migración progresan. Nada llamativo, y eso es exactamente lo que quieres ver: los tres instrumentos apuntando hacia abajo, hacia cero.
Sprint 3 — la alarma suena. Aquí está el corazón del proyecto. El burn-down no avanza: legacy_calls se queda en 450 (velocity 0). ¿Por qué? Porque las referencias al legacy subieron de 2 a 3 —alguien agregó catalog/promo.py, una feature nueva construida sobre el monolito que estamos matando—. La fitness function evalúa 3 <= 2 (el baseline que el trinquete había apretado a 2 en el sprint 2): FALSE, FAIL. La decisión es HOLD, revertir: el CI está rojo, el sprint no avanza hasta que se quite esa dependencia del legacy. Fíjate en lo que el tablero atrapó: sin la fitness function, el burn-down de tráfico habría podido seguir bajando por el frente mientras el legacy crecía por atrás, y nadie se habría enterado. La alarma sonó justo a tiempo.
Sprint 4 — la corrección. Se revirtió promo.py (la feature se reconstruye sobre el modern, no sobre el legacy). Las referencias vuelven a 2, la fitness da PASS (2 <= 2), y el burn-down retoma: legacy_calls baja de 450 a 300 (velocity 150). El trinquete no aprieta (2 no es menor que 2, sigue en 2), pero tampoco cedió terreno: el progreso del sprint 2 quedó protegido. La migración vuelve al descenso.
Sprints 5 y 6 — cerrando frentes. El burn-down sigue: 300 → 150 → 40, con el traffic% en 85% y luego 96%. Las referencias bajan a 1, y el trinquete aprieta a 1. Los endpoints llegan a 6/6 (todos cortados) y las tablas a 2/3. Fíjate en la columna decision del sprint 6: "faltan 3 de done" —el tablero cuenta cuántas condiciones de done siguen abiertas—. Aunque los endpoints ya estén todos cortados, faltan legacy_calls == 0, la tercera tabla, y legacy_refs == 0.
Sprint 7 — la pista. El burn-down toca cero: legacy_calls == 0, traffic% = 100. Las referencias llegan a 0 (trinquete a 0), la tercera tabla se corta (3/3), y con los seis endpoints ya cortados, las cuatro condiciones de done están en verde. done = True, y la acción que dispara no es "apagar" sino BORRAR el legacy. El renglón final lo sella: legacy_borrado = True. La migración no se declaró terminada por sensación ni por la casilla más visible; se declaró terminada porque el tablero completo —burn-down en cero, fitness en verde, todas las condiciones de done cumplidas— lo autorizó, y el legacy se retiró de verdad.
El diario cuenta la historia completa de una migración medida en ocho renglones: un burn-down que descendió a cero, una fitness function que atrapó un intento de hacer crecer el legacy y forzó su reversión, un trinquete que fijó cada avance, y un criterio de done que solo disparó el borrado cuando todo estuvo en cero. Cada instrumento hizo su trabajo, y —esto es lo que el módulo entero quería demostrar— juntos hicieron que la migración terminara, en vez de quedarse en el 98.5% para siempre.
La entrega del proyecto
Un capstone entrega artefactos, no solo comprensión. Aquí están los tres:
1. El tablero de progreso. El diario ejecutado del catalog, con las tres métricas por sprint: el burn-down (legacy_calls, traffic%, velocity), la fitness function con trinquete (refs, base, fit), y el avance hacia el done (endp, tab, y el conteo de condiciones pendientes). Un solo tablero donde se lee, de un vistazo, si la migración avanza, si el legacy está creciendo por atrás, y cuánto falta para poder borrarlo.
2. El código ejecutado. La clase MigrationTracker completa —traffic_percent, velocity, fitness, ratchet y done— corrida con su salida literal, el diario de ocho sprints. No es pseudocódigo ni descripción: es el tracker simulado, reproducible, que puedes correr y modificar (cambia el sprint del HOLD, sube más las referencias, deja una tabla sin cortar al final, y observa cómo el diario cambia —o cómo el done se niega a dispararse—).
3. La justificación: por qué medir el resultado es lo que hace terminar una migración. El tracker no movió una sola llamada; el strangler (M3), la extracción (M5) y la migración de datos (M6) hicieron el movimiento. Lo que el tracker agregó fue la terminación. Sin él, esta migración habría tenido dos caminos hacia el fracaso, ambos vistos en la L7. Podría haber crecido el legacy por atrás (el promo.py del sprint 3) sin que nadie lo notara, alargándose sola. O podría haberse declarado "terminada" en el sprint 6 —con el tráfico casi en cero y los endpoints cortados— dejando la tercera tabla y la última referencia colgando, y el legacy prendido "por si acaso" para siempre. El tracker cerró las dos puertas: la fitness function impidió el crecimiento silencioso, y el criterio de done impidió la victoria prematura. Esa es la justificación, y ahora la puedes respaldar con un diario ejecutado que llega a legacy_borrado = True.
Ejercicios de transferencia
Ejercicio 1 — El sprint que quiso declararse done antes. Imagina que en el sprint 6, con traffic% = 96 y los endpoints en 6/6, el equipo quiere declarar la migración terminada. (a) ¿Qué condiciones de done le faltan según el diario? (b) ¿Qué pasaría si borrara el legacy en ese momento? (c) ¿Cuál de las lecciones del módulo predijo exactamente este error?
Ver solución
(a) En el sprint 6 faltan tres condiciones (la columna decision lo dice: "faltan 3 de done"): legacy_calls == 0 (todavía hay 40 llamadas al legacy), tablas cortadas (2 de 3 —falta una tabla—), y legacy_refs == 0 (queda 1 referencia en el código). Solo los endpoints (6/6) están completos.
(b) Si borrara el legacy en el sprint 6, rompería tres cosas: las 40 requests que aún atiende el legacy se quedarían sin respuesta; la tabla compartida que el modern y el legacy todavía usan desaparecería (rompiendo al modern, que aún depende de ella); y la referencia de código que queda al legacy apuntaría a código borrado. Declarar done por la casilla más visible (el tráfico casi en cero, los endpoints cortados) con frentes abiertos es exactamente lo que rompe una migración al borrar.
(c) La lección 6 (definir el done) lo predijo: done no es una sola métrica sino una lista de condiciones que todas deben cumplirse (all(...)), porque la migración corta el legacy por varios frentes que terminan en momentos distintos —el tráfico y los endpoints primero (visibles), las tablas y las referencias al final (menos visibles)—. El diario muestra justo eso: en el sprint 6 los frentes visibles están cerrados pero los invisibles no. Y la lección 7 (la migración eterna) mostró el costo de ceder a esta tentación: declarar "terminado" con el legacy aún prendido.
Ejercicio 2 — El trinquete que faltó. Supón que el tracker usara la fitness function de la lección 4 (baseline fijo en 4) en vez del trinquete de la lección 5. (a) ¿Qué habría pasado en el sprint 3 con legacy_refs = 3? (b) ¿Por qué eso es peor? (c) ¿Qué renglón del diario cambiaría?
Ver solución
(a) Con un baseline fijo en 4, el sprint 3 (legacy_refs = 3) daría PASS, porque 3 <= 4. La fitness function no atraparía el promo.py que alguien agregó: el legacy creció de 2 a 3 referencias, pero como 3 sigue por debajo del baseline de partida (4), la alarma no suena.
(b) Es peor porque el progreso ya ganado se perdería en silencio. En el sprint 2, la migración había bajado a 2 referencias —un avance real—. Con el baseline fijo, volver a subir a 3 en el sprint 3 pasa sin alarma: el legacy recuperó terreno y nadie se enteró. El trinquete convierte cada avance en un piso irreversible (baseline apretado a 2 en el sprint 2), así que subir a 3 en el sprint 3 evalúa 3 <= 2 → FAIL, y se atrapa. El baseline fijo tolera el vaivén bajo su techo; el trinquete solo tolera el progreso.
(c) Cambiaría el renglón del sprint 3: en vez de FAIL ... HOLD, revertir, mostraría PASS ... avanza, y el burn-down probablemente habría "avanzado" mientras el legacy crecía por atrás —el peor de los mundos, un burn-down que baja por el frente ocultando un legacy que engorda por detrás—. El trinquete es lo que hace que el diario diga la verdad en el sprint 3.
Ejercicio 3 — Extiende el tracker a payments. Vas a arrancar la migración del módulo de payments de Mercado, que —a diferencia del catalog— tiene procesos batch nocturnos y más escrituras. (a) ¿Qué condición de done agregarías a la checklist para payments? (b) ¿Por qué el burn-down de payments podría atorarse en un último tramo distinto al del catalog? (c) ¿Qué forcing function pondrías desde el día uno para que no se vuelva eterna?
Ver solución
(a) Agregaría una condición para los procesos batch nocturnos: "todos los jobs nocturnos que tocaban payments reapuntados al modern y verificados en al menos un ciclo (un cierre de mes)". El catalog era casi todo lecturas síncronas; payments tiene flujos batch que corren fuera del tráfico normal y son fáciles de olvidar —un legacy_calls == 0 medido solo sobre el tráfico síncrono podría dar cero mientras un job nocturno sigue tocando el legacy una vez al mes—. La checklist de done tiene que incluir esos frentes menos visibles, o el done sería falso.
(b) Porque el último tramo del catalog eran los casos raros de precio (el descuento por volumen); el de payments serían esos procesos batch nocturnos y los flujos de excepción (reembolsos, contracargos, conciliaciones) que se ejercitan poco y son difíciles de migrar. Son distintos casos raros, pero el patrón es el mismo (lección 3): el último tramo son los flujos menos visibles y menos frecuentes, y el burn-down se aplana ahí si no se atacan a propósito. Además, al tener más escrituras, la migración de datos de payments (dual-write, parallel-run) es más delicada, y su parte del burn-down puede tardar más en llegar a cero.
(c) Desde el día uno pondría un deadline de done comprometido y visible ("el legacy de payments se borra en tal sprint, con un dueño") y haría visible el costo de mantener los dos sistemas de pagos vivos en un dashboard cada sprint —porque en pagos el costo de duplicar la operación (y el riesgo de conciliación entre dos sistemas) es alto y duele rápido—. Y, preventivamente, atacaría el último tramo temprano: migraría los procesos batch nocturnos en los primeros sprints, cuando hay energía y urgencia, en vez de dejarlos para el final donde los incentivos los abandonan (lección 7). El tracker con el criterio de done como gate del proyecto haría el resto: payments no se declara terminado hasta que su legacy está borrado.
Resumen y siguiente paso
En este capstone integraste todo el módulo en un solo instrumento ejecutado: construiste el MigrationTracker que combina el burn-down (con velocity y traffic_percent), la fitness function con trinquete, y el criterio de done, y lo corriste como el diario de la migración del catalog de Mercado. Viste el tablero completo trabajar junto —como el altímetro, la alarma y la checklist de un avión en aproximación—: el burn-down bajando de 1000 a 0, la fitness function atrapando en el sprint 3 a alguien que agregó código al legacy (HOLD hasta revertir), el trinquete apretando el baseline 4→3→2→1→0, y el criterio de done disparando el borrado solo cuando las cuatro condiciones llegaron a verde. Y produjiste la entrega del capstone: el tablero de progreso, el código ejecutado, y la justificación —respaldada por el diario— de por qué medir el resultado es lo que hace que una migración termine.
Con esto cierras el módulo 7. Dominas el tablero de instrumentos de una migración: sabes medir el resultado y no el esfuerzo, construir e interpretar el burn-down con su velocity y proyección, escribir una fitness function con trinquete que impide que el legacy crezca, definir el done como una lista de condiciones verificables, y reconocer y evitar la migración eterna. Sabes, en una frase, saber que migraste —con números, no con sensaciones—.
Lo que sigue es el final de la guía. El módulo 8 es el capstone de todo: hasta ahora aprendiste cada técnica por separado —caracterizar (M2), strangler (M3), branch by abstraction (M4), extraer un servicio (M5), migrar datos (M6), medir el progreso (este módulo)—. El módulo 8 las encadena todas, en orden, sobre una misma rebanada del catalog, de punta a punta: lo pone bajo characterization tests, lo mete tras un strangler facade, lo extrae con un anti-corruption layer, migra sus datos con dual-write y parallel-run, y —usando el tablero que acabas de construir— mide el progreso hasta apagar el legacy. Este tracker que hiciste será uno de los seis instrumentos del método completo. Vas a hacer la cumbre entera.
Recursos
- Sam Newman, Monolith to Microservices (O'Reilly, 2019), cap. 3 — sobre medir el avance de la descomposición de un monolito por la funcionalidad y las llamadas que se retiran de él, y sobre la disciplina de terminar en vez de coexistir para siempre; el
MigrationTrackerde este proyecto es esa idea hecha tablero. En inglés. - Martin Fowler, "StranglerFigApplication" (2004) — martinfowler.com/bliki/StranglerFigApplication.html. El patrón cuya señal de éxito es que el legacy atienda cada vez menos hasta atender nada y retirarse: exactamente lo que el burn-down mide y el criterio de done declara. En inglés.
- Neal Ford, Rebecca Parsons y Patrick Kua, Building Evolutionary Architectures (O'Reilly, 2ª ed., 2022) — el libro que define las fitness functions como pruebas que guardan propiedades arquitectónicas; el trinquete de este tracker es cómo se hace irreversible la propiedad "el legacy no crece". En inglés.
- Michael Feathers, Working Effectively with Legacy Code (Prentice Hall, 2004) — la base de la caracterización que hace posible medir una migración con seguridad; el tablero de este módulo mide el retiro del código que Feathers enseña a fijar primero. En inglés.