Módulo 7: Medir el progreso de la migración
Medir el resultado, no el esfuerzo
Descripción
Antes de construir cualquier tablero, hay una decisión que lo determina todo: qué medir. Y es más traicionera de lo que parece, porque hay dos familias de métricas que se ven igual de "de progreso" pero dicen cosas opuestas. Están las métricas de esfuerzo —cuántos story points quemó el equipo, cuántas horas metió, cuántas tareas cerró, cuántos PRs mergeó— y están las métricas de resultado —cuántas llamadas al legacy se cortaron de verdad, qué porcentaje del tráfico ya vive en la ruta nueva, cuántos endpoints y tablas salieron del monolito—. Esta lección muestra, ejecutada, por qué solo las de resultado dicen la verdad sobre una migración, y por qué las de esfuerzo casi siempre corren por delante y te hacen creer que estás casi terminando cuando apenas vas a la cuarta parte.
La diferencia no es filosófica; es medible. El esfuerzo mide lo que el equipo hizo; el resultado mide lo que le pasó al legacy. Y esos dos números se separan, a veces dramáticamente, porque una parte enorme del trabajo de una migración —montar la infraestructura, escribir los characterization tests, construir el anti-corruption layer, preparar el strangler facade— quema muchísimo esfuerzo sin cortar todavía una sola llamada al legacy. Ese trabajo es necesario, pero no es resultado: el legacy sigue atendiendo exactamente el mismo tráfico que antes. Si mides el esfuerzo, tu barra de progreso salta al 60% mientras el legacy no se movió ni un milímetro. Si mides el resultado, la barra dice la verdad incómoda: 0%, porque nada se ha cortado aún.
Conexión con el módulo. Esta es la lección que fija qué medir, y por eso va antes que todas las que construyen métricas. La lección 3 construye el burn-down —una métrica de resultado por excelencia (legacy_calls)—; la 4 y la 5 construyen la fitness function —que también mide un resultado: si el legacy creció—; y la 6 define el done sobre resultados (legacy_calls == 0, tablas cortadas), nunca sobre esfuerzo ("le metimos 2000 horas"). Todo el resto del módulo se apoya en la decisión de esta lección. Fíjate en la frontera: aquí no discutimos cómo estimar story points ni cómo planear sprints —eso es gestión de proyectos—; discutimos, dentro de una migración, cuál de las dos familias de métricas refleja el avance real hacia apagar el legacy. El esfuerzo tiene su lugar (planear, dimensionar el trabajo restante); pero como señal de progreso de la migración, engaña.
Una analogía: medir un viaje por horas al volante o por kilómetros que faltan
Imagina que manejas de una ciudad a otra, 600 kilómetros, y quieres saber cuánto avanzaste. Tienes dos formas de medirlo. La primera: cuántas horas llevas al volante. Llevas seis horas manejando, estás cansado, hiciste un esfuerzo enorme —seis horas es mucho—. La segunda: cuántos kilómetros te faltan para llegar. Te faltan 500. ¿Avanzaste? Según las horas, muchísimo: seis horas de trabajo duro. Según los kilómetros, casi nada: recorriste 100 de 600.
¿Cómo puede ser? Porque las horas al volante miden tu esfuerzo, no tu avance hacia el destino. Quizás te perdiste y diste vueltas. Quizás había tráfico y avanzaste a cinco kilómetros por hora. Quizás pasaste dos horas buscando estacionamiento en un pueblo. Todo eso son horas —esfuerzo real, cansancio real— que no te acercaron a tu destino. Las horas suben aunque no llegues; los kilómetros que faltan solo bajan cuando de verdad te acercas.
Una migración es idéntica. Los story points son las horas al volante: miden cuánto trabajó el equipo, y suben con cada sprint aunque el legacy no se haya movido. Las llamadas al legacy que quedan son los kilómetros que faltan: solo bajan cuando de verdad cortaste una dependencia del monolito. Si tu reporte de progreso habla de puntos quemados, estás midiendo horas al volante —puedes reportar "80% del esfuerzo" mientras el legacy sigue atendiendo al 80% del tráfico—. Si tu reporte habla de llamadas al legacy cortadas, estás midiendo los kilómetros que faltan: la única cifra que baja cuando de verdad llegas.
Ejemplo trabajado: la misma migración medida de las dos formas
Vamos a tomar una sola migración —la del catalog de Mercado— y medirla con las dos familias de métricas al mismo tiempo, sprint a sprint. Del lado del esfuerzo: cuántos story points acumuló el equipo, sobre 100 planeados, y su effort_percent. Del lado del resultado: cuántas legacy_calls quedan sobre la muestra fija de 1000 requests, y su outcome_percent (el tráfico que ya no toca el legacy). Y una tercera columna, la brecha: cuánto va el esfuerzo por delante del resultado. Los datos están fijos para que el patrón sea nítido y reproducible.
# Medir ESFUERZO vs medir RESULTADO. La misma migracion, dos metricas.
# El esfuerzo (story points quemados) corre por delante y engania; el resultado
# (llamadas al legacy que de verdad se cortaron) dice la verdad.
SAMPLE = 1000 # requests del catalog medidas por sprint
POINTS_TOTAL = 100 # story points planeados para la migracion
# Por sprint: (puntos acumulados, llamadas al legacy que quedan)
sprints = [
(10, 1000),
(35, 980),
(60, 900),
(80, 760),
(92, 400),
(100, 0),
]
def effort_percent(points_done):
return points_done / POINTS_TOTAL * 100
def outcome_percent(legacy_calls):
# el resultado real: que fraccion del trafico YA no toca el legacy
return (SAMPLE - legacy_calls) / SAMPLE * 100
print("Esfuerzo vs resultado: dos formas de medir la MISMA migracion\n")
print(f"{'sprint':>7}{'points':>8}{'effort%':>9}{'legacy_calls':>14}"
f"{'outcome%':>10}{'brecha':>9}")
print("-" * 57)
for i, (points_done, legacy_calls) in enumerate(sprints):
ep = effort_percent(points_done)
op = outcome_percent(legacy_calls)
gap = ep - op
print(f"{i:>7}{points_done:>8}{ep:>8.0f}%{legacy_calls:>14}"
f"{op:>9.0f}%{gap:>8.0f}")
print("-" * 57)
print("\n Sprint 3: el esfuerzo dice 80% hecho; el resultado dice 24%.")
print(" Quien reporta effort% canta victoria; outcome% (legacy_calls -> 0)")
print(" es el unico que no miente. Solo cuando ambos llegan a 100 se acabo.")
Qué esperar. Al correr el archivo, la salida es exactamente esta:
Esfuerzo vs resultado: dos formas de medir la MISMA migracion
sprint points effort% legacy_calls outcome% brecha
---------------------------------------------------------
0 10 10% 1000 0% 10
1 35 35% 980 2% 33
2 60 60% 900 10% 50
3 80 80% 760 24% 56
4 92 92% 400 60% 32
5 100 100% 0 100% 0
---------------------------------------------------------
Sprint 3: el esfuerzo dice 80% hecho; el resultado dice 24%.
Quien reporta effort% canta victoria; outcome% (legacy_calls -> 0)
es el unico que no miente. Solo cuando ambos llegan a 100 se acabo.
Lee las dos columnas de porcentaje en paralelo, renglón por renglón, y mira la brecha entre ellas.
En el sprint 0, el equipo ya quemó 10 puntos (10% del esfuerzo) pero el outcome_percent es 0%: las 1000 llamadas siguen en el legacy. Ese 10% de esfuerzo se fue en montar la infraestructura de la migración —el strangler facade, la instrumentación— sin cortar nada todavía. La brecha ya es de 10.
En los sprints 1 y 2, la brecha se abre: el esfuerzo va en 35% y 60%, pero el resultado apenas en 2% y 10%. El equipo trabajó muchísimo —escribió characterization tests, construyó el anti-corruption layer, preparó la migración de datos—, todo trabajo real y necesario, pero el legacy sigue atendiendo 980 y 900 de las 1000 requests. Si alguien reportara solo el effort_percent, diría "vamos al 60%, más de la mitad"; el outcome_percent dice la verdad: 10%, apenas empezando a cortar.
El sprint 3 es el momento de la mentira máxima: effort_percent = 80%, outcome_percent = 24%, brecha de 56. Ochenta por ciento del esfuerzo quemado, y solo la cuarta parte del tráfico cortado del legacy. Imagina el reporte a dirección: "estamos al 80%". Todos entienden "casi terminamos, falta un empujón". Pero el legacy todavía atiende 760 de las 1000 requests —el 76% del tráfico sigue en el sistema que supuestamente estás apagando—. La barra de esfuerzo está en 80%; la migración real está en 24%. Quien confía en el esfuerzo va a prometer un final que no llegará cuando lo prometió.
En los sprints 4 y 5, el resultado por fin acelera y alcanza al esfuerzo: la brecha se cierra de 56 a 32 a 0. Aquí es donde el trabajo de infraestructura de los primeros sprints "se cobra": con los tests, el ACL y el strangler ya construidos, cortar llamadas al legacy se vuelve rápido, y el outcome_percent sube de 24% a 60% a 100%. Al final, ambos llegan a 100 —y solo entonces la migración terminó de verdad—.
La forma de la brecha cuenta la historia: el esfuerzo sube parejo y temprano; el resultado se queda atrás al principio (todo el trabajo de preparación no corta nada) y luego acelera. Si mides el esfuerzo, tu barra miente en el peor momento —a mitad del proyecto, justo cuando alguien pregunta "¿cuándo terminan?"—. Si mides el resultado, tu barra es más lenta y más honesta: nunca te dice "casi" hasta que de verdad casi.
Profundización: por qué el esfuerzo siempre corre por delante
La brecha del ejemplo no es casualidad ni mala suerte: es la forma estructural de casi toda migración. Y entender por qué te vacuna contra creerle al esfuerzo.
La razón es que el trabajo de una migración incremental está cargado al principio. Antes de cortar la primera llamada al legacy, tienes que: entender el legacy y caracterizarlo (M2), construir el facade (M3), construir el servicio nuevo (M3, M5), montar el anti-corruption layer (M5), preparar la migración de datos (M6), e instrumentar todo para medir. Eso es una montaña de esfuerzo —puede ser el 40% o 50% del trabajo total— que produce cero resultado en términos de legacy_calls: el legacy, al terminar toda esa preparación, atiende exactamente el mismo tráfico que atendía el primer día. El esfuerzo se disparó; el resultado no se movió.
100 ┤ effort ● ● ● ● ● ●
│ ● ● ●
% │ ● ●
│ ● outcome ○
│ ● ○ ○
│ ● ○
│● ○
0 ┤● ○ ○ ○ ○ ○ ○
└──────────────────────────────────────────────────
sprint 0 3 (brecha maxima) 5 (se cierra)
El esfuerzo (los puntos llenos) sube en línea casi recta desde el sprint 0. El resultado (los círculos vacíos) se queda pegado al suelo mientras se construye la infraestructura, y solo despega cuando esa infraestructura ya existe y permite cortar llamadas rápido. La brecha máxima ocurre a mitad de camino —justo donde el esfuerzo ya va muy alto y el resultado apenas arranca—. Ese punto medio es donde mueren las promesas: el equipo siente que va al 80% (por el esfuerzo invertido) y promete un final cercano, pero el resultado dice que falta el 76%.
Hay una consecuencia práctica importante: el esfuerzo no sirve para proyectar la fecha de fin, pero el resultado sí. Si extrapolas la línea del esfuerzo, predices un final falso (temprano). Si extrapolas la pendiente del resultado —el burn-down de legacy_calls, que es la lección 3—, predices el final real. Por eso el burn-down, y no el "quemado de puntos", es el instrumento de proyección de este módulo.
Esto no significa que medir el esfuerzo esté mal en general. El esfuerzo es útil para planear (¿cuánto trabajo total hay?, ¿tenemos capacidad?) y para dimensionar lo que falta. Lo que no debe hacer el esfuerzo es hacerse pasar por progreso de la migración. Son dos preguntas distintas: "¿cuánto ha trabajado el equipo?" (esfuerzo) y "¿cuánto se ha muerto el legacy?" (resultado). La segunda es la que le importa al negocio, la que decide cuándo puedes apagar el viejo sistema, y la única que llega honestamente al 100%.
Una prueba rápida para saber si una métrica es de resultado o de esfuerzo: pregúntate "si el equipo trabajara todo el sprint pero no cortara ni una sola llamada al legacy, ¿esta métrica subiría?". Si la respuesta es sí (los story points suben, las horas suben, los PRs suben), es una métrica de esfuerzo y no mide el progreso de la migración. Si la respuesta es no (las legacy_calls solo bajan si de verdad cortaste una dependencia), es una métrica de resultado. Mide con las que responden "no".
Errores comunes
Reportar el progreso de la migración en story points. Qué pasa: el reporte de avance dice "quemamos 80 de 100 puntos, vamos al 80%", y todos —incluida la dirección— entienden que la migración está casi terminada. Por qué pasa: los story points ya se miden para el sprint, están a la mano, y "80 de 100" suena a progreso claro. Cómo detectarlo: el porcentaje de avance que reporta el equipo viene de puntos, horas o tareas, no de una medición del legacy; nadie puede responder "¿qué fracción del tráfico ya no toca el legacy?". Cómo corregirlo: reporta el resultado —el outcome_percent, las legacy_calls que quedan, los endpoints cortados—. Los puntos miden cuánto trabajó el equipo, no cuánto se murió el legacy, y las dos cosas se separan justo a mitad del proyecto (brecha de 56 en el ejemplo). Un reporte honesto de migración habla de lo que le pasó al legacy, no de lo que hizo el equipo.
Confundir "terminamos la infraestructura" con "vamos por la mitad". Qué pasa: el equipo termina el facade, el ACL y los tests —un trabajo enorme— y concluye "ya hicimos la mitad más difícil, el resto es cuesta abajo, vamos al 50%". Por qué pasa: la infraestructura es mucho trabajo y sí es la parte más difícil conceptualmente, así que "terminarla" se siente como un hito de mitad de camino. Cómo detectarlo: el outcome_percent (llamadas al legacy cortadas) sigue cerca de 0 aunque el equipo declare "50% hecho"; el hito celebrado es de esfuerzo, no de resultado. Cómo corregirlo: terminar la infraestructura es un hito real, pero de esfuerzo, no de resultado —el legacy no se movió—. Sí es verdad que después el resultado acelera (los sprints 4 y 5 del ejemplo), pero mientras legacy_calls no baje, el progreso de la migración es el que dice el resultado, no el que sientes por el trabajo hecho. Celebra la infraestructura como lo que es (una capacidad construida), y mide el avance con el legacy muriendo.
No instrumentar el sistema para medir el resultado. Qué pasa: el equipo querría medir el resultado, pero no tiene forma de contar cuántas llamadas van al legacy y cuántas al nuevo, así que por default reporta lo único que sí mide: el esfuerzo. Por qué pasa: medir legacy_calls requiere instrumentar el strangler router o el código para contar por dónde pasa cada request, y eso es trabajo que a veces se salta. Cómo detectarlo: cuando pides el outcome_percent, la respuesta es "no lo tenemos medido"; el único dato disponible es de puntos u horas. Cómo corregirlo: instrumenta desde el día uno. El strangler router (M3) ya sabe por dónde manda cada request —solo hay que contarlo—; el burn-down (lección 3) se alimenta de ese conteo. Sin instrumentación no hay métrica de resultado, y sin métrica de resultado la migración vuela a ciegas guiada por el esfuerzo. Medir el resultado no es opcional: es lo que hace que todas las demás lecciones de este módulo sean posibles.
Ejercicios
Ejercicio 1 — Clasifica las métricas. Para cada una, di si es de esfuerzo o de resultado, y aplica la prueba rápida ("¿subiría si el equipo trabaja pero no corta ninguna llamada al legacy?"): (a) story points quemados en el sprint; (b) porcentaje del tráfico servido por la ruta nueva; (c) número de PRs mergeados; (d) número de tablas que ya no comparte el monolito; (e) horas invertidas por el equipo.
Ver solución
- (a) Story points quemados → esfuerzo. Prueba: si el equipo trabaja un sprint entero refactorizando sin cortar una llamada al legacy, los puntos suben igual. Sube sin resultado → esfuerzo.
- (b) % del tráfico en la ruta nueva → resultado. Prueba: solo sube si de verdad desviaste tráfico del legacy al nuevo. No sube por trabajar; sube por cortar. → resultado.
- (c) PRs mergeados → esfuerzo. Prueba: puedes mergear diez PRs de preparación (tests, infraestructura) sin mover una sola llamada al legacy. Sube sin resultado → esfuerzo.
- (d) Tablas que ya no comparte el monolito → resultado. Prueba: solo baja cuando de verdad cortaste la dependencia de una tabla. Es un corte real del legacy → resultado.
- (e) Horas invertidas → esfuerzo. Prueba: las horas suben mientras el equipo esté trabajando, aunque el legacy no se mueva. El caso más puro de esfuerzo → esfuerzo.
La regla se sostiene: las de resultado (b, d) apuntan al legacy encogiéndose; las de esfuerzo (a, c, e) apuntan a la actividad del equipo. Solo las primeras miden el progreso de la migración.
Ejercicio 2 — Explica la brecha del sprint 3. En la salida, el sprint 3 tuvo effort_percent = 80%, outcome_percent = 24%, brecha de 56. (a) ¿En qué se fue ese 80% de esfuerzo si solo se cortó el 24% del tráfico? (b) Si reportas "80% hecho" a dirección, ¿qué van a esperar, y por qué te vas a equivocar? (c) ¿Por qué la brecha empieza a cerrarse a partir del sprint 4?
Ver solución
(a) Ese 80% de esfuerzo se fue, en su mayor parte, en construir la infraestructura de la migración que todavía no corta llamadas: caracterizar el legacy, montar el strangler facade, construir el anti-corruption layer, preparar la migración de datos, instrumentar. Todo ese trabajo es real y necesario, pero no reduce legacy_calls —el legacy, tras hacerlo, sigue atendiendo 760 de 1000—. El esfuerzo se cargó al principio; el resultado apenas empieza.
(b) Si reportas "80% hecho", dirección va a esperar que la migración termine pronto —"falta un 20%, un par de sprints"—. Te vas a equivocar porque el 80% es de esfuerzo, no de resultado: el legacy todavía atiende el 76% del tráfico, y apagarlo requiere cortar ese 76%, que es la mayor parte del trabajo de resultado que falta. Prometiste un final cercano midiendo la métrica que corre por delante; el final real lo marca el resultado, que dice 24%.
(c) Porque a partir del sprint 4 la infraestructura ya está construida, y cortar llamadas al legacy se vuelve rápido: con el facade, el ACL y los tests listos, cada sprint puede desviar mucho más tráfico. El outcome_percent acelera de 24% a 60% a 100%, alcanzando al esfuerzo. La brecha se cierra porque el resultado por fin "cobra" la inversión de infraestructura de los primeros sprints. Esto también explica por qué el esfuerzo no sirve para proyectar: su pendiente no anticipa este cambio de ritmo del resultado.
Ejercicio 3 — Diseña el reporte. Te piden un reporte semanal de una sola línea sobre el avance de la migración del catalog, para dirección. (a) Escribe una versión mala (basada en esfuerzo) y una buena (basada en resultado) para el sprint 3. (b) ¿Qué métrica de resultado incluirías además del outcome_percent? (c) ¿Cómo evitarías que "vamos al 24%" suene a que el equipo no trabajó, cuando en realidad quemó el 80% del esfuerzo?
Ver solución
(a) Mala (esfuerzo): "Migración del catálogo al 80%: quemamos 80 de 100 puntos, casi terminamos." Suena a final cercano, y es falso. Buena (resultado): "El 24% del tráfico del catálogo ya vive en el servicio nuevo (760 de 1000 requests aún tocan el legacy). La infraestructura está lista, así que el ritmo de corte acelerará en los próximos sprints." Dice la verdad del legacy y da contexto sobre el ritmo.
(b) Además del outcome_percent, incluiría el burn-down de legacy_calls con su tendencia (cuántas se cortaron esta semana y la proyección de cuándo llega a cero, que es la lección 3), y el conteo de endpoints y tablas cortados del monolito. Esas métricas de resultado, juntas, pintan el avance real y permiten proyectar el final —cosa que el esfuerzo no puede—.
(c) Separando explícitamente las dos preguntas en el reporte: "El equipo quemó el 80% del esfuerzo planeado (la infraestructura está lista, que era la parte más difícil); y como resultado, el 24% del tráfico ya no toca el legacy, con el ritmo acelerando ahora que la infraestructura existe." Así reconoces el trabajo hecho (esfuerzo) sin dejar que se disfrace de progreso de la migración (resultado). El truco es no ocultar el esfuerzo, sino no confundirlo con el resultado: son dos cosas verdaderas y distintas, y el reporte honesto muestra ambas etiquetadas por lo que son.
Resumen y siguiente paso
En esta lección fijaste la decisión que sostiene todo el módulo: medir el resultado, no el esfuerzo. Viste, con el viaje medido por horas al volante (esfuerzo) frente a kilómetros que faltan (resultado), que las dos familias de métricas se ven igual de "de progreso" pero dicen cosas opuestas —una sube por trabajar, la otra solo baja por cortar el legacy—. Y lo ejecutaste sobre la misma migración: el effort_percent corriendo por delante (80% en el sprint 3) mientras el outcome_percent decía la verdad (24%), con una brecha máxima de 56 justo a mitad de camino que luego se cierra. Aprendiste por qué el esfuerzo siempre corre por delante (el trabajo está cargado al principio y no produce resultado hasta que la infraestructura existe), y la prueba rápida para distinguir las dos: "¿subiría esta métrica si el equipo trabaja pero no corta ninguna llamada al legacy?".
Antes de avanzar deberías poder: clasificar una métrica como de esfuerzo o de resultado con la prueba rápida; explicar por qué el esfuerzo corre por delante y da falsa sensación de final; leer una brecha entre effort_percent y outcome_percent y decir qué significa; y escribir un reporte de avance honesto que no disfrace el esfuerzo de progreso.
La lección 3 toma la métrica de resultado por excelencia —las legacy_calls— y construye con ella el instrumento central del módulo: el burn-down. Vas a ver la pendiente descendente de las llamadas al legacy, sprint a sprint, hasta cero, y aprenderás a leer lo que de verdad importa: no el valor de un sprint aislado, sino la tendencia —la velocity con la que bajan— y la proyección que de ella se deriva. Es la lección que convierte "el 24% del tráfico está migrado" en "a este ritmo, terminaremos en el sprint tal", que es lo que de verdad quiere saber quien espera el final.
Recursos
- Sam Newman, Monolith to Microservices (O'Reilly, 2019), cap. 3 — Newman insiste en medir el progreso de una migración por lo que le pasa al monolito (funcionalidad retirada, llamadas cortadas), no por la actividad del equipo. La base de la distinción de esta lección. En inglés.
- Martin Fowler, "StranglerFigApplication" (2004) — martinfowler.com/bliki/StranglerFigApplication.html. El patrón cuya meta es que el legacy muera: el único progreso que cuenta es el que reduce lo que el legacy atiende, no el esfuerzo invertido en construir lo nuevo. En inglés.
- Neal Ford, Rebecca Parsons y Patrick Kua, Building Evolutionary Architectures (O'Reilly, 2ª ed., 2022) — el libro que defiende medir propiedades objetivas y verificables de la arquitectura (fitness functions) en vez de percepciones; el mismo espíritu de preferir el resultado medible al esfuerzo percibido. En inglés.
- Martin Fowler, martinfowler.com — el bliki con las entradas sobre métricas, arquitectura evolutiva y strangler fig que enmarcan por qué se mide el resultado y no la actividad. En inglés.