Módulo 1: Por qué no reescribir
El conocimiento tácito enterrado en el legacy
Descripción
Los dos modos de falla anteriores eran de tiempo: el negocio no se detiene (lección 2) y el alcance se infla (lección 3). Este es distinto y más traicionero, porque no se trata de cuánto tarda la reescritura, sino de que sale incorrecta sin que nadie se dé cuenta. La causa es el conocimiento tácito: las reglas de negocio que viven enterradas en el código legacy y que nadie escribió en ningún documento. Cuando miras un sistema viejo, hay partes que se ven "raras" —un if extraño, un caso especial para un cliente específico, un redondeo que no cuadra con el manual—, y la tentación del rewrite es tratarlas como basura a limpiar: "esto está mal, en la versión nueva lo hacemos bien". El problema es que esas rarezas casi nunca son bugs. Son cicatrices: cada una es un parche a una queja real, una regla que impuso una auditoría, una promesa que se le hizo a un cliente grande, un caso borde que costó dinero descubrir. El código feo es feo porque el mundo real es feo, y el legacy absorbió esa fealdad línea por línea durante años.
Hay una idea vieja que captura esto: la cerca de Chesterton. Si te encuentras una cerca cruzando un camino y no entiendes para qué sirve, la respuesta ingenua es "no sirve, quitémosla"; la respuesta sabia es "no la quites hasta que entiendas por qué la pusieron". El código legacy está lleno de cercas de Chesterton, y una reescritura a ciegas las derriba todas, porque reimplementa la especificación "limpia" —la que está en la doc, o la que el equipo cree que es correcta— y tira todas las reglas que la doc nunca mencionó. El resultado son regresiones silenciosas: comportamientos que el legacy tenía bien (para el negocio) y que el sistema nuevo cambia sin que ningún test lo grite, porque el legacy no tiene tests. Esta lección lo mide: vamos a reproducir un puñado de órdenes históricas de Mercado por el legacy y por un rewrite "limpio", y a contar cuántas salen distintas.
Conexión con el módulo. Es el tercero y más peligroso de los modos de falla del big rewrite: no falla el calendario, falla la corrección, y en silencio. La idea central —el legacy es la especificación— es también el puente al módulo 2: la forma de capturar ese conocimiento tácito antes de tocar el código es el characterization test, que fija el comportamiento actual (rarezas incluidas) para que cualquier cambio que lo altere salte de inmediato. Aquí medimos el conocimiento en juego; el módulo 2 enseña la técnica para protegerlo. La frontera es clara: la teoría de testing a fondo es el ecosistema de Testing; aquí, la reproducción como termómetro del riesgo.
Una analogía: la receta de la abuela con la anotación al margen
Heredaste el recetario de tu abuela. Su famoso pan tiene la receta escrita con letra clara: harina, agua, levadura, sal, tiempos, temperaturas. Pero al margen, con lápiz, hay anotaciones sueltas: "en verano, 10 min menos", "si la harina es de la marca nueva, un chorrito más de agua", "el horno de arriba calienta de más, bajar 15 grados". Decides modernizar: pasas la receta a limpio, a una tarjeta bonita, y —porque las anotaciones al margen se ven desordenadas y "poco profesionales"— las omites. Te quedas con la receta "oficial", la limpia.
El primer pan que horneas con la tarjeta nueva sale mal: en verano, con la harina nueva, en tu horno que calienta de más. Cada anotación al margen que borraste era una regla ganada con años de panes fallidos —tu abuela no las escribió por capricho, las escribió porque aprendió a los golpes que sin ellas el pan sale mal—. La receta "limpia" era la teoría; las anotaciones eran el conocimiento real, el que hace que el pan salga bien en el mundo desordenado de veranos, marcas de harina y hornos disparejos.
El código legacy es ese recetario. La especificación "oficial" —lo que está en la doc, lo que el equipo cree que el sistema hace— es la receta a limpio. Las rarezas del código —los if especiales, los redondeos extraños, los casos por cliente— son las anotaciones al margen: conocimiento tácito ganado a los golpes, que nadie pasó a la doc porque parecían detalles. Un rewrite que reimplementa la especificación limpia y tira las rarezas está horneando con la tarjeta bonita, y sus panes van a salir mal —solo que en Mercado un pan mal salido es un cliente cobrado de más, un pedido que se rompe, una auditoría que falla—.
Ejemplo trabajado: contar las regresiones de un rewrite "limpio"
Vamos a modelar el cálculo del total de una orden en el checkout de Mercado. El legacy acumuló, en años, varias reglas que nadie documentó: un precio congelado para un bundle viejo, un cupón que no acumula con el descuento por volumen (fix tras una queja), envío gratis para clientes fundadores (una promesa vieja), y un truncado al peso (un bug histórico del que ahora dependen las conciliaciones contables). El rewrite "limpio" implementa la especificación obvia —descuentos que acumulan, todos pagan envío, redondeo estándar— y no sabe nada de las reglas ocultas. Reproducimos siete órdenes históricas por ambos y contamos las diferencias:
ORDERS = [
# (id, customer_id, sku, qty, unit_price, coupon)
("A", 42, "SHOES-01", 2, 300.00, None), # cliente fundador (id<100)
("B", 500, "LEGACY-BUNDLE", 5, 200.00, None), # sku con precio congelado
("C", 700, "PHONE-09", 10, 150.00, "WELCOME"), # cupon + descuento volumen
("D", 800, "MUG-03", 1, 500.00, None), # caso sin reglas ocultas
("E", 900, "BOOK-02", 3, 333.33, None), # dispara el truncado
("F", 50, "TV-40", 4, 400.00, None), # fundador + volumen
("G", 600, "CASE-01", 2, 250.00, "WELCOME"), # solo cupon, sin conflicto
]
def legacy_total(cid, sku, qty, price, coupon):
# El comportamiento REAL, con todas sus rarezas ganadas a lo largo de anios.
if sku == "LEGACY-BUNDLE":
subtotal = 799.0 # promo vieja nunca retirada; ignora qty
else:
subtotal = qty * price
volume = 0.10 if subtotal > 1000 else 0.0
welcome = 0.15 if coupon == "WELCOME" else 0.0
# Fix de 2019: el cupon WELCOME NO acumula con el descuento por volumen.
discount = max(volume, welcome) if (volume and welcome) else volume + welcome
total = subtotal * (1 - discount)
total += 0.0 if cid < 100 else 99.0 # clausula heredada: fundadores sin envio
return float(int(total)) # bug historico: trunca al peso
def clean_rewrite_total(cid, sku, qty, price, coupon):
# La especificacion "obvia y correcta" que el equipo del rewrite escribiria
# leyendo la doc (que no menciona ninguna de las reglas ocultas de arriba).
subtotal = qty * price
volume = 0.10 if subtotal > 1000 else 0.0
welcome = 0.15 if coupon == "WELCOME" else 0.0
discount = volume + welcome # los descuentos "deberian" acumular
total = subtotal * (1 - discount)
total += 99.0 # todos pagan envio
return round(total, 2) # redondeo estandar
HIDDEN_RULE = {
"A": "envio gratis a fundadores",
"B": "precio congelado del bundle",
"C": "cupon no acumula con volumen",
"E": "truncado al peso",
"F": "envio gratis a fundadores",
}
print(f"{'order':>6}{'legacy':>10}{'rewrite':>10}{'match':>7} regla perdida")
print("-" * 60)
regressions = 0
for oid, cid, sku, qty, price, coupon in ORDERS:
lg = legacy_total(cid, sku, qty, price, coupon)
rw = clean_rewrite_total(cid, sku, qty, price, coupon)
match = "OK" if lg == rw else "DIFF"
if lg != rw:
regressions += 1
note = "" if lg == rw else HIDDEN_RULE.get(oid, "?")
print(f"{oid:>6}{lg:>10.2f}{rw:>10.2f}{match:>7} {note}")
print("-" * 60)
print(f"\n Ordenes reproducidas: {len(ORDERS)}")
print(f" Regresiones silenciosas del rewrite 'limpio': {regressions}"
f" de {len(ORDERS)}")
print(f" Cada DIFF es un cliente cobrado distinto que el legacy - sin que")
print(f" ningun test lo grite, porque el legacy no tiene tests todavia.")
Qué esperar. Al correr el archivo, la salida es exactamente esta:
order legacy rewrite match regla perdida
------------------------------------------------------------
A 600.00 699.00 DIFF envio gratis a fundadores
B 898.00 1099.00 DIFF precio congelado del bundle
C 1374.00 1224.00 DIFF cupon no acumula con volumen
D 599.00 599.00 OK
E 1098.00 1098.99 DIFF truncado al peso
F 1440.00 1539.00 DIFF envio gratis a fundadores
G 524.00 524.00 OK
------------------------------------------------------------
Ordenes reproducidas: 7
Regresiones silenciosas del rewrite 'limpio': 5 de 7
Cada DIFF es un cliente cobrado distinto que el legacy - sin que
ningun test lo grite, porque el legacy no tiene tests todavia.
Lee la tabla fila por fila, porque cada DIFF es una anotación al margen que el rewrite borró.
Orden A (cliente fundador 42): el legacy cobra 600, el rewrite 699. La diferencia son 99 pesos de envío que el rewrite cobra y el legacy no. ¿Por qué? Porque hay una regla vieja —envío gratis para clientes fundadores (id < 100)— que la doc nunca mencionó. El rewrite, sin saberlo, le empezó a cobrar el envío a los clientes más antiguos y leales de Mercado. Nadie lo notó en el código; lo van a notar los fundadores en su siguiente compra.
Orden B (el bundle viejo): legacy 898, rewrite 1099. El LEGACY-BUNDLE tenía un precio congelado de 799 —una promo vieja que nunca se retiró y de la que algunos clientes dependen—. El rewrite calculó el precio "correcto" por cantidad (5 × 200 = 1000) y le sumó envío. Doscientos pesos de diferencia por un caso especial que se veía como "código muerto que hay que limpiar".
Orden C (cupón + volumen): legacy 1374, rewrite 1224. Aquí el legacy es más caro que el rewrite, y esa es la sorpresa: no todas las regresiones cobran de menos. El legacy tiene un fix de 2019 —el cupón WELCOME no acumula con el descuento por volumen— que se puso tras descubrir que la acumulación regalaba márgenes. El rewrite, "haciendo lo correcto", volvió a acumularlos, y le está regalando 150 pesos a cada cliente que combine cupón y volumen. Multiplica eso por miles de órdenes: es una fuga de margen que nadie autorizó.
Orden E (el truncado): legacy 1098, rewrite 1098.99. Noventa y nueve centavos de diferencia. Parece nada —¿a quién le importan 99 centavos?—. Le importa a la conciliación contable, que durante años cuadró con los totales truncados del legacy. El rewrite redondea "bien" y ahora los totales no cuadran con el histórico, y el equipo de finanzas pasa una semana persiguiendo una diferencia de centavos que se multiplica por cada orden. El bug histórico se volvió, con los años, un contrato tácito.
Órdenes D y G salieron OK: no toda orden dispara una regla oculta, y eso es importante —el rewrite no está todo mal, lo que lo hace más peligroso, porque el 71% que coincide da una falsa sensación de que funciona—. Cinco de siete órdenes salieron distintas, cada una por una regla que el rewrite no sabía que existía. Y el remate está en la última línea: ningún test lo gritó, porque el legacy no tiene tests. Las cinco regresiones habrían llegado a producción invisibles, a descubrirse una por una a través de clientes molestos, fugas de margen y conciliaciones rotas —el peor lugar y el peor momento para descubrirlas—.
Profundización: el legacy es la especificación
La conclusión de esta lección es una frase que conviene grabar: en un sistema legacy sin documentación, el código no implementa la especificación; el código es la especificación. No hay un documento superior contra el cual el legacy pueda estar "equivocado" en las reglas que importan al negocio —lo que el sistema hace es, para el negocio, lo correcto por definición, porque es lo que los clientes esperan, lo que la contabilidad cuadra y lo que los procesos asumen—. Cuando el rewrite reimplementa la especificación "limpia" y difiere del legacy, no está corrigiendo al legacy: está rompiendo el contrato que el legacy encarna.
Esto invierte por completo la intuición del rewrite. La reescritura se vende como "hacerlo bien esta vez", con la idea de que el legacy está lleno de errores que la versión nueva va a corregir. Pero la mayoría de esas "correcciones" son regresiones disfrazadas: el legacy no estaba mal, estaba ajustado a la realidad de un modo que la doc no capturó. La verdadera especificación de Mercado no cabe en ningún documento; vive distribuida en miles de líneas de código, y la única forma honesta de conocerla es preguntándole al código qué hace —reproduciendo sus salidas, como acabas de hacer— antes de cambiarlo.
Aquí es donde el módulo 2 entra en escena. La técnica para capturar esa especificación tácita se llama characterization test: en vez de escribir tests contra lo que el sistema debería hacer (que no sabes), escribes tests que fijan lo que el sistema actualmente hace —rarezas incluidas—. El test de la orden A no dice "el total debería ser X"; dice "hoy el legacy devuelve 600 para esta orden, y quiero que siga devolviendo 600". Con esa red tendida sobre las cinco reglas ocultas, cualquier reimplementación que las rompa falla al instante, y las cercas de Chesterton dejan de ser invisibles. El punto de esta lección es entender por qué esa red es imprescindible; el cómo tenderla es el módulo 2.
flowchart TD
L["Código legacy<br/>(la especificación real,<br/>con sus cercas de Chesterton)"]
D["Documentación<br/>(la especificación 'limpia',<br/>incompleta)"]
R1["Rewrite a ciegas<br/>desde la doc"]
R2["Modernización con red<br/>(characterization tests, M2)"]
D --> R1 --> X["5 regresiones silenciosas<br/>en producción"]
L -.captura el comportamiento real.-> R2 --> Y["cambios seguros:<br/>si rompes una regla, el test grita"]
Errores comunes
Tratar lo "raro" del legacy como basura a limpiar. Qué pasa: al leer el código viejo, el equipo ve un if con un caso especial sin comentario y concluye "esto está mal / es código muerto / lo hacemos bien en la nueva versión", y lo elimina. Por qué pasa: el código feo parece error, y limpiarlo se siente como una mejora obvia. Cómo detectarlo: si estás a punto de borrar una rama de código porque "no tiene sentido" y no puedes explicar por qué la pusieron, estás frente a una cerca de Chesterton. Cómo corregirlo: invierte la carga de la prueba. En un sistema legacy vivo, asume que cada rareza tuvo una razón hasta que demuestres lo contrario —busca en el historial de commits, pregunta a quien lleve más tiempo, revisa si algún cliente o proceso depende de ella—. Y antes de tocarla, fíjala con un characterization test (módulo 2): si de verdad era código muerto, el test lo confirmará; si no lo era, el test te salvará de una regresión.
Confiar en la documentación como si fuera la verdad. Qué pasa: el rewrite se planea contra la doc del sistema —el diagrama, el manual, lo que el equipo recuerda— asumiendo que eso describe fielmente lo que el sistema hace. Por qué pasa: la doc es lo que hay escrito, y es más fácil de leer que 50.000 líneas de código. Cómo detectarlo: pregunta "¿cuándo se actualizó esta doc por última vez, y quién garantiza que coincide con el código de hoy?". Si la respuesta es incómoda —y en un legacy siempre lo es—, la doc es una aproximación, no la especificación. Cómo corregirlo: la fuente de verdad de lo que un sistema legacy hace es el sistema corriendo, no su documentación. Reproduce su comportamiento con entradas reales (como en el ejemplo) y trata esas salidas como el contrato. La doc sirve como pista, nunca como garantía. Las cinco reglas del ejemplo no estaban en ninguna doc; estaban solo en el código y en la memoria de los clientes.
Creer que las regresiones "se van a notar en las pruebas". Qué pasa: el equipo asume que, si el rewrite rompe algo, el QA o los tests lo van a atrapar antes de producción. Por qué pasa: en proyectos con buena cobertura, esa suposición es razonable. Cómo detectarlo: pregunta "¿contra qué comparamos la salida del rewrite para saber si es correcta?". Si la respuesta es "contra lo que creemos que debería hacer", no hay red —estás comparando contra la especificación limpia, que es justo la que no tiene las reglas ocultas—. Cómo corregirlo: la única comparación que atrapa estas regresiones es rewrite contra legacy, no rewrite contra la doc. Correr las mismas entradas por ambos y exigir que coincidan (lo que el módulo 6 llama parallel-run) es lo que convierte una regresión silenciosa en una diferencia visible. Sin esa comparación, un legacy sin tests garantiza que las cinco regresiones del ejemplo lleguen a producción calladas.
Ejercicios
Ejercicio 1 — Diagnostica la cerca. Estás modernizando el módulo de envíos de Mercado y encuentras este código: if destination_zip.startswith("90") and weight > 5: shipping_days += 2. No hay comentario, y la doc no lo menciona. Un compañero dice "esto es arbitrario, bórralo en la versión nueva". Da tres hipótesis plausibles de por qué alguien pudo escribir esa regla, y describe qué harías antes de decidir si eliminarla.
Ver solución
Tres hipótesis plausibles de la cerca de Chesterton (cualquier razón real vale):
- Una zona geográfica con logística difícil. Los códigos postales que empiezan con "90" pueden corresponder a una región montañosa, insular o remota donde los paquetes pesados (>5 kg) de verdad tardan dos días más porque requieren un transportista distinto. La regla codifica una realidad operativa aprendida a los golpes.
- Un acuerdo con un transportista. Quizá el contrato con la paquetería que cubre esa zona especifica plazos más largos para carga pesada, y la regla refleja la promesa contractual que Mercado le hace al cliente para no prometer de más.
- Un parche tras una ola de quejas. Pudo haber una tanda de clientes de esa zona molestos porque los paquetes pesados llegaban tarde y la estimación decía otra cosa; alguien ajustó la estimación para que fuera honesta y dejaran de llegar reclamos.
Qué haría antes de decidir: (a) buscar el commit que introdujo la línea y leer su mensaje y su fecha —a veces la razón está ahí—; (b) preguntar a la persona con más antigüedad en logística o soporte si recuerda algo de esa zona; (c) mirar datos reales: ¿los envíos pesados a esa zona de hecho tardan más? Si los datos lo confirman, la regla es correcta y hay que preservarla; (d) pase lo que pase, fijar el comportamiento con un characterization test (módulo 2) antes de tocar el módulo, para que si la regla importaba, romperla salte de inmediato. Lo que no haría es borrarla porque "se ve arbitraria": esa es exactamente la cerca de Chesterton que no se derriba sin entender.
Ejercicio 2 — El 71% que engaña. En el ejemplo, 2 de 7 órdenes (el 29%) coincidieron entre legacy y rewrite. Explica por qué ese porcentaje de coincidencia, lejos de ser tranquilizador, hace que el rewrite sea más peligroso. ¿Qué habría pasado si hubiéramos probado solo con las órdenes D y G?
Ver solución
El 29% de coincidencia (órdenes D y G) es peligroso precisamente porque funciona lo suficiente para inspirar confianza falsa. Si el rewrite fallara en el 100% de los casos, el problema sería evidente al primer vistazo y nadie lo pondría en producción. Pero un rewrite que acierta en los casos "normales" (una orden simple sin reglas ocultas, como D y G) y solo falla en los casos "raros" pasa las pruebas superficiales, las demos y la intuición del equipo —"lo probé y funciona"—, y esconde sus regresiones justo donde nadie mira: en los bordes.
Si hubiéramos probado solo con las órdenes D y G, el rewrite habría salido OK en el 100% de las pruebas, y el equipo habría concluido con toda tranquilidad que el cálculo del total era correcto. Las cinco regresiones (envío a fundadores, bundle congelado, cupón no acumulable, truncado) habrían llegado a producción intactas, porque la muestra de prueba no tocó ninguna regla oculta. Esta es la trampa de probar con "casos típicos": las reglas tácitas viven en los casos atípicos, y una muestra que solo cubre lo típico da un veredicto verde falso.
La lección de método: para atrapar regresiones de conocimiento tácito, hay que reproducir entradas reales e históricas —que sí contienen los casos raros porque el mundo real los produjo—, no entradas "de ejemplo" inventadas por el equipo, que tienden a ser todas típicas. El módulo 2 (characterization tests sobre datos reales) y el módulo 6 (parallel-run) atacan justo esto.
Ejercicio 3 — El legacy es la especificación. Explica, con tus palabras, la frase "en un sistema legacy sin documentación, el código es la especificación". ¿Significa esto que el legacy nunca tiene bugs de verdad? Distingue entre una rareza que hay que preservar y un bug que sí hay que corregir, y di cómo decidirías cuál es cuál.
Ver solución
La frase significa que, a falta de un documento superior y actualizado, lo que define "el comportamiento correcto" de un sistema legacy es lo que el sistema efectivamente hace, porque eso es lo que el mundo alrededor (clientes, contabilidad, procesos, integraciones) ya asume y depende. No hay una autoridad externa contra la cual el legacy pueda estar equivocado en las reglas que el negocio ya absorbió: si el legacy cobra de cierta forma y la contabilidad cuadra con eso, esa forma es la especificación de facto.
No, esto no significa que el legacy nunca tenga bugs reales. Sí los tiene. La distinción es sutil pero decidible:
- Una rareza que hay que preservar es un comportamiento del que algo o alguien depende: un cliente lo espera, un proceso lo asume, la contabilidad cuadra con él, una integración lo consume. Aunque parezca "incorrecto" en abstracto (como el truncado al peso), cambiarlo rompe ese algo. El truncado del ejemplo es una rareza: feo, pero la conciliación depende de él.
- Un bug de verdad es un comportamiento del que nadie depende y que produce un resultado que todos —incluido el negocio— reconocen como no deseado: un crash intermitente, un cálculo que da resultados absurdos que nadie ha visto porque el caso no se ha dado, un dato que se corrompe. Corregirlo no rompe ningún contrato tácito porque no había contrato.
Cómo decidir cuál es cuál: la pregunta clave es "¿alguien o algo depende de este comportamiento tal como es?". Se responde investigando (historial, datos reales, preguntar a soporte y finanzas), no adivinando. Y la regla de oro de la modernización: preserva primero, corrige después. En una migración, tu objetivo inmediato es paridad —replicar el comportamiento actual, bugs incluidos—; una vez que el sistema nuevo iguala al viejo y está bajo tests, entonces —y solo entonces— arreglas los bugs reales como cambios conscientes y separados, cada uno con su prueba. Corregir bugs durante la migración mezcla dos cosas y hace imposible saber si una diferencia es una mejora intencional o una regresión accidental.
Resumen y siguiente paso
En esta lección cerraste el trío de modos de falla del big rewrite con el más traicionero: el conocimiento tácito enterrado en el legacy. Viste, con el recetario de la abuela, que las rarezas del código viejo casi nunca son basura sino anotaciones al margen —reglas de negocio ganadas a los golpes que nadie documentó—, y que un rewrite "limpio" que las tira hornea con la receta incompleta. Y lo mediste: reproducir siete órdenes históricas por el legacy y por un rewrite limpio reveló 5 regresiones silenciosas de 7, cada una por una regla oculta (envío a fundadores, bundle congelado, cupón no acumulable, truncado contable), y ninguna habría gritado en un legacy sin tests. La conclusión que grabar: el legacy es la especificación, y conocerla exige preguntarle al código qué hace, no confiar en la doc.
Antes de avanzar deberías poder: explicar la cerca de Chesterton aplicada al código; distinguir una rareza que preservar de un bug que corregir; argumentar por qué un alto porcentaje de coincidencia hace al rewrite más peligroso, no menos; y decir por qué la comparación que importa es rewrite-contra-legacy, no rewrite-contra-doc.
Con esto termina el caso contra el rewrite: viste los tres modos de falla —el negocio que no se detiene, el segundo sistema, el conocimiento perdido—. La lección 5 gira el argumento y construye el caso a favor de la alternativa: el caso a favor de lo incremental. Vas a medir por qué transformar por rebanadas entrega valor temprano y arriesga poco, con dos métricas concretas —los valor-periodos acumulados y el riesgo en juego por despliegue— que muestran, en números, la ventaja del camino que sí funciona.
Recursos
- Michael Feathers, Working Effectively with Legacy Code (Prentice Hall, 2004) — la referencia definitiva. Su definición de "legacy" (código sin tests) y su técnica del characterization test son la respuesta directa al problema de esta lección: capturar el conocimiento tácito antes de tocarlo. Puente al módulo 2. En inglés.
- G. K. Chesterton, The Thing (1929), cap. "The Drift from Domesticity" — el origen de la "cerca de Chesterton": no derribes una cerca hasta entender por qué la pusieron. El principio que gobierna cómo tratar las rarezas del legacy. En inglés.
- Michael Feathers, characterization tests — término acuñado en Working Effectively with Legacy Code; resumen en Wikipedia: en.wikipedia.org/wiki/Characterization_test. La técnica de fijar el comportamiento actual como red de seguridad, desarrollada en el módulo 2 de esta guía. En inglés.
- Sam Newman, Monolith to Microservices (O'Reilly, 2019), sobre migración de datos y comportamiento — cómo el parallel-run (correr viejo y nuevo en paralelo y comparar) atrapa estas regresiones antes del switch. Se desarrolla en el módulo 6. En inglés.