Módulo 8: Proyecto — modernizar una rebanada de Mercado

Medir el progreso de la rebanada

Descripción

La rebanada ya está caracterizada (L2), tras el facade (L3), extraída con su ACL (L4) y con sus datos migrados (L5). El movimiento está en marcha. El quinto paso del método —el módulo 7 hecho acción— responde la pregunta que ninguna de esas técnicas contesta por sí sola: ¿esto avanza, y a qué ritmo? No agrega una técnica de movimiento nueva; le pone al movimiento un tablero de instrumentos, con dos medidas que trabajan juntas: el burn-down de llamadas al legacy, que dice cuánto falta, y la fitness function, que impide que el legacy crezca por detrás mientras lo reduces por el frente.

El burn-down es la métrica central: sprint a sprint, cuentas cuántas llamadas al legacy quedan (legacy_calls, que incluye el fallback del strangler) y las ves bajar hacia cero. Y aquí aparece un fenómeno que este capstone tenía anunciado desde la lección 3: el tramo terco final. El burn-down baja rápido mientras se migran los casos normales, pero se atasca en 100 —las compras por volumen, el descuento por volumen que el modern todavía no cubre y que caen al legacy por fallback—. Ese tramo no cierra solo: cierra únicamente cuando el modern implementa el bulk pricing, y —esto es lo que cierra el círculo con el primer paso— lo implementa guiado por el golden master que congelaste en la lección 2. Sin el golden master, el modern calcularía un "5% limpio" que difiere del legacy, el fallback nunca llegaría a cero, y el burn-down se quedaría atascado en 100 para siempre.

La segunda medida es la fitness function, que tapa el punto ciego del burn-down. El burn-down mide el legacy encogiendo por el frente (las llamadas que dejas de mandarle), pero no ve si alguien lo hace crecer por atrás (código nuevo construido sobre el monolito que estás matando). La fitness function cuenta las referencias del código al legacy y falla —rompe el CI— si crecieron respecto a un baseline. En este paso vas a ver las dos medidas en un solo tablero: el burn-down bajando con su tramo terco, y la fitness function atrapando, en un sprint, a alguien que agregó código al legacy.

Conexión con el módulo. Este es el quinto paso del método (M7), y usa dos artefactos de pasos anteriores: el fallback del strangler (L3) es lo que el burn-down mide hacia cero, y el golden master (L2) es lo que permite cerrar el tramo terco. Produce, para el paso siguiente, dos de las condiciones de done: legacy_calls == 0 (el burn-down en cero) y legacy_refs == 0 (la fitness en cero). Fíjate en la frontera: aquí medimos el movimiento que las lecciones 3 a 5 ejecutaron; no movemos nada nuevo. Y la fitness function como concepto —qué es, cómo se integra en una arquitectura evolutiva— es de architecture-decisions-and-tradeoffs; aquí la aplicamos a un fin concreto: que el legacy del catalog no crezca mientras lo estrangulas.

Una analogía: la barra de descarga que se atasca en 99%

Todos conocemos la barra de progreso de una descarga que baja rápido —10%, 40%, 70%, 90%— y luego se atasca en 99% un tiempo que se siente eterno. No es que la barra mienta: es que el último 1% suele ser lo más difícil (verificar el archivo, escribir el último bloque, cerrar la conexión), mientras que el primer 90% fue lo fácil (bajar el grueso de los datos). La barra que llega a 99% rápido y ahí se queda te enseña algo real: el progreso visible no es uniforme, y el último tramo es de otra naturaleza que el primero.

Ahora imagina que esa barra tuviera, al lado, una segunda luz que vigila algo distinto: que nadie esté agregando archivos a la descarga mientras baja. Sería absurdo que, mientras la barra avanza hacia el 100%, alguien metiera archivos nuevos a la cola por detrás —la barra bajaría por el frente mientras el total crece por atrás, y nunca terminarías—. Esa segunda luz se pondría roja si el total de archivos por descargar creciera, y no dejaría continuar hasta quitarlos.

El burn-down de la migración es la barra de descarga: baja rápido con los casos normales y se atasca en el tramo terco (el bulk pricing), que es de otra naturaleza —los casos raros, los que requieren reproducir la rareza exacta—. Y la fitness function es la segunda luz: vigila que nadie agregue código al legacy por detrás mientras el burn-down baja por el frente, y se pone roja (FAIL) si el total de referencias al legacy crece. La barra dice cuánto falta; la luz impide que el trabajo crezca mientras avanzas. Las dos juntas son lo que hace que la descarga —la migración— de verdad llegue al 100%.

Ejemplo trabajado: el burn-down con tramo terco y la fitness que atrapa

Vamos a ejecutar el tablero en dos partes. La parte 1 corre el burn-down sprint a sprint con su fitness function: el legacy_calls baja de 1000, se atasca en 100 (el bulk que el modern no cubre), y la fitness function atrapa en el sprint 4 a alguien que agregó código al legacy. La parte 2 muestra por qué el tramo terco cierra en el sprint 5: el modern implementa el bulk pricing, y solo la implementación guiada por el golden master (que reproduce el truncado raro) cuadra con los números congelados en la lección 2 —con ella, el fallback llega a cero y el burn-down termina—.

# Paso 5 del metodo: medir el progreso de la rebanada. El burn-down de llamadas al
# legacy baja hasta 0 -con el tramo terco final (el bulk) que solo cierra cuando el
# modern lo implementa GUIADO por el golden master de la leccion 2- y la fitness
# function da FAIL si alguien agrega codigo nuevo al legacy.

import math

TAX_RATE = 0.16
BULK_MIN_QTY = 10
BULK_DISCOUNT = 0.05


# --- PARTE 1: el burn-down con su fitness function, sprint a sprint. ---
# legacy_calls incluye el fallback: el tramo final (100) son las compras por
# volumen que el modern aun no cubre, atascadas hasta que implemente el bulk.
sprints = [
    # sprint, legacy_calls, legacy_refs, nota
    (0, 1000, 4, "arranque"),
    (1,  600, 3, "migran casos normales"),
    (2,  300, 2, "migran casos normales"),
    (3,  100, 2, "solo queda el bulk (fallback)"),
    (4,  100, 3, "bulk atascado + alguien agrego codigo al legacy"),
    (5,    0, 0, "modern implementa el bulk (guiado por golden master)"),
]

baseline = 4          # el trinquete de la fitness function
prev_calls = None

print("Burn-down de llamadas al legacy + fitness function (catalog de Mercado)\n")
print(f"{'spr':>3}{'legacy_calls':>14}{'vel':>6}{'refs':>6}{'base':>6}{'fitness':>9}   nota")
print("-" * 92)
for sprint, legacy_calls, legacy_refs, nota in sprints:
    vel = 0 if prev_calls is None else prev_calls - legacy_calls
    fit_ok = legacy_refs <= baseline
    if fit_ok and legacy_refs < baseline:
        baseline = legacy_refs                 # trinquete: solo baja
    fit = "PASS" if fit_ok else "FAIL"
    tag = "" if fit_ok else "  <- CI ROJO: revertir"
    print(f"{sprint:>3}{legacy_calls:>14}{vel:>6}{legacy_refs:>6}{baseline:>6}"
          f"{fit:>9}   {nota}{tag}")
    prev_calls = legacy_calls
print("-" * 92)
print("  El burn-down se atasca en 100 (el bulk) hasta el sprint 5; la fitness atrapo")
print("  en el sprint 4 el codigo agregado al legacy (refs 3 > baseline 2 -> FAIL).\n")


# --- PARTE 2: por que el tramo terco cierra. El modern implementa el bulk GUIADO
#     por el golden master; solo si reproduce el numero exacto, el fallback -> 0. ---
def legacy_price(unit_price_cents, quantity):
    subtotal = unit_price_cents * quantity
    if quantity >= BULK_MIN_QTY:                     # rareza: trunca a la decena de centavo
        subtotal = math.floor(subtotal * (1 - BULK_DISCOUNT) / 10) * 10
    return math.floor(subtotal * (1 + TAX_RATE))


def modern_bulk_naive(unit_price_cents, quantity):
    subtotal = unit_price_cents * quantity
    if quantity >= BULK_MIN_QTY:                     # "5% limpio", SIN la rareza del truncado
        subtotal = round(subtotal * (1 - BULK_DISCOUNT))
    return math.floor(subtotal * (1 + TAX_RATE))


def modern_bulk_guided(unit_price_cents, quantity):
    subtotal = unit_price_cents * quantity
    if quantity >= BULK_MIN_QTY:                     # reproduce el truncado del golden master
        subtotal = math.floor(subtotal * (1 - BULK_DISCOUNT) / 10) * 10
    return math.floor(subtotal * (1 + TAX_RATE))


# El golden master de los casos de volumen (de la leccion 2).
bulk_cases = [("usb-hub", 3499, 10), ("kbd-mech", 7333, 12), ("cable-hdmi", 1299, 25)]
golden = {sku: legacy_price(p, q) for sku, p, q in bulk_cases}


def matches_golden(impl):
    return all(impl(p, q) == golden[sku] for sku, p, q in bulk_cases)


print("Por que el tramo terco cierra: el bulk del modern contra el golden master")
print(f"  {'sku':<11}{'golden':>8}{'naive':>8}{'guided':>8}")
print("  " + "-" * 35)
for sku, p, q in bulk_cases:
    print(f"  {sku:<11}{golden[sku]:>8}{modern_bulk_naive(p, q):>8}{modern_bulk_guided(p, q):>8}")
print("  " + "-" * 35)
print(f"  modern 'naive' (5% limpio) cuadra con el golden master: {matches_golden(modern_bulk_naive)}")
print(f"  modern 'guided' (truncado del golden master) cuadra:     {matches_golden(modern_bulk_guided)}")
print("\n  Solo la implementacion GUIADA por el golden master reproduce el numero")
print("  exacto (rareza incluida). Con ella el fallback llega a 0 y el burn-down")
print("  cierra: el golden master de la leccion 2 es lo que permite terminar.")

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

Burn-down de llamadas al legacy + fitness function (catalog de Mercado)

spr  legacy_calls   vel  refs  base  fitness   nota
--------------------------------------------------------------------------------------------
  0          1000     0     4     4     PASS   arranque
  1           600   400     3     3     PASS   migran casos normales
  2           300   300     2     2     PASS   migran casos normales
  3           100   200     2     2     PASS   solo queda el bulk (fallback)
  4           100     0     3     2     FAIL   bulk atascado + alguien agrego codigo al legacy  <- CI ROJO: revertir
  5             0   100     0     0     PASS   modern implementa el bulk (guiado por golden master)
--------------------------------------------------------------------------------------------
  El burn-down se atasca en 100 (el bulk) hasta el sprint 5; la fitness atrapo
  en el sprint 4 el codigo agregado al legacy (refs 3 > baseline 2 -> FAIL).

Por que el tramo terco cierra: el bulk del modern contra el golden master
  sku          golden   naive  guided
  -----------------------------------
  usb-hub       38558   38558   38558
  kbd-mech      96964   96971   96964
  cable-hdmi    35786   35787   35786
  -----------------------------------
  modern 'naive' (5% limpio) cuadra con el golden master: False
  modern 'guided' (truncado del golden master) cuadra:     True

  Solo la implementacion GUIADA por el golden master reproduce el numero
  exacto (rareza incluida). Con ella el fallback llega a 0 y el burn-down
  cierra: el golden master de la leccion 2 es lo que permite terminar.

Lee el tablero en sus dos partes, porque juntas explican no solo cuánto falta sino por qué el final es difícil y qué lo hace posible.

El burn-down y su tramo terco. La columna legacy_calls baja rápido al principio: 1000 → 600 → 300, con la velocity (vel) marcando 400, 300 —los casos normales se migran a buen ritmo—. Pero en el sprint 3 llega a 100 y ahí se atasca: la velocity cae a 0 en el sprint 4, y el legacy_calls se queda en 100. Ese 100 son las compras por volumen —el bulk pricing que el modern todavía no cubre y que caen al legacy por fallback—. Es exactamente el tramo terco que la lección 3 anticipó cuando el fallback se quedó en 100 a traffic_percent = 100. El burn-down no se atasca por falta de trabajo, sino porque lo que queda es de otra naturaleza: el caso raro que requiere reproducir la rareza exacta.

La fitness function que atrapa. Mira el sprint 4. El burn-down está atascado (100, velocity 0), y además pasa algo peor: las referencias al legacy (refs) suben de 2 a 3 —alguien agregó código nuevo al legacy—. La fitness function evalúa 3 <= 2 (el baseline que el trinquete había apretado a 2): FAIL, y el tablero marca <- CI ROJO: revertir. Sin la fitness function, el burn-down habría seguido mostrando 100 (sin cambio) mientras el legacy crecía por atrás, y nadie lo habría notado. La fitness es la segunda luz de la barra de descarga: atrapó el archivo que alguien metió a la cola por detrás.

El cierre del tramo terco. En el sprint 5, el legacy_calls cae de 100 a 0 (velocity 100): el tramo terco cerró. ¿Cómo? La nota lo dice: el modern implementó el bulk pricing, guiado por el golden master. Y la parte 2 muestra por qué eso importa tanto. Compara dos implementaciones del bulk en el modern contra el golden master de la lección 2:

  • La naive aplica un "5% limpio" con redondeo normal. Sobre los casos de volumen, da 38558, 96971, 35787 —y el golden master dice 38558, 96964, 35786—. No cuadra (False): difiere en kbd-mech (por 7 centavos) y cable-hdmi (por 1 centavo). Si el modern usara esta implementación, sus precios de volumen serían distintos de los del legacy, el fallback los detectaría como fallos, y el burn-down nunca cerraría.
  • La guided reproduce el truncado a la decena de centavo —la rareza que el golden master congeló—. Sobre los mismos casos da 38558, 96964, 35786cuadra (True) con el golden master, centavo por centavo—. Con esta implementación, el modern calcula el bulk idéntico al legacy, el fallback llega a cero, y el burn-down cierra.

Aquí se cierra la costura más importante del método: el golden master del paso 1 es lo que permite terminar el paso 5. El tramo terco del burn-down no cierra con cualquier implementación del bulk; cierra solo con la que reproduce el comportamiento exacto del legacy —rareza incluida—, y esa fidelidad la garantiza el golden master que congelaste antes de tocar nada. La red que pusiste al principio es, literalmente, lo que te deja llegar al final.

Profundización: la tendencia importa más que el valor, y por qué el tramo terco no cierra solo

Dos ideas de este paso merecen desarrollarse: cómo leer el burn-down, y por qué el último tramo necesita una fuerza.

Lee la tendencia, no el valor. Un burn-down se lee como una película (la velocity entre sprints), no como una foto (el valor de un sprint aislado). El valor legacy_calls = 100 no te dice, por sí solo, si vas a terminar: ¿100 es bueno o malo? Depende de la velocity. En los sprints 1-3, la velocity fue 400, 300, 200 —bajando pareja, la migración avanza—. En el sprint 4, la velocity cayó a 0 con legacy_calls todavía en 100 —esa es la firma del atascamiento—. La velocity cayendo mientras el burn-down sigue lejos de cero es la alarma temprana: te dice que el tramo terco llegó y que no cerrará solo. Leer eso a tiempo (en el sprint 4) es lo que te permite actuar —priorizar el bulk pricing— en vez de descubrir meses después que la migración lleva un año atascada en 100.

   legacy_calls
   1000 |#
        |##
        |####
    300 |######
    100 |#########_______   <- el tramo terco: velocity -> 0, atascado en el bulk
      0 |                #   <- cierra cuando el modern implementa el bulk
        +----------------------------------
         s1   s2   s3   s4   s5
              (baja pareja)  (atasco)  (cierre)

El tramo terco no cierra solo. El burn-down se atasca en el bulk por una razón estructural: los incentivos se invierten en el último tramo. Al principio, cada sprint corta mucho tráfico (los casos normales, fáciles y numerosos), el progreso es visible y motivador. Al final, lo que queda es el caso raro y difícil —el bulk con su rareza— que requiere mucho trabajo por poco burn-down visible (un sprint entero para pasar de 100 a 0). Y como el legacy "ya casi no se usa" (el 90% ya está en el modern), la urgencia de cerrar el último 10% es mínima. El tramo terco necesita una fuerza externa para cerrarse: aquí, la decisión explícita de implementar el bulk pricing guiado por el golden master en el sprint 5, en vez de dejarlo "para después". Sin esa decisión, el burn-down se queda en 100 —el patrón de la migración eterna que la lección 7 del módulo 7 disecó, y que el done de la lección siguiente existe para prevenir—.

Una precisión sobre la fitness function: su trinquete (el baseline que solo baja) es lo que hace irreversible el progreso por atrás. En el tablero, el baseline bajó 4 → 3 → 2 y se quedó ahí; cuando las referencias intentaron subir a 3 en el sprint 4, la fitness falló porque el baseline ya estaba en 2. Sin el trinquete (con un baseline fijo en 4), ese sprint 4 habría dado PASS (3 <= 4), y el legacy habría recuperado terreno en silencio. El trinquete convierte cada reducción de referencias en un piso que no se puede deshacer —el complemento perfecto del burn-down, que asegura que lo ganado por el frente no se pierda por atrás—.

Errores comunes

Leer el valor de un sprint en vez de la tendencia. Qué pasa: el equipo mira legacy_calls = 100 y discute si eso es bueno o malo, sin mirar la velocity ni de dónde venía. Por qué pasa: el valor de hoy es el número más visible; la tendencia requiere comparar sprints. Cómo detectarlo: nadie puede decir si la migración va a terminar ni cuándo; las conversaciones giran en torno al número actual ("estamos en 100") y no a la pendiente. Cómo corregirlo: mide y reporta la velocity (cuántas legacy_calls se cortaron respecto al sprint anterior). El valor 100 con velocity 200 (bajando) significa "terminas pronto"; el mismo 100 con velocity 0 (atascado) significa "te trabaste". Solo la tendencia los distingue. La velocity cayendo mientras el burn-down sigue lejos de cero es la señal temprana del atascamiento —actúa ahí, no meses después—.

Implementar el tramo terco sin el golden master, "limpiando" la rareza. Qué pasa: para cerrar el bulk, el equipo lo implementa "bien" (un 5% limpio con redondeo normal) en vez de reproducir el truncado raro del legacy. Por qué pasa: la rareza se ve como un bug, y limpiar código feo se siente correcto. Cómo detectarlo: el modern calcula el bulk con precios ligeramente distintos a los del legacy; el fallback no llega a cero (las compras por volumen siguen difiriendo) o —peor— el modern sirve precios que rompen la conciliación de un socio. Cómo corregirlo: implementa el tramo terco guiado por el golden master, reproduciendo el número exacto (rareza incluida). El golden master de la lección 2 es la especificación de qué debe calcular el modern para cerrar el fallback; sin él, la implementación "limpia" difiere y el burn-down no cierra. Terminar la migración exige reproducir el comportamiento, no mejorarlo (mejorarlo, si se quiere, es un cambio deliberado posterior, con el golden master regrabado a conciencia).

Confiar en el burn-down y no vigilar el crecimiento por atrás. Qué pasa: el equipo sigue el burn-down bajando y no pone una fitness function, así que no ve cuando alguien agrega código nuevo al legacy. Por qué pasa: el burn-down es la métrica satisfactoria y visible; la fitness function es un instrumento extra que "no parece urgente". Cómo detectarlo: el burn-down baja (o se mantiene) por el frente mientras las referencias al legacy crecen por atrás sin que nada se ponga rojo; la migración se alarga sin explicación. Cómo corregirlo: pon la fitness function con trinquete junto al burn-down —cuenta las referencias al legacy y falla si crecen sobre el baseline (que solo baja)—. El burn-down mide el avance; la fitness protege ese avance. En el ejemplo, sin la fitness, el código agregado en el sprint 4 habría pasado inadvertido; con ella, el CI se puso rojo y forzó la reversión. Las dos medidas son complementarias: una sola deja un flanco abierto.

Ejercicios

Ejercicio 1 — El tramo terco. El burn-down se atascó en 100 en los sprints 3 y 4. (a) ¿Qué representan esas 100 llamadas? (b) ¿Por qué no bajaron solas? (c) ¿Qué las llevó a cero en el sprint 5, y qué artefacto de una lección anterior lo hizo posible?

Ver solución

(a) Esas 100 llamadas son las compras por volumen —el bulk pricing que el modern todavía no implementa—, que caen al legacy por fallback. Es exactamente el tramo que la lección 3 anticipó cuando el fallback se quedó en 100 a traffic_percent = 100: el traffic_percent estaba en 100, pero el modern no cubría el bulk, así que 100 requests seguían atendidas por el legacy.

(b) No bajaron solas porque el tramo terco es de otra naturaleza que el primer 90%: son el caso raro y difícil (el bulk con su rareza), requieren mucho trabajo por poco burn-down visible, y como el legacy "ya casi no se usa", la urgencia de cerrarlo es mínima. Los incentivos se invierten en el último tramo, así que necesita una fuerza externa —una decisión explícita de atacarlo— para cerrarse. Sin esa decisión, se quedaría en 100 (la migración eterna).

(c) Lo llevó a cero la implementación del bulk pricing en el modern, guiada por el golden master de la lección 2. El artefacto clave es el golden master: solo una implementación que reproduce el número exacto del legacy (el truncado raro incluido) hace que el fallback llegue a cero. La implementación "naive" (5% limpio) no cuadra con el golden master y el burn-down no cerraría; la "guided" (con el truncado) sí cuadra, y por eso cierra. La red del paso 1 es lo que permite terminar el paso 5.

Ejercicio 2 — Naive vs guided. La parte 2 comparó dos implementaciones del bulk contra el golden master. (a) ¿Por qué la naive no cuadra? (b) ¿Por qué usb-hub cuadra en las dos? (c) ¿Qué pasaría con el fallback si el modern se quedara con la naive?

Ver solución

(a) La naive aplica un "5% limpio" con redondeo normal (round), mientras el legacy trunca el subtotal a la decena de centavo más baja. Esa diferencia de redondeo produce números distintos en los casos donde los centavos caen de forma que el truncado y el redondeo difieren: kbd-mech (96971 vs golden 96964) y cable-hdmi (35787 vs golden 35786). Como difiere del golden master, no reproduce el comportamiento del legacy.

(b) usb-hub cuadra en las dos (38558) porque, para ese caso, el truncado y el redondeo normal dieron el mismo número —los centavos cayeron de forma que ambos métodos coinciden—. Es el mismo detalle que la lección 2 mostró: la rareza no se manifiesta en todos los casos de volumen, solo en algunos. Por eso probar solo usb-hub daría un falso verde; se necesitan varios casos de volumen (como kbd-mech y cable-hdmi) para revelar la diferencia.

(c) Si el modern se quedara con la naive, sus precios de volumen serían distintos de los del legacy (por unos centavos en varios casos). El parallel-run/fallback lo detectaría como una discrepancia: las compras por volumen del modern no coincidirían con lo que el legacy calculaba, así que o el fallback las seguiría mandando al legacy (y el burn-down no cerraría), o —si se apagara el legacy— el modern serviría precios distintos, rompiendo la conciliación del socio de lealtad. El fallback nunca llegaría a cero limpiamente. Solo la guided (que reproduce el golden master) hace que el modern calcule idéntico y el fallback cierre.

Ejercicio 3 — Las dos medidas juntas. El tablero corre el burn-down y la fitness function a la vez. (a) ¿Qué mide cada una, y por qué una sola no basta? (b) ¿Qué habría pasado en el sprint 4 sin la fitness function? (c) ¿Qué papel juega el trinquete del baseline?

Ver solución

(a) El burn-down mide el legacy encogiendo por el frente —cuántas llamadas al legacy quedan, bajando hacia cero (cuánto falta)—. La fitness function vigila que el legacy no crezca por atrás —cuenta las referencias del código al legacy y falla si crecen (protege el avance)—. Una sola no basta: el burn-down no ve si alguien agrega código nuevo al legacy (bajaría por el frente mientras el legacy crece por detrás); la fitness no dice cuánto falta. Juntas cubren los dos flancos: cuánto falta y que no retroceda.

(b) Sin la fitness function, el sprint 4 habría pasado inadvertido: el burn-down mostraría 100 (sin cambio, atascado), pero las referencias al legacy habrían subido de 2 a 3 —alguien agregó código nuevo al legacy— sin que nada se pusiera rojo. El legacy habría crecido por atrás mientras el burn-down parecía solo "estancado", y el crecimiento silencioso habría alargado la migración. La fitness function es lo que convirtió ese sprint en un CI rojo que forzó la reversión.

(c) El trinquete (el baseline que solo baja) hace irreversible el progreso por atrás. El baseline bajó 4 → 3 → 2 siguiendo las referencias, y se quedó en 2. Cuando en el sprint 4 las referencias intentaron subir a 3, la fitness falló porque 3 > 2 (el baseline apretado). Sin el trinquete (baseline fijo en 4), ese 3 <= 4 habría dado PASS y el legacy habría recuperado terreno en silencio. El trinquete convierte cada reducción de referencias en un piso que no se puede deshacer: lo ganado por el frente no se pierde por atrás.

Resumen y siguiente paso

En esta lección diste el quinto paso del método: medir el progreso de la rebanada (módulo 7). Ejecutaste el tablero de dos medidas complementarias: el burn-down de llamadas al legacy bajando de 1000 hacia cero —con su tramo terco atascado en 100 (el bulk pricing que el modern no cubría, el fallback de la lección 3)— y la fitness function con trinquete que atrapó, en el sprint 4, a alguien que agregó código al legacy (CI rojo, revertir). Viste, con la barra de descarga que se atasca en 99% y su segunda luz que vigila la cola, que el burn-down dice cuánto falta y la fitness impide que el legacy crezca por atrás. Y cerraste la costura central del método: el tramo terco solo cierra cuando el modern implementa el bulk guiado por el golden master de la lección 2 —la implementación "naive" (5% limpio) no cuadra con los números congelados, la "guided" (con el truncado raro) sí, y solo con ella el fallback llega a cero—. La red del primer paso es lo que permite terminar el quinto.

Antes de avanzar deberías poder: leer un burn-down por su tendencia (velocity) y no por el valor de un sprint; reconocer el tramo terco y por qué no cierra solo; explicar por qué el golden master es lo que permite cerrarlo; y argumentar por qué el burn-down y la fitness function son complementarios.

La lección 7 da el sexto y último paso del método: declarar el done y apagar el legacy (módulo 7). Con el burn-down en cero y la fitness en verde, parece que terminaste —pero "parece" no basta—. Vas a ejecutar el criterio de done como una lista de condiciones que todas deben cumplirse (llamadas al legacy en 0, fallback en 0, discrepancias en 0, referencias al legacy en 0, characterization en verde), que autoriza a borrar el legacy —no a dejarlo "apagado por si acaso"—. Y vas a cerrar con la justificación medida: por qué modernizar por rebanadas venció a la reescritura, sobre lo que las lecciones midieron de verdad.

Recursos

  • Sam Newman, Monolith to Microservices (O'Reilly, 2019), cap. 3 — sobre medir el avance de la descomposición por la funcionalidad y las llamadas que se retiran del monolito; el burn-down de este paso es esa idea hecha gráfica, con su tramo terco final. 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; aquí aplicamos una a que el legacy del catalog no crezca durante la migración. 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: lo que el burn-down mide, con el tramo terco como su desafío final. En inglés.
  • Martin Fowler, "Patterns of Legacy Displacement" — martinfowler.com/articles/patterns-legacy-displacement. Comparar la salida del nuevo contra el golden master del viejo; el parallel run como patrón nombrado es de Sam Newman (Monolith to Microservices). El mecanismo con que se verifica que el bulk del modern reproduce el número exacto antes de cerrar el fallback. En inglés.