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

Evitar la migración eterna

Descripción

Esta lección trata el peor resultado posible de un proyecto de modernización, y tiene nombre: la migración eterna. Es el estado en que una migración avanza bien durante meses, llega al 90% o 98%, y ahí se queda para siempre —el legacy nunca se apaga, el equipo mantiene dos sistemas indefinidamente, y el premio de la migración nunca se cobra—. Es peor que no haber empezado, porque no empezar te deja con un sistema que mantener; la migración eterna te deja con dos, para siempre, más la complejidad de tenerlos coexistiendo. Y lo más doloroso es que casi siempre llega después de que la parte difícil ya se hizo: el equipo construyó toda la infraestructura, movió el 98% del tráfico, y luego no terminó el último 2% —clavando el proyecto en el punto exacto donde ya pagó todos los costos y está a un empujón de cobrar todos los beneficios—.

La migración eterna casi nunca es un fracaso técnico. El burn-down bajó, la fitness function funcionó, los datos se migraron. Es un fracaso de terminación: no hubo un criterio de done que obligara a cerrar el último tramo, y sin esa obligación, el último tramo —el más aburrido, el de los casos raros, el que ya no tiene urgencia porque "el 98% ya está en el nuevo"— nunca sube en la lista de prioridades. Siempre hay algo más urgente que apagar un legacy que "de todos modos ya casi no se usa". Esta lección muestra, ejecutada, cómo dos migraciones idénticas en su parte técnica terminan de formas opuestas según tengan o no un criterio de done que las fuerce a cerrar —y cuánto cuesta, en números, la que no termina—.

Conexión con el módulo. Esta lección es la consecuencia de no tener lo que construimos en las lecciones anteriores: sin la definición de done (lección 6), sin el burn-down que detecta el atoramiento (lección 3), la migración se vuelve eterna. Es también el argumento de fondo de todo el módulo —la razón por la que medir el progreso importa—: se mide para terminar, y no terminar es el desastre que la medición existe para evitar. La lección 8 integrará todo en un tablero que sí lleva la migración a su cierre. Fíjate en la frontera: el M3 (lección 7) ya nombró la migración eterna para una rebanada individual (dejar el legacy prendido con cero tráfico). Aquí la tratamos a escala de programa —una migración completa que se atora en el último tramo— y agregamos lo que la evita: el criterio de done y la forcing function que fuerzan el cierre.

Una analogía: la remodelación que "ya casi" y los tres años entre escombros

Conoces a alguien —o eres ese alguien— que remodeló su casa y "ya casi termina". La cocina quedó, los cuartos quedaron, pintaron casi todo. Pero falta: un baño sin azulejos, un cuarto que quedó de bodega con los materiales sobrantes, una puerta que nunca colgaron, el patio con los escombros de la obra amontonados en una esquina. Y así llevan tres años. No es que no puedan terminar —falta poquísimo, un fin de semana de trabajo—; es que ya nadie tiene la urgencia, el maestro se fue a otra obra, y "total, ya se puede vivir en la casa". Viven entre escombros, con la obra al 95%, indefinidamente, porque el último 5% nunca sube en la lista de prioridades de la vida.

Y esa remodelación a medias cuesta, aunque nadie lleve la cuenta. Cuesta el cuarto que no pueden usar (perdieron una habitación). Cuesta tropezarse con los materiales cada día. Cuesta la incomodidad de un baño sin terminar. Y si rentaron un departamento "mientras terminan la obra", cuesta dos rentas a la vez —la del departamento temporal y la de la casa que ya casi—, un gasto doble que se suponía era por unos meses y lleva tres años. El costo de "ya casi" no es cero; es un goteo constante que se acumula, mes tras mes, porque la obra nunca cruza la línea de terminada.

La migración eterna es esa remodelación. El 98% del tráfico en el nuevo es la casa donde "ya se puede vivir". El legacy prendido es el patio con los escombros y el cuarto de bodega —los restos de la obra que nadie retira—. Y las dos rentas son el costo de mantener dos sistemas: pagas la operación del modern y la del legacy, la complejidad de los dos, el equipo dividido entre los dos, un gasto doble que se suponía temporal y se volvió permanente. La diferencia entre la casa terminada y la de tres años entre escombros no es la habilidad del maestro —los dos saben poner azulejos—; es que alguien puso una fecha de entrega y la hizo cumplir. Sin fecha, "ya casi" es para siempre.

Ejemplo trabajado: dos migraciones, una termina y otra no

Vamos a ejecutar dos migraciones lado a lado, con la misma velocidad los primeros seis sprints, y ver cómo terminan de formas opuestas. La migración A no tiene criterio de done: baja bien hasta el 98.5% y ahí se atora (se queda en 15 llamadas al legacy para siempre). La migración B tiene una forcing function (un deadline de done): sigue empujando el último tramo hasta llegar a cero y borrar el legacy. En cada sprint contamos el costo acumulado de mantener los dos sistemas: mientras el legacy esté vivo, se paga; cuando se borra, el costo se detiene.

# La migracion eterna vs la que termina. Dos migraciones, 12 sprints.
# A (sin criterio de done) se atora en 98.5% para siempre.
# B (con forcing function) llega a 0 y BORRA el legacy.
# El costo de mantener dos sistemas se acumula mientras el legacy siga vivo.

COST_PER_SPRINT = 10   # lo que cuesta tener los dos sistemas prendidos un sprint

# legacy_calls por sprint (sprints 1..12)
migration_A = [1000, 700, 400, 150, 40, 15, 15, 15, 15, 15, 15, 15]  # plateau
migration_B = [1000, 700, 400, 150, 40, 15,  0,  0,  0,  0,  0,  0]  # termina

def run(name, calls):
    print(f"{name}")
    print(f"  {'sprint':>7}{'legacy_calls':>14}{'legacy vivo?':>14}{'costo acum.':>13}")
    print("  " + "-" * 48)
    cost = 0
    deleted_at = None
    for sprint, legacy_calls in enumerate(calls, start=1):
        alive = legacy_calls > 0 or deleted_at is None
        # se borra el legacy el primer sprint que llega a 0 (y ahi para el costo)
        if legacy_calls == 0 and deleted_at is None:
            deleted_at = sprint
        if deleted_at is not None and sprint > deleted_at:
            alive = False
        if alive:
            cost += COST_PER_SPRINT
        status = "si" if alive else "BORRADO"
        print(f"  {sprint:>7}{legacy_calls:>14}{status:>14}{cost:>13}")
    print("  " + "-" * 48)
    if deleted_at:
        print(f"  -> legacy BORRADO en el sprint {deleted_at}. Costo total: {cost}. "
              f"Deja de pagar.\n")
    else:
        final = calls[-1]
        pct = (1000 - final) / 1000 * 100
        print(f"  -> nunca llega a 0 (atorada en {pct:.1f}%). Costo a 12 sprints: "
              f"{cost}, y SIGUE pagando para siempre.\n")

print("Migracion eterna vs migracion que termina\n")
run("A - sin criterio de done (se atora en el ultimo 1.5%):", migration_A)
run("B - con forcing function (deadline de done):", migration_B)

print("  Misma velocidad los primeros 6 sprints. La diferencia no es tecnica:")
print("  es tener (o no) un criterio de done que obligue a cerrar el ultimo tramo.")

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

Migracion eterna vs migracion que termina

A - sin criterio de done (se atora en el ultimo 1.5%):
   sprint  legacy_calls  legacy vivo?  costo acum.
  ------------------------------------------------
        1          1000            si           10
        2           700            si           20
        3           400            si           30
        4           150            si           40
        5            40            si           50
        6            15            si           60
        7            15            si           70
        8            15            si           80
        9            15            si           90
       10            15            si          100
       11            15            si          110
       12            15            si          120
  ------------------------------------------------
  -> nunca llega a 0 (atorada en 98.5%). Costo a 12 sprints: 120, y SIGUE pagando para siempre.

B - con forcing function (deadline de done):
   sprint  legacy_calls  legacy vivo?  costo acum.
  ------------------------------------------------
        1          1000            si           10
        2           700            si           20
        3           400            si           30
        4           150            si           40
        5            40            si           50
        6            15            si           60
        7             0            si           70
        8             0       BORRADO           70
        9             0       BORRADO           70
       10             0       BORRADO           70
       11             0       BORRADO           70
       12             0       BORRADO           70
  ------------------------------------------------
  -> legacy BORRADO en el sprint 7. Costo total: 70. Deja de pagar.

  Misma velocidad los primeros 6 sprints. La diferencia no es tecnica:
  es tener (o no) un criterio de done que obligue a cerrar el ultimo tramo.

Mira las dos migraciones en paralelo. Los primeros seis sprints son idénticos: las dos bajan 1000, 700, 400, 150, 40, 15 —la misma velocidad, la misma parte técnica, el mismo equipo capaz—. Hasta el sprint 6, no hay forma de distinguirlas: las dos van bien, las dos llegaron al 98.5%, las dos hicieron el trabajo difícil. Si las juzgaras en el sprint 6, dirías que las dos son un éxito.

La diferencia aparece en el sprint 7, y no es técnica. La migración A se atora: se queda en 15 llamadas al legacy, sprint tras sprint, del 7 al 12 y más allá. Ese 15 es el último 1.5% —los casos raros, los flujos que nadie documentó, el proceso nocturno olvidado—, y sin un criterio de done que obligue a cerrarlo, nunca sube en la lista de prioridades. Siempre hay algo más urgente que apagar un legacy que "ya casi no se usa". La columna legacy vivo? dice "si" en cada renglón, y el costo acum. sigue subiendo: 70, 80, 90, 100, 110, 120 al sprint 12 —y el renglón final lo remata: "SIGUE pagando para siempre"—. La migración A hizo el 98.5% del trabajo y no cobrará jamás el premio, porque el legacy nunca se apaga.

La migración B termina. En el sprint 7, la forcing function —un deadline de done, la decisión de que el último tramo se cierra en tal sprint— empuja las últimas 15 llamadas a cero. legacy_calls == 0, done se cumple, y el legacy se BORRA. A partir del sprint 8, la columna dice "BORRADO" y el costo acum. se congela en 70: dejó de pagar. La migración B cobró el premio —un solo sistema, el monolito una rebanada más pequeño, el equipo libre para lo siguiente—.

Ahora compara los costos: A pagó 120 y contando (para siempre); B pagó 70 y paró. Y la brecha crece cada sprint: en el sprint 12 es 120 contra 70, pero en el sprint 20 sería 200 contra 70, en el sprint 50 sería 500 contra 70. La migración eterna no tiene un costo fijo; tiene un costo que se acumula sin límite, porque nunca deja de pagar las dos rentas. Y todo por un último 1.5% que costaba un sprint cerrar. El renglón final lo dice sin rodeos: la diferencia no es técnica —las dos tuvieron la misma velocidad los primeros seis sprints—; es tener, o no, un criterio de done que obligue a cerrar el último tramo.

Profundización: por qué el último tramo no se cierra solo, y cómo forzarlo

La migración eterna tiene una causa estructural que vale la pena entender, porque explica por qué el último tramo necesita una fuerza para cerrarse y no se cierra por sí mismo.

El problema es que los incentivos se invierten en el último tramo. Al principio de la migración, cada sprint corta mucho tráfico (200, 300 llamadas) y el progreso es visible, motivador, celebrado —hay urgencia y recompensa—. Al final, el último 1.5% son los casos más difíciles y menos visibles: el flujo que un solo cliente usa una vez al mes, el proceso batch nocturno que nadie recuerda, la integración con un socio olvidado. Cerrar esos casos es mucho trabajo por muy poco burn-down visible —un sprint entero para pasar de 15 a 0—, y el beneficio (poder borrar el legacy) es real pero diferido. Mientras tanto, el legacy "ya casi no molesta" (98.5% del tráfico ya está en el nuevo), así que la presión de terminarlo es mínima. La combinación —mucho esfuerzo, poco progreso visible, poca urgencia— hace que el último tramo siempre pierda contra cualquier otra cosa en la lista de prioridades. No se cierra solo porque nada lo empuja a cerrarse.

   Progreso visible / urgencia
        │
    alta│ ● ● ●
        │       ● ●
        │           ●
        │             ●___________________  <- el ultimo tramo:
    baja│                                       mucho esfuerzo, poco
        │                                       burn-down, cero urgencia
        └────────────────────────────────────
         sprint 1                    "ya casi" para siempre
                                     (aqui muere la migracion)

La solución es una forcing function: un mecanismo que fuerza el cierre del último tramo a pesar de la falta de urgencia natural. Hay varias formas, y las buenas migraciones usan al menos una:

  • Un deadline de done, comprometido y visible. "El legacy del catalog se apaga el 30 de tal mes" —una fecha pública, en el plan, con un dueño—. La fecha crea la urgencia que el último tramo no tiene por sí mismo. Es lo que la migración B tenía y la A no.
  • El costo del legacy, hecho visible. Poner en un dashboard, cada sprint, cuánto cuesta mantener el legacy vivo (operación, mantenimiento, el equipo dividido). Cuando el costo de "ya casi" es visible y se acumula a la vista de todos, la presión de cerrarlo aparece. El número 120-y-contando del ejemplo es esa visibilidad.
  • La definición de done como gate del proyecto. El proyecto de migración no se cierra —no se declara terminado, no se libera al equipo, no se celebra— hasta que el legacy está borrado. Si "terminado" exige el borrado (lección 6), no hay forma de declarar victoria en el 98.5%; el proyecto sigue oficialmente abierto, con su presión, hasta el cierre real.
  • Atacar el último tramo primero, no al final. Una variante preventiva: identificar los casos raros y difíciles temprano (cuando hay energía y urgencia) en vez de dejarlos para el final (cuando no hay ninguna de las dos). Migrar el proceso nocturno en el sprint 3, no en el 11.

El punto común de todas es que el último tramo necesita una fuerza externa, porque los incentivos naturales lo abandonan. Medir el progreso —tener el burn-down que muestra el atoramiento, el criterio de done que exige el cero, el costo visible que se acumula— es lo que provee esa fuerza. Una migración sin instrumentos no ve que se está atorando hasta que ya lleva un año en 98.5%; una migración con el tablero de este módulo ve la velocity caer en el sprint 7 y actúa. Ese es, en última instancia, el porqué de todo el módulo: se mide para terminar, y la migración eterna es lo que le pasa a quien no mide.

Una precisión importante para no sobrecorregir: forzar el cierre no significa apagar el legacy con casos aún dependientes de él. La forcing function empuja a completar el último tramo (cerrar de verdad las 15 llamadas migrando sus casos al modern), no a apagar el legacy dejando esos casos sin atender. La diferencia entre "cerrar" y "abandonar" es que cerrar migra los casos que faltan; apagar sin migrarlos los rompe. El done exige legacy_calls == 0 real, no legacy_calls == 0 por haber apagado el legacy y dejado 15 requests fallando. La forcing function acelera el trabajo del último tramo; no lo salta.

Errores comunes

Dejar el último tramo para el final "porque ya casi". Qué pasa: el equipo migra rápido el 90% fácil y deja los casos raros y difíciles para el final, donde nunca se hacen. Por qué pasa: los casos fáciles dan burn-down visible y satisfactorio; los difíciles son mucho trabajo por poco progreso aparente, así que se posponen. Cómo detectarlo: el burn-down baja rápido y luego se aplana lejos de cero (la forma "el que se atora" de la lección 3); el legacy_calls restante son siempre los mismos casos raros, sprint tras sprint. Cómo corregirlo: ataca el último tramo temprano, cuando hay energía y urgencia, no al final cuando no hay ninguna. Identifica los casos raros y difíciles al principio y migra algunos en los primeros sprints. Y cuando llegues al último tramo, dale una forcing function (deadline, costo visible) que lo empuje, porque no se cerrará solo —los incentivos lo abandonan justo ahí—.

No tener criterio de done, así que la migración nunca se declara terminada. Qué pasa: la migración llega al 98% y se queda ahí porque no hay una línea de meta definida que obligue a cerrar el 2% restante. Por qué pasa: sin un done escrito (lección 6), "terminado" es una sensación, y en el 98% la sensación es "ya casi, luego lo cerramos" —un "luego" que no llega—. Cómo detectarlo: nadie puede decir cuándo estará terminada la migración; el legacy lleva meses en el mismo porcentaje; el proyecto no está "cerrado" ni "abierto", solo flotando. Cómo corregirlo: define el done (lección 6) y trátalo como el gate del proyecto —la migración no se cierra hasta que el legacy está borrado—. Sin criterio de done, no hay forma de saber que te atoraste ni presión para desatorarte; con él, el 98.5% es visiblemente "no terminado" y el proyecto sigue oficialmente abierto hasta el cierre real. El criterio de done es la fecha de entrega de la remodelación: sin ella, "ya casi" es para siempre.

No medir (ni hacer visible) el costo de mantener dos sistemas. Qué pasa: el equipo no lleva la cuenta de cuánto cuesta el legacy vivo, así que la migración eterna no se siente cara y nadie presiona por cerrarla. Por qué pasa: el costo de operar el legacy está repartido (servidores, licencias, mantenimiento, atención del equipo) y no aparece como una cifra única y visible. Cómo detectarlo: cuando preguntas "¿cuánto nos cuesta tener el legacy prendido?", nadie tiene el número; el costo es real pero invisible, así que no genera presión. Cómo corregirlo: haz visible el costo —un dashboard, cada sprint, con lo que cuesta mantener el legacy vivo (operación, el equipo dividido, la complejidad de dos sistemas)—. Cuando el costo de "ya casi" se acumula a la vista de todos (como el 120-y-contando del ejemplo), la presión de cerrarlo aparece sola. Lo que no se mide no se siente, y lo que no se siente se queda en 98.5% para siempre; el costo visible es lo que convierte la migración eterna de un problema abstracto en una cifra que duele cada mes.

Ejercicios

Ejercicio 1 — Lee los dos costos. En la salida, la migración A pagó 120 al sprint 12 y la B pagó 70. (a) ¿Por qué son iguales hasta el sprint 6 y difieren después? (b) ¿Cuánto pagaría cada una al sprint 20? (c) ¿Qué representa que el costo de B se "congele" y el de A no?

Ver solución

(a) Son iguales hasta el sprint 6 porque las dos migraciones tienen exactamente la misma velocidad en esa parte (bajan 1000→700→400→150→40→15), así que las dos pagan el costo de mantener los dos sistemas vivos los mismos 6 sprints: 60 cada una al sprint 6. Difieren a partir del sprint 7 porque ahí B llega a 0 y borra el legacy (deja de pagar), mientras A se atora en 15 (sigue pagando). La diferencia no está en la parte técnica (idéntica), sino en si se cierra el último tramo.

(b) Al sprint 20: B seguiría en 70 —borró el legacy en el sprint 7 y no ha pagado un peso más desde entonces—. A estaría en 200 —pagó 10 por sprint durante los 20 sprints, y sigue—. La brecha, que en el sprint 12 era 50 (120 vs 70), en el sprint 20 sería 130 (200 vs 70), y crece sin límite.

(c) Que B terminó y A no. El costo de B se congela porque borró el legacy: dejó de mantener dos sistemas, cobró el premio, el gasto se detuvo. El costo de A no se congela porque nunca terminó: sigue manteniendo dos sistemas indefinidamente, pagando las "dos rentas" para siempre. Un costo que se congela es la firma de una migración cerrada; un costo que sube sin parar es la firma de la migración eterna —el goteo constante de "ya casi" que se acumula sin fin—.

Ejercicio 2 — Por qué el último tramo se atora. El texto dice que "los incentivos se invierten en el último tramo". (a) ¿Por qué el primer 90% se hace solo y el último 2% no? (b) Da un ejemplo concreto de un caso raro que quedaría en ese último tramo en el catalog de Mercado. (c) ¿Por qué "el legacy ya casi no se usa" es justamente lo que impide terminarlo?

Ver solución

(a) Porque al principio cada sprint corta mucho tráfico (200-300 llamadas): el progreso es visible, satisfactorio y urgente, así que se hace con energía. Al final, el último 2% son los casos más difíciles y menos visibles, y cerrarlos es mucho trabajo por muy poco burn-down (un sprint entero para pasar de 15 a 0). El esfuerzo alto, el progreso visible bajo y la urgencia mínima hacen que el último tramo pierda contra cualquier otra prioridad. El primer 90% tiene incentivos a favor; el último 2% los tiene en contra.

(b) Un ejemplo concreto: un proceso nocturno que genera el reporte mensual de inventario del catálogo, que corre a las 3 a.m. el último día del mes y todavía consulta las tablas del legacy. Nadie lo ve en el día a día, nadie recuerda que existe, y solo se ejercita una vez al mes —así que es de los últimos casos en descubrirse y de los más fáciles de posponer—. Mientras ese proceso siga tocando el legacy, legacy_calls no llega a cero, pero como es invisible el resto del mes, no genera urgencia. Otros ejemplos: un endpoint que solo usa un socio B2B, o un flujo de devoluciones que se dispara rara vez.

(c) Porque "ya casi no se usa" elimina la urgencia de terminarlo, que es lo único que empujaría a cerrar el último tramo. Si el legacy atendiera el 50% del tráfico, apagarlo sería urgente (medio negocio depende de él). Pero al 1.5%, el legacy "no molesta" —el 98.5% ya está en el nuevo—, así que siempre hay algo más importante que hacer, y el 1.5% se pospone indefinidamente. Es la paradoja de la migración eterna: cuanto más avanzaste, menos urgente se vuelve terminar, justo cuando estás más cerca de cobrar el premio. Por eso el último tramo necesita una fuerza externa (la forcing function): la urgencia natural ya se agotó.

Ejercicio 3 — Diseña la forcing function. La migración del catalog de Mercado lleva tres sprints atorada en legacy_calls = 15 (98.5%). Te piden desatorarla. (a) Propón dos forcing functions concretas. (b) ¿Cómo te asegurarías de no "forzar el cierre" apagando el legacy con casos aún dependientes? (c) ¿Qué habría prevenido este atoramiento desde el principio?

Ver solución

(a) Dos forcing functions concretas: (1) un deadline de done comprometido y visible —"el legacy del catalog se borra el sprint 10, fecha en el plan, con un dueño asignado"—, que crea la urgencia que el último tramo perdió; y (2) hacer visible el costo —un renglón en el dashboard del equipo, cada sprint, con lo que cuesta mantener el legacy vivo (operación + el equipo dividido), acumulándose a la vista de todos—, para que "ya casi" deje de sentirse gratis. Una tercera opción: tratar el done como gate del proyecto —no cerrar oficialmente la migración ni liberar al equipo hasta que el legacy esté borrado—, para que el 98.5% cuente como "no terminado" y el proyecto siga con su presión.

(b) Me aseguraría de que la forcing function empuje a completar el último tramo, no a saltarlo: antes del deadline, identificar exactamente qué son esas 15 llamadas (con la observabilidad: qué casos, qué flujos, qué clientes las generan) y migrar esos casos al modern, de modo que al llegar el deadline legacy_calls sea cero de verdad —porque los casos ya viven en el nuevo—, no cero por haber apagado el legacy dejándolos fallando. El done exige legacy_calls == 0 real; apagar el legacy con 15 casos aún dependientes no es terminar, es romper 15 flujos. La forcing function acelera el trabajo, no lo salta.

(c) Lo que habría prevenido el atoramiento desde el principio: (1) definir el done el día uno (lección 6), con el borrado como criterio, para que el 98.5% fuera visiblemente "no terminado"; (2) atacar el último tramo temprano —identificar los casos raros y difíciles en los primeros sprints, cuando había energía y urgencia, y migrar algunos entonces— en vez de dejarlos para el final; y (3) medir el progreso con el burn-down (lección 3), que habría mostrado la velocity cayendo a cero en el sprint 7 —la señal temprana del atoramiento— a tiempo de actuar, en vez de descubrir el problema tres sprints después. En una frase: tener el tablero de este módulo desde el principio. La migración eterna es lo que le pasa a quien no mide para terminar.

Resumen y siguiente paso

En esta lección enfrentaste el peor resultado de una migración: la migración eterna, que se atora en el último tramo y mantiene dos sistemas para siempre. Viste, con la remodelación que "ya casi" y los tres años entre escombros pagando dos rentas, que el costo de no terminar es un goteo que se acumula sin fin. Y lo ejecutaste: dos migraciones con la misma velocidad los primeros seis sprints, una sin criterio de done que se atoró en 98.5% y pagó 120-y-contando, y otra con una forcing function que llegó a cero, borró el legacy y congeló su costo en 70. Aprendiste por qué el último tramo no se cierra solo (los incentivos se invierten: mucho esfuerzo, poco progreso visible, cero urgencia), las forcing functions que lo cierran (deadline de done, costo visible, done como gate del proyecto, atacar el último tramo temprano), y que forzar el cierre es completar el último tramo, no apagar el legacy con casos aún dependientes.

Antes de avanzar deberías poder: reconocer la migración eterna por la forma de su burn-down y su costo acumulado; explicar por qué el último tramo se atora aunque la parte técnica esté resuelta; proponer forcing functions concretas para desatorar una migración; y argumentar por qué medir el progreso es lo que evita la migración eterna.

La lección 8 es el capstone: integrar los tres instrumentos del módulo —el burn-down, la fitness function con trinquete y el criterio de done— en un solo MigrationTracker, y correrlo como un diario de la migración del catalog de Mercado que sí termina. Vas a ver el tablero completo trabajar junto: el burn-down bajando, la fitness function atrapando a alguien que agrega código al legacy en el sprint 3, el trinquete apretando el baseline, y el criterio de done disparando el borrado al llegar a cero. Es todo el módulo en una sola simulación ejecutada, y el cierre de la guía.

Recursos

  • Martin Fowler, "StranglerFigApplication" (2004) — martinfowler.com/bliki/StranglerFigApplication.html. Fowler advierte que el peligro del strangler es quedarse a medias —dos sistemas coexistiendo—: la migración eterna es exactamente ese peligro, y terminar (retirar el legacy) es lo que lo evita. En inglés.
  • Sam Newman, Monolith to Microservices (O'Reilly, 2019), cap. 3 — Newman describe el riesgo de las migraciones que nunca terminan de descomponer el monolito y se quedan con lo peor de los dos mundos; la fuente del argumento de esta lección. En inglés.
  • Neal Ford, Rebecca Parsons y Patrick Kua, Building Evolutionary Architectures (O'Reilly, 2ª ed., 2022) — sobre medir y gobernar el estado de una arquitectura en evolución para que el cambio llegue a su fin en vez de quedarse indefinido; el marco para tener las forcing functions que cierran una migración. En inglés.
  • Martin Fowler, martinfowler.com — el bliki con las entradas sobre strangler fig y modernización que fundamentan por qué una migración debe terminar con el legacy retirado y no coexistir para siempre. En inglés.