Migrar un sistema monolítico en producción hacia una arquitectura de microservicios es una de las maniobras más arriesgadas que afronta un equipo de software. El error clásico es la reescritura completa ("big bang rewrite"): congelar el desarrollo de nuevas funcionalidades, construir el nuevo sistema desde cero durante meses y, el día de la verdad, descubrir que no replica todos los matices del original. La industria ha aprendido a hacerlo de otra manera: de forma incremental, manteniendo el monolito vivo y en producción mientras se le van extrayendo piezas. El patrón que da nombre a esta estrategia es el Strangler Fig (higuera estranguladora), acuñado por Martin Fowler. En esta lección veremos la estrategia incremental, cómo identificar las costuras del monolito, cómo proteger los modelos nuevos con una Anti-Corruption Layer, la técnica de Branch by Abstraction y los riesgos que debes anticipar.

Contenido

  1. El problema de la reescritura "big bang"
  2. El patrón Strangler Fig
  3. Identificar las costuras (seams) del monolito
  4. Branch by Abstraction
  5. Anti-Corruption Layer (ACL)
  6. Estrategia incremental paso a paso
  7. Riesgos y cómo mitigarlos
  8. Errores Comunes y Consejos
  9. Ejercicios
  10. Conclusión

  1. El problema de la reescritura "big bang"

La tentación de tirar el monolito y empezar de cero es enorme: el código viejo "da asco", nadie entiende del todo cómo funciona y el nuevo paradigma promete limpieza. Pero la reescritura total falla casi siempre por las mismas razones:

  • El monolito codifica años de reglas de negocio implícitas. Muchas de esas reglas no están documentadas: viven en if aparentemente absurdos que en realidad resuelven un caso límite real.
  • El negocio no se detiene. Mientras reescribes durante 18 meses, el monolito sigue recibiendo cambios. Persigues un blanco en movimiento.
  • El día del cambio (cutover) es un salto al vacío. Toda la migración de datos y de tráfico ocurre a la vez, con máximo riesgo y mínima reversibilidad.

La alternativa madura es estrangular el monolito poco a poco. Cada paso es pequeño, está en producción y es reversible.

  1. El patrón Strangler Fig

El nombre viene de la higuera estranguladora: una planta que crece alrededor de un árbol anfitrión, lo va envolviendo y, con los años, lo sustituye por completo hasta que el árbol original desaparece. Aplicado al software, la idea es interponer una capa de enrutamiento (normalmente un gateway o proxy) delante del monolito. Esa capa decide, petición a petición, si la atiende el monolito antiguo o un servicio nuevo ya extraído.

graph TD
    Cliente[Cliente] --> GW[Gateway / Strangler Facade]
    GW -->|funcionalidad migrada| MS1[Servicio Pagos nuevo]
    GW -->|funcionalidad migrada| MS2[Servicio Catálogo nuevo]
    GW -->|aún no migrado| Mono[Monolito legado]
    MS1 --> DBN[(BD del servicio)]
    Mono --> DBM[(BD del monolito)]

El diagrama muestra la pieza clave: el Strangler Facade (la fachada o gateway). Al principio, casi todo el tráfico va al monolito. A medida que extraes capacidades, vas redirigiendo rutas concretas (por ejemplo, /pagos/*) hacia el servicio nuevo. El cliente no se entera del cambio porque siempre habla con la misma fachada. Cuando la última ruta abandona el monolito, este queda vacío y se puede apagar.

Ventajas frente al big bang:

  • Entrega continua de valor: cada extracción es un incremento que ya aporta.
  • Riesgo acotado: si un servicio nuevo falla, redirige esa ruta de vuelta al monolito.
  • Sin congelación: el monolito sigue evolucionando en las partes no migradas.

  1. Identificar las costuras (seams) del monolito

Una costura (seam, término de Michael Feathers) es un punto del sistema donde puedes alterar el comportamiento sin editar el código en ese lugar. Para extraer un microservicio necesitas encontrar costuras limpias que delimiten una capacidad de negocio. Las mejores candidatas a salir primero son módulos que cumplen:

Criterio Por qué facilita la extracción
Bajo acoplamiento con el resto Pocas dependencias que romper al separarlo
Alta cohesión interna Forma una capacidad de negocio completa por sí misma
Datos relativamente independientes Su base de datos no está enredada con todas las tablas
Alto valor o alto cambio Justifica el esfuerzo: o escala distinto, o cambia mucho
Riesgo controlable Si falla, no tumba todo el negocio

El peor candidato para empezar es el módulo central que toca todo (por ejemplo, "Cliente" en muchas empresas). Empieza por los pétalos, no por el corazón.

// ANTES: el monolito llama directamente a su clase interna
public class ProcesadorPedido {
    private final CalculadoraEnvio calculadora = new CalculadoraEnvio(); // dependencia rígida

    public Pedido procesar(Pedido p) {
        double coste = calculadora.calcular(p.getDestino(), p.getPeso());
        p.setCosteEnvio(coste);
        return p;
    }
}

Aquí no hay costura: ProcesadorPedido instancia directamente CalculadoraEnvio con new. Para poder sustituir el cálculo de envío por un servicio remoto, primero tenemos que crear una costura. Eso nos lleva a la siguiente técnica.

  1. Branch by Abstraction

Branch by Abstraction es una técnica para hacer un cambio grande en pequeños pasos sin usar ramas de larga duración en el control de versiones. La idea: introduces una abstracción (una interfaz) entre el consumidor y la implementación actual; luego añades una segunda implementación detrás de la misma abstracción; finalmente conmutas de una a otra y borras la vieja.

// PASO 1: extraer una abstracción (interfaz) sobre la capacidad
public interface ServicioEnvio {
    double calcular(Direccion destino, double peso);
}

// PASO 2: la implementación actual (legada) cumple la interfaz, sin cambiar su lógica
public class ServicioEnvioLocal implements ServicioEnvio {
    private final CalculadoraEnvio calculadora = new CalculadoraEnvio();
    public double calcular(Direccion destino, double peso) {
        return calculadora.calcular(destino, peso);
    }
}

// PASO 3: el consumidor depende de la abstracción, no de la clase concreta
public class ProcesadorPedido {
    private final ServicioEnvio envio; // inyectado
    public ProcesadorPedido(ServicioEnvio envio) { this.envio = envio; }

    public Pedido procesar(Pedido p) {
        p.setCosteEnvio(envio.calcular(p.getDestino(), p.getPeso()));
        return p;
    }
}

Vamos paso a paso por qué esto habilita la migración:

  • Paso 1 crea la "rama por abstracción": ServicioEnvio es ahora el punto de conmutación. Toda la diferencia entre el viejo y el nuevo mundo pasará por esta interfaz.
  • Paso 2 envuelve la lógica existente sin tocarla. El sistema sigue comportándose igual; solo hemos interpuesto una capa fina.
  • Paso 3 invierte la dependencia: ProcesadorPedido ya no sabe cómo se calcula el envío, solo que existe un ServicioEnvio.

Ahora podemos añadir una nueva implementación que llame por red al microservicio:

// PASO 4: nueva implementación que delega en el microservicio remoto
public class ServicioEnvioRemoto implements ServicioEnvio {
    private final ClienteHttp cliente;
    public ServicioEnvioRemoto(ClienteHttp cliente) { this.cliente = cliente; }

    public double calcular(Direccion destino, double peso) {
        // llamada HTTP al nuevo servicio de envíos
        return cliente.post("/envios/calcular",
                Map.of("destino", destino, "peso", peso), Double.class);
    }
}

Con un feature flag decides qué implementación se inyecta. Puedes conmutar para el 1% del tráfico, observar, y subir gradualmente. Si algo va mal, vuelves a ServicioEnvioLocal al instante. Cuando el 100% del tráfico use la versión remota durante un tiempo prudencial, eliminas la implementación local. La "rama" se ha fusionado sin dramas.

  1. Anti-Corruption Layer (ACL)

El monolito legado suele tener un modelo de datos viejo, con nombres confusos, campos reutilizados para varias cosas y conceptos que ya no encajan con el negocio actual. Si el microservicio nuevo adopta ese modelo tal cual, hereda la corrupción. La Anti-Corruption Layer (capa anticorrupción, término de Eric Evans en DDD) es una capa de traducción que aísla el modelo limpio del servicio nuevo del modelo sucio del legado.

graph LR
    MSnuevo[Servicio nuevo\nmodelo limpio] --> ACL[Anti-Corruption Layer\ntraducción]
    ACL --> Legado[Monolito / sistema legado\nmodelo antiguo]
// Modelo limpio del servicio nuevo
public record Cliente(String id, String nombreCompleto, NivelFidelidad nivel) {}

// La ACL traduce del formato legado al modelo limpio
public class ClienteAcl {

    private final ClienteLegadoApi legado;
    public ClienteAcl(ClienteLegadoApi legado) { this.legado = legado; }

    public Cliente obtener(String id) {
        // El legado devuelve un mapa con nombres crípticos: CL_NOM, CL_AP1, CL_TIP
        Map<String,String> raw = legado.fetch(id);
        String nombre = raw.get("CL_NOM") + " " + raw.get("CL_AP1");
        // "TIP=3" en el legado significa cliente VIP: traducimos a nuestro enum
        NivelFidelidad nivel = "3".equals(raw.get("CL_TIP"))
                ? NivelFidelidad.VIP : NivelFidelidad.ESTANDAR;
        return new Cliente(id, nombre.trim(), nivel);
    }
}

Qué conseguimos con esta ACL:

  • El servicio nuevo nunca ve CL_NOM ni CL_TIP. Trabaja con Cliente, nombreCompleto y un enum NivelFidelidad claro.
  • Toda la "fealdad" del legado queda encapsulada en un único lugar. Si el legado cambia, solo retocas la ACL.
  • Es un punto natural de borrado: cuando el legado muera, eliminas la ACL y el modelo limpio sobrevive intacto.

La ACL tiene un coste (código y latencia de traducción), pero protege la inversión en el modelo nuevo. Es casi obligatoria al integrar con sistemas heredados que no controlas.

  1. Estrategia incremental paso a paso

Una secuencia típica y segura para estrangular un monolito:

  1. Pon una fachada delante del monolito (gateway/proxy). De momento reenvía el 100% al monolito. No cambia nada funcional, pero crea el punto de conmutación.
  2. Añade observabilidad: logs, métricas y trazas distribuidas. No puedes migrar a ciegas; necesitas medir latencia y errores antes y después.
  3. Elige la primera capacidad según los criterios de costuras (bajo acoplamiento, datos separables).
  4. Aplica Branch by Abstraction dentro del monolito para esa capacidad.
  5. Construye el microservicio con su propia base de datos. Si toca datos del legado, separa los datos (replicación o doble escritura temporal).
  6. Pon una ACL si el modelo del legado contamina.
  7. Conmuta tráfico gradualmente con feature flags; observa; revierte si hace falta.
  8. Elimina el código muerto del monolito una vez estabilizado.
  9. Repite con la siguiente capacidad hasta vaciar el monolito.

La separación de datos suele ser la parte más dura. Técnicas habituales: vistas de solo lectura sobre la BD del monolito al principio, luego doble escritura, y finalmente la BD propia del servicio como fuente de verdad.

  1. Riesgos y cómo mitigarlos

Riesgo Mitigación
Transacciones que cruzaban módulos ya no son ACID Patrón Saga, consistencia eventual y diseño de compensaciones
Latencia: lo que era llamada en memoria ahora es red Medir, agrupar llamadas, cachear; no extraer si el chatter es altísimo
Base de datos compartida que impide separar Identificar tablas por dominio, vistas, doble escritura progresiva
El monolito "zombi" que nunca termina de morir Definir métrica de progreso (rutas migradas) y fecha de apagado
Explosión de complejidad operativa Invertir antes en CI/CD, observabilidad y plataforma
Fronteras mal elegidas Validar con DDD (Bounded Contexts) antes de cortar

El riesgo más subestimado es el de consistencia de datos: dentro del monolito una transacción local lo resolvía todo; en lo distribuido necesitas sagas y aceptar consistencia eventual. No extraigas un servicio si su capacidad participa en transacciones fuertemente acopladas con otras, salvo que estés preparado para gestionar la compensación.

  1. Errores Comunes y Consejos

  • Empezar por el módulo central. Es el más enredado. Empieza por los periféricos para ganar experiencia y confianza.
  • Migrar sin observabilidad. Sin métricas y trazas no sabrás si el servicio nuevo es mejor o peor. Instrumenta antes de cortar.
  • Compartir la base de datos "temporalmente" para siempre. Una BD compartida entre el monolito y el servicio nuevo es un microservicio distribuido en lo peor de ambos mundos. Ten un plan real de separación.
  • No usar feature flags. Sin conmutación gradual ni reversión, cada extracción vuelve a ser un mini big bang.
  • Olvidar borrar el código viejo. Si no eliminas la implementación legada tras conmutar, acumulas deuda y confusión.
  • Consejo: trata cada extracción como un experimento con hipótesis ("este servicio bajará la latencia de pagos un 30%"). Si no se cumple, plantéate revertir.

  1. Ejercicios

Ejercicio 1. Tienes un monolito de comercio electrónico con los módulos: Catálogo, Carrito, Pagos, Cliente y Recomendaciones. ¿Cuál extraerías primero y cuál dejarías para el final? Justifícalo con los criterios de costuras.

Ejercicio 2. El módulo de "Notificaciones" instancia directamente new EmailSender() en 12 sitios. Describe cómo aplicarías Branch by Abstraction para poder sustituirlo por un microservicio de notificaciones, paso a paso.

Ejercicio 3. El servicio nuevo de "Facturación" debe leer clientes del legado, cuya tabla usa la columna EST con valores A, B, C que significan activo, baja temporal y baja definitiva. Explica qué pieza usarías y por qué, y esboza la traducción.

Soluciones

Solución 1. Primero Recomendaciones: bajo acoplamiento (es un añadido), datos relativamente independientes, y si falla no rompe la compra. Después Catálogo o Pagos. Cliente al final: es el módulo central, casi todo depende de él, y separar sus datos es lo más arriesgado.

Solución 2. (1) Crear la interfaz ServicioNotificaciones. (2) Implementarla con NotificacionesLocal que envuelva el EmailSender actual sin cambiar su lógica. (3) Reemplazar las 12 instanciaciones por inyección de ServicioNotificaciones. (4) Crear NotificacionesRemoto que llame por red al microservicio. (5) Conmutar con feature flag de forma gradual. (6) Borrar NotificacionesLocal y EmailSender cuando el 100% use la versión remota de forma estable.

Solución 3. Una Anti-Corruption Layer, para que el servicio de Facturación no herede la columna críptica EST. La ACL traduce: AEstadoCliente.ACTIVO, BEstadoCliente.BAJA_TEMPORAL, CEstadoCliente.BAJA_DEFINITIVA, exponiendo un enum claro al modelo limpio. Si mañana el legado cambia los códigos, solo se toca la ACL.

  1. Conclusión

Migrar de monolito a microservicios no es un salto, sino un goteo. El patrón Strangler Fig mantiene el sistema vivo y en producción mientras se le extraen capacidades una a una a través de una fachada de enrutamiento. Hemos visto cómo identificar costuras limpias, cómo crearlas con Branch by Abstraction y feature flags, cómo proteger el modelo nuevo con una Anti-Corruption Layer, y cuáles son los riesgos —sobre todo los de datos y consistencia— que hay que anticipar. Todo cambio incremental de este calibre genera muchas decisiones que conviene registrar y justificar para que el equipo no las olvide ni las repita. Precisamente de eso trata la siguiente lección: la Gobernanza Arquitectónica y los Architecture Decision Records (ADR), donde aprenderemos a documentar y gobernar las decisiones de arquitectura de forma ligera y sostenible.

Curso de Arquitectura de Aplicaciones

Módulo 1: Fundamentos de la Arquitectura de Aplicaciones

Módulo 2: Principios y Tácticas de Diseño

Módulo 3: Estilos y Patrones Arquitectónicos

Módulo 4: Arquitecturas Distribuidas y Microservicios

Módulo 5: Arquitecturas Dirigidas por Eventos y Mensajería

Módulo 6: Diseño Dirigido por el Dominio (DDD)

Módulo 7: Datos y Persistencia

Módulo 8: Arquitectura en la Nube y Despliegue

Módulo 9: Calidad, Seguridad y Observabilidad

Módulo 10: Evolución, Gobernanza y Casos Prácticos

© Copyright 2026. Todos los derechos reservados