Módulo 7: Medir el progreso de la migración

Presentación del módulo: medir el progreso de la migración

Por qué este módulo existe aquí

Los seis módulos anteriores te enseñaron a mover. Pusiste el legacy bajo characterization tests para poder tocarlo sin miedo (M2). Le pusiste un strangler facade delante y desviaste el tráfico del viejo al nuevo, un porcentaje a la vez (M3). Migraste la implementación por dentro con branch by abstraction (M4). Extrajiste el servicio del catalog del monolito con un anti-corruption layer (M5). Y moviste sus datos de un almacén a otro sin apagar el sistema (M6). Sabes ejecutar cada técnica de migración que existe. Pero hay una pregunta que ninguna de ellas responde, y es la que decide si tu proyecto de modernización termina o se arrastra para siempre: ¿cómo sé si esto avanza, y cómo sé cuándo se acabó?

Este módulo es la respuesta, y fíjate en lo que no es: no es una técnica de movimiento nueva. No moverás tráfico, ni código, ni datos que no hayas movido ya. Lo que agrega este módulo son los instrumentos —el tablero— para saber si las técnicas que ya dominas están llevando la migración a su destino, o si el proyecto lleva año y medio "casi terminado" sin que nadie apague nada. Un piloto sin instrumentos puede tener el mejor avión del mundo y aun así no saber si está subiendo, si le queda combustible, o si va hacia la pista. Los módulos anteriores te dieron el avión; este te da los instrumentos.

El enemigo que este módulo combate tiene nombre: la migración eterna. Es el peor resultado posible de un proyecto de modernización —peor, incluso, que no haberlo empezado—: mantener dos sistemas a la vez, para siempre. El viejo prendido "por si acaso", el nuevo corriendo al lado, y tú pagando la operación de los dos, la complejidad de los dos, y el cansancio de un equipo que nunca ve la meta. Y la migración eterna casi nunca es un fracaso técnico: las técnicas funcionaron, el tráfico se movió, el servicio nuevo corre. Es un fracaso de medición. Nadie definió qué significaba "terminado". Nadie midió el avance con una métrica que no engañe. Y nadie tuvo el criterio duro para decir: "el burn-down llegó a cero, se borra el legacy".

Este módulo te da esas tres cosas, y todo el módulo es aprenderlas una por una y verlas trabajar juntas:

  • La métrica de avance que dice la verdad. El burn-down de las llamadas al legacy, bajando sprint a sprint hasta cero, con el % de tráfico en la ruta nueva subiendo de 0 a 100. Y por qué el resultado (llamadas cortadas) mide de verdad mientras el esfuerzo (story points quemados) engaña.
  • La fitness function que impide que el legacy vuelva a crecer. Una prueba automatizada que cuenta las referencias del código al módulo legacy y falla si crecieron: nadie debería agregar código nuevo al monolito que estás matando, y esta prueba lo atrapa en cada PR.
  • La definición de "done". El criterio duro que declara la migración terminada solo cuando legacy_calls == 0 y el legacy se puede borrar de verdad —no dejarlo "apagado por si acaso"—.

Conexión con el módulo. Esta es la lección-mapa. No entramos todavía al detalle de cada métrica; instalamos la metáfora (la barra de progreso que de verdad llega al 100%), el conjunto de instrumentos (burn-down, fitness function, criterio de done) y el vocabulario (legacy_calls, traffic_percent, burn_down, migration_fitness, baseline, done). Y corremos un ejemplo-mapa: las tres métricas ejecutadas en miniatura, para ver los números moverse antes de entrar al detalle. La lección 2 separa medir el resultado de medir el esfuerzo; la 3 construye el burn-down completo; la 4 escribe la fitness function; la 5 la convierte en un trinquete que impide crecer al legacy; la 6 define el done; la 7 diagnostica y evita la migración eterna; y la 8 lo integra todo en un tablero del catalog de Mercado.

Fíjate en la frontera, que es importante en este módulo. La fitness function como concepto —una prueba automatizada que guarda una propiedad arquitectónica y se rompe cuando esa propiedad se degrada— se enseñó en architecture-decisions-and-tradeoffs-guide. Aquí no re-explicamos qué es una fitness function ni su teoría general; la aplicamos a la migración: una fitness function concreta cuyo único trabajo es que el legacy nunca crezca durante el estrangulamiento. De igual forma, el strangler router que genera el traffic_percent fue el módulo 3 de esta guía, y la migración de datos que mueve las tablas fue el módulo 6; aquí no los volvemos a construir —los medimos—. Este módulo se para sobre el trabajo de los anteriores y le pone encima un tablero.

Y la promesa de siempre: nada se afirma "de memoria", todo se ejecuta. Cada simulación corre con Python 3.14 y solo la biblioteca estándar, con datos fijos, así que la salida que ves en cada bloque "Qué esperar" es la salida literal de correr el código. Puedes copiarlo y reproducirlo idéntico.

Una analogía: la barra de progreso que de verdad llega al 100%

Todos conocemos las dos barras de progreso. Está la buena: descargas un archivo, la barra avanza a un ritmo parejo —10%, 30%, 60%, 90%—, y llega al 100%, y el archivo está en tu carpeta, completo, y la ventana se cierra. La descarga terminó. Puedes usar el archivo, olvidarte de él, cerrar el navegador. Hay un final, y lo alcanzaste.

Y está la mala: la barra que sube rápido hasta el 99% y ahí se queda. Y se queda. Y sigue ahí, en 99%, girando, "casi", durante minutos, a veces para siempre, hasta que la matas y empiezas de nuevo. Esa barra es una mentira: te dice que casi terminó, pero el 1% que falta nunca llega, y mientras tanto no puedes usar el archivo ni seguir con tu vida. El 99% eterno es peor que el 50% honesto, porque el 50% honesto sabe que le falta la mitad y sigue avanzando; el 99% eterno finge que ya casi, y no se mueve.

Una migración es exactamente esas dos barras. La migración buena llega al 100% —el legacy se apaga, se borra, el equipo pasa a otra cosa, hay un final—. La migración mala se queda en el 99% para siempre: el 98% del tráfico ya está en el nuevo, pero ese último 2% nunca se mueve, el legacy nunca se apaga, y el equipo mantiene dos sistemas indefinidamente. Es el mismo 99% eterno de la descarga trabada, solo que cuesta millones y agota a un equipo entero.

La diferencia entre las dos barras no está en la técnica de descargar —las dos usan el mismo protocolo—. Está en si hay una medición honesta que llega al final y un criterio que declara "terminado". Este módulo es lo que convierte tu migración en la barra buena: una métrica que de verdad avanza hasta el 100% (el burn-down), una alarma que impide que el 1% que falta crezca mientras no miras (la fitness function), y una definición de "terminado" que no se conforma con "ya casi" (el criterio de done). Hay una segunda imagen que aparecerá en la lección 7 y que vale la pena tener desde ya: la remodelación de la casa que "ya casi" pero nunca se termina, y vives tres años entre escombros, pagando la renta de la casa vieja y la nueva a la vez. Esa es la migración eterna en su forma más dolorosa.

Ejemplo trabajado: las tres métricas en miniatura

No vamos a describir las métricas: las vamos a ejecutar, aunque sea en su versión más pequeña, las tres juntas. La idea es correr, sobre los datos del catalog de Mercado, el burn-down de las llamadas al legacy, la fitness function que vigila que el legacy no crezca, y el criterio de done que declara cuándo se puede borrar. Cada pieza aparece aquí en una línea; las lecciones que siguen las desarrollan.

Un detalle del modelo que verás repetido en todo el módulo: medimos sobre una muestra fija de 1000 requests del catalog por sprint. De esas 1000, legacy_calls es cuántas todavía terminan cayendo en el monolito legacy (porque la ruta nueva aún no cubre ese caso, o porque el código nuevo todavía llama de vuelta al monolito para alguna función). Las demás las atiende la ruta nueva. El traffic_percent es, entonces, la fracción de las 1000 que ya no toca el legacy. El burn-down es ver legacy_calls bajar sprint a sprint.

# El mapa del modulo, ejecutado en miniatura:
# burn-down + fitness function + metrica de done, las tres piezas juntas.

# --- Muestra fija: 1000 requests del catalog por sprint. legacy_calls = las
#     que todavia caen en el monolito legacy. ---
SAMPLE = 1000
burn_down = [1000, 820, 610, 400, 210, 60, 0]   # legacy_calls por sprint

def traffic_percent(legacy_calls):
    return (SAMPLE - legacy_calls) / SAMPLE * 100

print("El mapa del modulo, ejecutado en miniatura\n")
print("1) BURN-DOWN de llamadas de catalog al monolito (sprint a sprint)\n")
print(f"{'sprint':>7}{'legacy_calls':>13}{'new_route':>11}{'traffic%':>10}   burn-down")
print("-" * 66)
for sprint, legacy_calls in enumerate(burn_down):
    new_route = SAMPLE - legacy_calls
    tp = traffic_percent(legacy_calls)
    bar = "#" * (legacy_calls * 22 // SAMPLE)
    print(f"{sprint:>7}{legacy_calls:>13}{new_route:>11}{tp:>9.0f}%   |{bar:<22}|")

# 2) FITNESS FUNCTION: cuenta las referencias al monolito legacy en el codigo del
#    catalog. FALLA si SUBIERON respecto al baseline (alguien agrego codigo nuevo
#    al legacy que estamos matando).
def migration_fitness(legacy_refs, baseline):
    return legacy_refs <= baseline    # las referencias nunca deben crecer

print("\n2) FITNESS FUNCTION (las referencias al legacy no pueden crecer)\n")
baseline = 12
for legacy_refs in (9, 14):
    ok = migration_fitness(legacy_refs, baseline)
    verdict = "PASS" if ok else "FAIL <- se agrego codigo nuevo al legacy"
    print(f"   baseline={baseline}  legacy_refs={legacy_refs:>2}  -> {verdict}")

# 3) DONE: la migracion termina SOLO cuando legacy_calls == 0 (y el legacy se
#    puede borrar; no se deja "prendido por si acaso").
def done(legacy_calls):
    return legacy_calls == 0

print("\n3) DONE (la migracion termina cuando legacy_calls == 0)\n")
for legacy_calls in (60, 0):
    d = done(legacy_calls)
    action = "BORRAR el legacy" if d else "seguir: el legacy aun atiende"
    print(f"   legacy_calls={legacy_calls:>2}  done={str(d):<5}  -> {action}")

print("\n  Tres numeros, no tres opiniones: cuanto falta (burn-down), si el legacy")
print("  crece a escondidas (fitness), y cuando de verdad se acabo (done).")

Qué esperar. Al correr el archivo, la salida es exactamente esta:

El mapa del modulo, ejecutado en miniatura

1) BURN-DOWN de llamadas de catalog al monolito (sprint a sprint)

 sprint legacy_calls  new_route  traffic%   burn-down
------------------------------------------------------------------
      0         1000          0        0%   |######################|
      1          820        180       18%   |##################    |
      2          610        390       39%   |#############         |
      3          400        600       60%   |########              |
      4          210        790       79%   |####                  |
      5           60        940       94%   |#                     |
      6            0       1000      100%   |                      |

2) FITNESS FUNCTION (las referencias al legacy no pueden crecer)

   baseline=12  legacy_refs= 9  -> PASS
   baseline=12  legacy_refs=14  -> FAIL <- se agrego codigo nuevo al legacy

3) DONE (la migracion termina cuando legacy_calls == 0)

   legacy_calls=60  done=False  -> seguir: el legacy aun atiende
   legacy_calls= 0  done=True   -> BORRAR el legacy

  Tres numeros, no tres opiniones: cuanto falta (burn-down), si el legacy
  crece a escondidas (fitness), y cuando de verdad se acabo (done).

Lee la salida en tres bloques, porque ahí está el módulo entero.

El bloque 1 es el burn-down, la métrica de avance. Mira la columna legacy_calls bajar de arriba hacia abajo —1000, 820, 610, 400, 210, 60, 0— y mira la barra encogerse en paralelo. Cada sprint, más requests dejan de tocar el legacy y las atiende la ruta nueva; el traffic_percent sube de 0% a 100%. Ese descenso es la evidencia visual de que la migración avanza: el árbol viejo recibe cada vez menos savia hasta secarse. La lección 3 lo desarrolla, y verás algo importante: lo que dice si terminarás no es cuánto vale un sprint, sino la pendiente con la que baja.

El bloque 2 es la fitness function, la alarma anti-crecimiento. Toma dos escenarios contra un baseline de 12 referencias al legacy. Con legacy_refs = 9 (bajaron), da PASS: el legacy encogió, todo bien. Con legacy_refs = 14 (subieron), da FAIL: alguien agregó código nuevo que llama al legacy —justo lo que no debe pasar cuando estás matando un módulo—. La fitness function convierte esa regla ("nadie agrega código al legacy") de un buen deseo a una prueba automática que rompe el CI. La lección 4 la construye.

El bloque 3 es el criterio de done, el final honesto. Con legacy_calls = 60, done = False: el legacy todavía atiende 60 requests, la migración sigue. Con legacy_calls = 0, done = True: nadie llama al legacy, y entonces —y solo entonces— se borra. Es la diferencia entre la barra que llega al 100% y la que se queda en 99%. La lección 6 lo define con todo el rigor (spoiler: el criterio real tendrá más de una condición).

Los tres números juntos son el tablero. El burn-down dice cuánto falta. La fitness function dice si el legacy crece a escondidas. El done dice cuándo de verdad se acabó. Ninguno es una opinión; los tres son mediciones que puedes reproducir.

El conjunto de instrumentos

Ese ejemplo tocó las tres métricas sin desarrollarlas. Vale la pena verlas explícitas, porque son la columna vertebral de las siete lecciones que siguen:

Instrumento           Leccion   Que mide                            Que decide
────────────────────  ────────  ──────────────────────────────────  ─────────────────
resultado vs esfuerzo   L2        avance REAL vs avance aparente      en que confiar
burn_down (legacy_calls) L3       cuanto falta y a que ritmo          si converge a 0
migration_fitness       L4-L5     si el legacy crece (refs > base)    si frenar un PR
done (criterio duro)    L6        cuando esta de verdad terminado     cuando BORRAR
migracion eterna        L7        el costo de NO terminar             cuando forzar el cierre

Y así encajan en el flujo del módulo:

flowchart LR
    A["medir resultado<br/>(no esfuerzo)"] --> B["burn_down<br/>de legacy_calls"]
    B --> C["migration_fitness<br/>(el legacy no crece)"]
    C --> D["trinquete<br/>(el progreso no retrocede)"]
    D --> E["criterio de done<br/>(legacy_calls == 0)"]
    E --> F["BORRAR el legacy<br/>(evitar la migracion eterna)"]

Léelo así: primero decides qué medir —el resultado, no el esfuerzo (L2)—. Con eso construyes el burn-down de legacy_calls, que dice cuánto falta y a qué ritmo (L3). En paralelo, la fitness function vigila que el legacy no crezca mientras lo matas (L4), y el trinquete hace ese progreso irreversible (L5). Cuando el burn-down toca cero y todas las condiciones de done se cumplen (L6), se borra el legacy —y con eso evitas la migración eterna (L7)—. El tablero completo, integrado, es el capstone (L8).

El mapa: dónde está este módulo en la guía

Este módulo es el que le pone instrumentos a todo lo anterior. Moviste tráfico, código y datos; ahora mides si ese movimiento llega a su fin:

flowchart TD
    M3["M3 · Strangler fig<br/>(genera el traffic_percent)"]
    M5["M5 · Extraer un servicio<br/>(anti-corruption layer)"]
    M6["M6 · Migrar datos<br/>(mueve las tablas)"]
    M7["M7 · Medir el progreso<br/>(burn-down, fitness, done)"]
    AD["architecture-decisions<br/>(la fitness function COMO CONCEPTO)"]
    M3 --> M7
    M5 --> M7
    M6 --> M7
    AD -.aplicada aqui.-> M7

Léelo así: el strangler fig (M3) genera el traffic_percent que aquí medimos como burn-down; extraer el servicio (M5) y migrar los datos (M6) producen los endpoints y las tablas que aquí contamos como "cortados del monolito"; y la fitness function como concepto viene de architecture-decisions, donde aprendiste qué es y cómo se escribe una en general —aquí la aplicamos a un objetivo concreto de la migración—. Este módulo no reconstruye ninguna de esas piezas; les pone un tablero encima para saber si el conjunto avanza hacia un final.

Y la frontera con las guías hermanas del ecosistema, que conviene tener clara: la teoría general de las fitness functions —qué tipos hay, cómo se integran en el pipeline, cómo se gobierna una arquitectura evolutiva— es de architecture-decisions y del libro de Ford & Parsons. Aquí usamos una fitness function, con un propósito: que el legacy no crezca durante el estrangulamiento. Del mismo modo, cómo se construye el strangler o cómo se mueven los datos a escala pertenece a M3/M6 y a Data Engineering; aquí solo medimos su avance.

Errores comunes

Confundir "la migración avanza" con "el equipo está ocupado". Qué pasa: el equipo reporta el progreso en términos de actividad —cuántos story points quemó, cuántas horas metió, cuántos PRs mergeó— y todos asumen que la migración avanza porque hay mucho trabajo. Por qué pasa: la actividad es visible y se siente como progreso; medir el resultado (cuántas llamadas al legacy se cortaron de verdad) requiere instrumentar el sistema y a veces revela una verdad incómoda. Cómo detectarlo: el reporte de avance habla de esfuerzo (puntos, horas, tareas) y no de resultado (llamadas al legacy, endpoints cortados, tráfico en el nuevo); nadie puede decirte qué porcentaje del tráfico ya no toca el legacy. Cómo corregirlo: mide el resultado. Un equipo puede quemar cientos de puntos "preparando" la migración sin cortar una sola llamada al legacy —igual que puedes manejar ocho horas en círculos sin acercarte a tu destino—. La lección 2 muestra, ejecutado, cómo el esfuerzo dice 80% mientras el resultado dice 24%; la única métrica que no engaña es la que apunta al legacy muriendo.

No tener una definición de "terminado" antes de empezar. Qué pasa: el equipo arranca la migración sin acordar qué significará que esté completa, y cuando llega al 90% nadie sabe si eso es "ya casi" o "falta un mundo". Por qué pasa: al principio el final se ve tan lejos que definirlo parece prematuro; y una vez en marcha, cada quien tiene su idea de "listo". Cómo detectarlo: si preguntas "¿cómo sabremos que terminamos?" y las respuestas difieren —"cuando el tráfico esté en el nuevo", "cuando el servicio corra", "cuando ya no haya bugs"—, no hay definición. Cómo corregirlo: define el done el día uno, como una lista de condiciones verificables (legacy_calls == 0, endpoints y tablas cortados, referencias al legacy en cero, legacy borrado), y ponla en el plan. Sin criterio de done, la migración no tiene línea de meta —y una carrera sin meta es la migración eterna de la lección 7—. La lección 6 construye ese criterio.

Ejercicios

Ejercicio 1 — Los tres instrumentos. Empareja cada pregunta con el instrumento del módulo que la responde: (a) "¿cuánto nos falta y a qué ritmo vamos?", (b) "¿alguien está agregando código al legacy que se supone estamos matando?", (c) "¿ya terminamos, podemos borrar el viejo?". Luego explica por qué necesitas los tres y no basta con uno.

Ver solución
  • (a) "¿cuánto nos falta y a qué ritmo?" → el burn-down (legacy_calls, lección 3). Mide cuántas llamadas al legacy quedan y, por su pendiente, a qué velocidad bajan y cuándo llegarán a cero.
  • (b) "¿alguien agrega código al legacy?" → la fitness function (lección 4). Cuenta las referencias al legacy y falla si crecieron respecto al baseline, atrapando el código nuevo agregado al módulo que se está matando.
  • (c) "¿ya terminamos?" → el criterio de done (lección 6). Declara la migración completa solo cuando legacy_calls == 0 (y las demás condiciones se cumplen), lo que autoriza a borrar el legacy.

Necesitas los tres porque cada uno tapa un hueco de los otros. El burn-down te dice que avanzas, pero no impide que el legacy crezca por otro lado mientras lo reduces por el frente —eso lo vigila la fitness function—. Y ninguno de los dos declara el final: un burn-down puede estar en 99% para siempre, y la fitness function solo dice "no creció", no "terminó" —eso lo declara el criterio de done—. Con solo el burn-down te falta la alarma anti-crecimiento y la línea de meta; con solo la fitness function no sabes cuánto falta; con solo el done no sabes si vas llegando. El tablero es los tres juntos.

Ejercicio 2 — Lee el mapa. En el ejemplo trabajado, el burn-down fue 1000, 820, 610, 400, 210, 60, 0 en los sprints 0 a 6. (a) ¿Qué traffic_percent corresponde a legacy_calls = 400, y qué significa? (b) En el bloque de la fitness function, ¿por qué legacy_refs = 9 da PASS y legacy_refs = 14 da FAIL, si en los dos casos el legacy todavía existe? (c) ¿Por qué legacy_calls = 60 da done = False aunque el 94% del tráfico ya esté en el nuevo?

Ver solución

(a) legacy_calls = 400 sobre una muestra de 1000 significa que 600 requests ya no tocan el legacy, así que traffic_percent = 600 / 1000 × 100 = 60%. El 60% del tráfico del catalog lo atiende la ruta nueva; el 40% restante todavía cae en el monolito legacy. Es el sprint 3: pasaste la mitad, pero aún falta un 40% por migrar.

(b) La fitness function no mide si el legacy existe —claro que existe, la migración está en curso—; mide si el legacy creció respecto al baseline. El baseline es 12. Con legacy_refs = 9, las referencias bajaron (de 12 a 9): el legacy encogió, que es exactamente lo que debe pasar → PASS. Con legacy_refs = 14, subieron (de 12 a 14): alguien agregó dos referencias nuevas al legacy → FAIL. La regla es "el legacy nunca crece durante la migración", y la fitness function la vigila comparando contra el baseline, no contra cero.

(c) Porque done no pregunta "¿ya casi?", pregunta "¿el legacy dejó de atender por completo?". Con legacy_calls = 60, todavía hay 60 requests que dependen del legacy: si lo apagaras ahora, esas 60 quedarían sin quién las atienda. El 94% no basta —el 6% que falta es real, y mientras ese 6% dependa del legacy, no se puede borrar—. Es la barra en 94%, no en 100%: cerca, pero no terminada. La lección 6 hará este criterio aún más estricto (no basta legacy_calls == 0; también hacen falta las tablas cortadas y las referencias en cero).

Ejercicio 3 — ¿Concepto o aplicación? Para cada situación, di si es algo que este módulo enseña (la métrica aplicada a la migración) o algo que pertenece a otra guía (el concepto o la técnica de base), y por qué: (a) qué es en general una fitness function y qué tipos existen; (b) escribir una fitness function que cuenta referencias al legacy y falla si crecen; (c) construir el strangler router que reparte el traffic_percent; (d) leer el burn-down de legacy_calls y proyectar el sprint de cierre.

Ver solución
  • (a) Qué es una fitness function y sus tipos → architecture-decisions (el concepto). La teoría general —qué es una fitness function, cómo se clasifica (atómica/holística, activada/continua), cómo gobierna una arquitectura evolutiva— se enseñó allá y en el libro de Ford & Parsons. Aquí se da por sabida.
  • (b) Escribir la fitness function que cuenta referencias al legacy → este módulo (la aplicación). Tomar el concepto conocido y aplicarlo a un objetivo concreto de la migración —que el legacy no crezca— es exactamente lo que hace la lección 4.
  • (c) Construir el strangler router → módulo 3 de esta guía (la técnica de base). El router que reparte el traffic_percent se construyó en M3. Aquí no lo construimos; medimos el burn-down que produce.
  • (d) Leer el burn-down y proyectar el cierre → este módulo (la métrica aplicada). Interpretar la pendiente de legacy_calls y proyectar cuándo llega a cero es la lección 3, el corazón de medir el progreso.

La lección de fondo: este módulo no inventa las técnicas ni los conceptos —los toma de M3, M5, M6 y de architecture-decisions— y les pone encima la capa que faltaba: medir si el conjunto avanza hacia un final. Reconocer esa frontera te evita re-explicar lo que ya sabes y te enfoca en lo nuevo: los instrumentos.

Resumen y siguiente paso

En esta lección instalaste la última pieza de la guía: los instrumentos para medir el progreso de una migración. Viste que este módulo no mueve nada nuevo —mueve la medición—, y que su enemigo es la migración eterna, un fracaso no de técnica sino de medición. Instalaste la metáfora (la barra de progreso que de verdad llega al 100% frente a la que se queda en 99% para siempre) y los tres instrumentos: el burn-down que dice cuánto falta, la fitness function que impide que el legacy crezca, y el criterio de done que declara cuándo se puede borrar. Y los ejecutaste en miniatura: legacy_calls bajando de 1000 a 0 con su traffic_percent, la fitness dando PASS a la baja y FAIL cuando el legacy crece, y el done disparando "borrar" solo en cero.

Antes de avanzar deberías poder: nombrar los tres instrumentos y qué decide cada uno; explicar por qué la migración eterna es un fracaso de medición y no de técnica; leer un estado legacy_calls = N y convertirlo en traffic_percent; y distinguir lo que este módulo aplica (la métrica) de lo que otras guías enseñan (el concepto de fitness function, la técnica del strangler).

La lección 2 ataca la pregunta más básica y más traicionera: qué medir. Vas a ver, ejecutada, la diferencia entre medir el resultado (las llamadas al legacy que de verdad se cortaron) y medir el esfuerzo (los story points que se quemaron) —y cómo el esfuerzo corre por delante y da una falsa sensación de "casi terminado" mientras el resultado, más lento y más honesto, dice la verdad—. Es la base sobre la que se apoyan todas las demás métricas: si mides lo que no debes, ninguna alarma ni ningún criterio de done te salvará.

Recursos

  • Martin Fowler, "StranglerFigApplication" (2004) — martinfowler.com/bliki/StranglerFigApplication.html. El texto fundacional insiste en que la meta del strangler es reemplazar, no coexistir: el sistema viejo debe morir. Este módulo es la disciplina de medir para que de verdad muera. En inglés.
  • Sam Newman, Monolith to Microservices (O'Reilly, 2019), cap. 3 — el desvío incremental y, sobre todo, cómo saber que una migración de un monolito por rebanadas está avanzando y cómo llevarla a su cierre. La referencia para medir el progreso de la descomposición. 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. Aquí aplicamos una de ellas a la migración; el concepto general está en este libro. En inglés.
  • Martin Fowler, martinfowler.com — el índice de su bliki, con las entradas sobre strangler fig, parallel change y arquitectura evolutiva que sostienen toda esta guía. El punto de partida para leer las fuentes de cada patrón. En inglés.