Migraciones arquitectónicas y Strangler Fig | Nicolás Garzón
Una migración arquitectónica no es solamente construir el sistema nuevo. Es transferir comportamiento, datos, tráfico y ownership mientras el negocio continúa operando y existe una ruta segura para detener, comparar o revertir.
Los cambios arquitectónicos grandes rara vez pueden realizarse en un único despliegue. Durante un periodo coexistirán:
Modelo antiguo y nuevo.
Lectores con versiones diferentes.
Dos rutas de tráfico.
Datos pendientes de migración.
Contratos temporales.
Ownership compartido o en transición.
Texto
Copiar estado actual
→ crear seam
→ coexistencia controlada
→ migrar capacidad y datos
→ verificar equivalencia
→ transferir ownership
→ retirar legadoEl éxito no consiste en que el sistema nuevo reciba tráfico. Consiste en que el camino anterior, las sincronizaciones temporales y el riesgo de volver atrás hayan sido eliminados de forma segura.
Una reescritura big bang concentra demasiadas incertidumbres:
Comportamiento no documentado.
Datos inconsistentes.
Integraciones desconocidas.
Rendimiento real.
Procesos operativos.
Usuarios que dependen de detalles accidentales.
Cuanto más tiempo se construye el reemplazo aislado, más diverge del sistema en producción. Al final, el equipo debe migrar todo de una vez y descubrir errores bajo máxima presión.
Una migración incremental divide el riesgo y produce evidencia antes de comprometer todo el sistema.
Qué reglas y funciones atiende cada camino.
Dónde está la fuente de verdad, cómo se sincroniza y cómo se verifican divergencias.
Qué clientes, tenants o porcentaje utilizan la nueva ruta.
Qué equipo y componente tiene autoridad para cambiar, operar y responder por la capacidad.
Migrar solo comportamiento sin ownership deja dos sistemas responsables. Migrar tráfico sin datos correctos produce respuestas divergentes.
El patrón Strangler Fig coloca una frontera alrededor del sistema existente y reemplaza capacidades de manera gradual.
Texto
Copiar clientes
↓
facade / router / API gateway
├── capacidad antigua → legado
└── capacidad migrada → sistema nuevoEl router puede decidir por:
Ruta o endpoint.
Capacidad.
Tenant.
Usuario interno.
Feature flag.
Porcentaje de tráfico.
Región.
La fachada no debe convertirse en un segundo legado lleno de lógica. Su responsabilidad temporal es enrutar, adaptar contratos y observar la transición.
Una buena primera extracción tiene:
Límite comprensible.
Valor real.
Dependencias manejables.
Datos identificables.
Riesgo acotado.
Señales para comparar.
Evita comenzar por el núcleo más interconectado solo porque parece importante.
Ejemplos iniciales razonables:
Notificaciones.
Generación de documentos.
Búsqueda derivada.
Catálogo de solo lectura.
Una capacidad con invariantes distribuidas y muchas escrituras puede requerir preparar boundaries antes de extraer.
Mapea entradas y salidas.
Identifica consumidores.
Registra errores y estados.
Captura métricas.
Crea characterization tests.
Busca procesos manuales.
Analiza datos históricos y excepciones.
El legado suele contener reglas no documentadas. Algunas son bugs; otras representan necesidades reales.
El seam permite interceptar o sustituir comportamiento.
Facade.
Adapter.
API interna.
Evento.
Router.
Capa de acceso a datos.
Texto
Copiar antes:
OrdersController → LegacyInventoryDatabase
después:
OrdersController → InventoryPort → LegacyInventoryAdapterEste paso no cambia todavía el resultado. Centraliza el acceso para poder introducir el camino nuevo.
El sistema nuevo no necesita copiar internals del legado. Necesita mantener el comportamiento externo que todavía es requerido.
Entradas.
Salidas.
Errores.
Idempotencia.
Autorización.
Latencia esperada.
Semántica de estados.
Diferencias intencionales.
“Ambos devuelven JSON” no demuestra equivalencia.
La parte más difícil suele ser el ownership de datos.
Copia datos históricos al modelo nuevo.
Lotes.
Checkpoints.
Idempotencia.
Transformaciones versionadas.
Métricas.
Reintentos.
Verificación de conteos y semántica.
Un backfill debe poder reanudarse sin duplicar ni reiniciar todo.
Captura cambios del sistema antiguo y los aplica al nuevo.
Reduce modificación del legado.
Permite sincronización continua.
Eventos de bajo nivel sin semántica de dominio.
Orden y duplicación.
Cambios de schema.
Latencia de replicación.
Una operación escribe ambos sistemas.
Texto
Copiar request
→ write antiguo
→ write nuevoEs peligroso si no existe coordinación. El primer write puede completar y el segundo fallar.
Outbox desde la fuente de verdad.
Escritura principal + propagación asíncrona.
Reconciliación periódica.
Idempotencia en destino.
Si se utiliza dual write temporal, deben existir estados de fallo y reparación.
Se consulta ambos caminos para comparar o fallback.
Duplica carga.
Puede ocultar divergencias si siempre se prefiere un resultado.
Requiere normalización.
Es útil como herramienta temporal de validación, no como arquitectura permanente.
En cada fase debe existir una autoridad explícita.
Texto
Copiar fase 1: legado escribe, nuevo replica
fase 2: nuevo escribe, legado recibe compatibilidad
fase 3: nuevo es única autoridadNo declares “ambos son fuente de verdad”. Eso significa que los conflictos no tienen resolución definida.
Shadow traffic envía una copia de requests al camino nuevo sin utilizar su resultado para el usuario.
Respuestas.
Latencia.
Errores.
Consumo.
Casos no previstos.
Debe evitar efectos duplicados. Es adecuado para lecturas o ejecución en modo dry-run. Para escrituras se necesita sandbox, simulación o deduplicación explícita.
La comparación debe considerar diferencias permitidas, como orden de campos o timestamps.
Usuarios internos.
Tenant piloto.
Región.
Feature flag.
1 %, 10 %, 50 %, 100 %.
Criterio de entrada.
Métricas.
Error budget.
Tiempo de observación.
Criterio de pausa.
Plan de rollback.
Aumentar tráfico automáticamente sin comprobar integridad puede propagar un fallo rápido.
Volver a la versión anterior del código no siempre restaura el estado.
¿Qué datos escribió el sistema nuevo?
¿El legado puede leerlos?
¿Se perdieron cambios?
¿Se puede volver a enrutar sin reconciliar?
¿Qué eventos ya se publicaron?
¿Qué operaciones externas ya ocurrieron?
Rollback: volver al camino anterior.
Roll forward: corregir y continuar.
Compensación: aplicar una operación que contrarreste un efecto.
En migraciones de datos, roll forward suele ser más seguro que intentar deshacer transformaciones complejas.
Es útil cuando el cambio ocurre dentro del mismo proceso.
Encapsula la implementación actual detrás de una abstracción.
Implementa la alternativa.
Selecciona por configuración.
Compara y migra consumidores.
Retira la implementación anterior.
TypeScript
Copiar interface SearchPort {
search ( input: SearchInput) : Promise < SearchResult[ ] > ;
} El adapter antiguo usa SQL; el nuevo usa un índice de búsqueda. El dominio no conoce cuál está activo.
Añade el formato nuevo sin romper el anterior.
Actualiza escritores, lectores y datos.
Elimina el formato viejo cuando ninguna versión depende de él.
Ejemplo de rename de columna:
Añadir business_id junto a tenant_id.
Poblar datos.
Escribir ambos temporalmente.
Migrar lectores.
Verificar.
Dejar de escribir tenant_id.
Eliminar después.
Situación actual: búsquedas SQL lentas y acopladas al catálogo.
Usar un motor de búsqueda derivado sin convertirlo en fuente de verdad.
Definir evento ProductIndexed o cambios de catálogo.
Crear outbox en la transacción del catálogo.
Construir índice nuevo.
Ejecutar backfill.
Comparar resultados con queries actuales.
Activar para tenants internos.
Migrar porcentaje de lecturas.
Observar staleness, errores y relevancia.
Mantener fallback temporal.
Retirar queries antiguas cuando la nueva ruta sea estable.
Catálogo sigue siendo autoridad.
El índice puede reconstruirse.
Se mide lag.
Las claves incluyen tenant.
Un fallo de búsqueda no modifica productos.
Este caso es más sencillo que migrar inventario porque el índice es derivado.
Más complejo porque existen escrituras e invariantes.
Centralizar todas las escrituras mediante un puerto.
Definir operaciones de negocio.
Revocar escrituras directas progresivamente.
Crear modelo nuevo y backfill.
Propagar cambios mediante outbox.
Ejecutar comparación y reconciliación.
Mover una sucursal piloto.
Transferir autoridad de escritura.
Migrar el resto.
Revocar permisos del legado y eliminar sincronización.
La fase de transferencia de autoridad debe estar claramente marcada.
Tráfico por camino.
Diferencias de resultado.
Lag.
Errores de transformación.
Registros pendientes.
Tiempo de backfill.
Tenants migrados.
Uso del fallback.
Datos sin reconciliar.
Componentes antiguos todavía llamados.
Una migración sin métricas puede quedar atascada durante años.
Cada fase necesita responsables:
Sistema antiguo.
Sistema nuevo.
Datos.
Routing.
Operación.
Decisión de cutover.
Ownership temporal compartido debe tener fecha de finalización. Si nadie posee retirar el legado, la migración termina añadiendo otro sistema.
Antes de iniciar, define qué significa terminar:
100 % del tráfico migrado.
Datos reconciliados.
Fuente de verdad nueva.
Permisos antiguos revocados.
Fallback eliminado.
Jobs de sincronización retirados.
Infraestructura antigua apagada.
Runbooks actualizados.
Costos y SLO dentro de objetivo.
Sin criterios, “temporal” se vuelve permanente.
El sistema nuevo evoluciona aislado y el cutover concentra todo el riesgo.
Dos autoridades producen divergencia y operación compleja.
No existe forma de saber si el resultado es correcto.
Ignora datos y efectos externos ya realizados.
Mover tablas o clases sin ownership conserva el acoplamiento mediante red.
Mientras existan fallback, sincronización y legado activo, la deuda continúa.
El equipo se mueve a otra prioridad y deja dos sistemas.
Migrar implica comportamiento, datos, tráfico y ownership.
La coexistencia debe ser explícita y temporal.
Siempre debe existir una fuente de verdad definida.
Dual writes necesitan coordinación y reparación.
Shadow traffic y rollout reducen incertidumbre.
Rollback debe considerar estado, no solo código.
Strangler Fig reemplaza capacidades gradualmente.
Una migración termina cuando se retira el camino anterior.
¿Por qué construir el sistema nuevo no completa una migración?
¿Qué diferencia existe entre backfill y CDC?
¿Por qué dual write puede divergir?
¿Qué debe considerar un rollback además del código?
¿Qué criterios usarías para declarar retirada una capacidad legado?
Ver respuestas orientativas
Porque faltan datos, tráfico, ownership, operación y retiro del legado.
Backfill copia historial; CDC propaga cambios nuevos.
Una escritura puede completar y la otra fallar sin una transacción común.
Datos, eventos, efectos externos y compatibilidad del camino anterior.
Tráfico cero, datos reconciliados, autoridad transferida, permisos y componentes retirados.
Conway, Team Topologies y arquitectura sociotécnica explica por qué las fronteras del sistema también dependen de comunicación, ownership y carga cognitiva de los equipos.