Patrón Strangler Fig

6 min read

Tu empresa tiene un monolito de diez años. Funciona, factura, y nadie quiere tocarlo porque cada cambio es una apuesta.

Alguien propone “reescribirlo todo desde cero con microservicios”. El plan: dos años de desarrollo en paralelo, y un día de lanzamiento donde se apaga lo viejo y se enciende lo nuevo.

Esa estrategia, conocida como Big Bang rewrite (reescritura en bloque), tiene un historial pésimo.

Mientras el equipo construye lo nuevo, el monolito sigue evolucionando y la meta se mueve. Después de dos años, el sistema nuevo todavía no hace todo lo que hacía el viejo, y el día del lanzamiento todos contienen la respiración.

El patrón Strangler Fig (Higuera Estranguladora) propone lo contrario: migrar de a poco, sin apagar nunca lo existente hasta que ya no haga falta.


El origen del nombre

Martin Fowler tomó el nombre de la higuera estranguladora, una planta que crece alrededor de un árbol existente.

Empieza como una enredadera y con los años lo envuelve por completo, hasta que el árbol original muere y queda la higuera en su lugar.

Aplicado a software: construís funcionalidad nueva alrededor del sistema viejo, la vas tomando pieza por pieza, y el monolito se va “estrangulando” hasta poder apagarlo.


El diagrama


Guía paso a paso

Paso 1 — Poner una fachada delante del monolito
Un API Gateway, Nginx o un proxy inverso por el que pasa todo el tráfico. Al principio redirige el 100% de las solicitudes al monolito, sin cambiar nada del comportamiento. Este paso es el más importante: sin él no hay forma de migrar gradualmente.

Paso 2 — Elegir una funcionalidad acotada para migrar primero
Buscá algo con límites claros y bajo riesgo: facturación, notificaciones, el catálogo. Evitá empezar por el núcleo más enredado del sistema.

Paso 3 — Construir el servicio nuevo
Con su propia base de datos y su propio despliegue, implementando la misma funcionalidad que el módulo viejo.

Paso 4 — Redirigir esa ruta en la fachada
Un cambio de configuración, no de código del monolito. Idealmente de forma gradual (primero 5% del tráfico, luego más) y con la posibilidad de volver atrás en segundos.

Paso 5 — Repetir hasta vaciar el monolito
Cuando ya no recibe tráfico, se apaga. Recién ahí, y no antes, se borra el código viejo.


El código

Simulamos una fachada de enrutamiento que decide por prefijo de ruta si una solicitud va al monolito o a un servicio nuevo, incluyendo un rollback cuando se detecta un problema.


import lombok.AllArgsConstructor;
import java.util.LinkedHashMap;
import java.util.Map;

// --- Cualquier destino que pueda atender una solicitud ---

interface Backend {
    String handle(String path);
}

class LegacyMonolith implements Backend {
    @Override
    public String handle(String path) {
        return "[MONOLITO] " + path;
    }
}

@AllArgsConstructor
class NewService implements Backend {
    private final String serviceName;

    @Override
    public String handle(String path) {
        return "[" + serviceName + "] " + path;
    }
}

// --- La fachada: decide por ruta a dónde va cada solicitud ---

@AllArgsConstructor
class StranglerRouter {
    private final Backend legacy;
    private final Map<String, Backend> migratedRoutes = new LinkedHashMap<>();

    public void migrate(String pathPrefix, Backend newBackend) {
        migratedRoutes.put(pathPrefix, newBackend);
        System.out.println(">> Ruta " + pathPrefix + " migrada al servicio nuevo");
    }

    public void rollback(String pathPrefix) {
        migratedRoutes.remove(pathPrefix);
        System.out.println(">> ROLLBACK: " + pathPrefix + " vuelve al monolito");
    }

    public String route(String path) {
        return migratedRoutes.entrySet().stream()
                .filter(entry -> path.startsWith(entry.getKey()))
                .map(Map.Entry::getValue)
                .findFirst()
                .orElse(legacy) // lo que no se migró sigue en el monolito
                .handle(path);
    }
}

// --- Main ---

public class StranglerFigDemo {

    public static void main(String[] args) {
        StranglerRouter router = new StranglerRouter(new LegacyMonolith());
        String[] requests = {"/invoices/1001", "/customers/55", "/reports/monthly"};

        printRoutes("ETAPA 0: todo en el monolito", router, requests);

        router.migrate("/invoices", new NewService("billing-service"));
        printRoutes("ETAPA 1: facturación migrada", router, requests);

        router.migrate("/customers", new NewService("customer-service"));
        printRoutes("ETAPA 2: clientes migrados", router, requests);

        // En producción se detecta un problema en facturación: volvemos atrás en segundos
        router.rollback("/invoices");
        printRoutes("ETAPA 3: rollback de facturación", router, requests);
    }

    static void printRoutes(String title, StranglerRouter router, String... paths) {
        System.out.println("\n=== " + title + " ===");
        for (String path : paths) {
            System.out.println(router.route(path));
        }
    }
}

Output esperado:


=== ETAPA 0: todo en el monolito ===
[MONOLITO] /invoices/1001
[MONOLITO] /customers/55
[MONOLITO] /reports/monthly

>> Ruta /invoices migrada al servicio nuevo

=== ETAPA 1: facturación migrada ===
[billing-service] /invoices/1001
[MONOLITO] /customers/55
[MONOLITO] /reports/monthly

>> Ruta /customers migrada al servicio nuevo

=== ETAPA 2: clientes migrados ===
[billing-service] /invoices/1001
[customer-service] /customers/55
[MONOLITO] /reports/monthly

>> ROLLBACK: /invoices vuelve al monolito

=== ETAPA 3: rollback de facturación ===
[MONOLITO] /invoices/1001
[customer-service] /customers/55
[MONOLITO] /reports/monthly

Fijate lo que pasó en la etapa 3: ante un problema en el servicio nuevo, volver atrás fue un cambio de configuración de una línea, no un despliegue de emergencia. Esa es la ventaja central del patrón: cada paso es pequeño y reversible.


Para tener en cuenta en producción

  • La parte difícil son los datos, no el enrutamiento: el servicio nuevo necesita sus datos, y el monolito sigue escribiendo los suyos. Durante la transición tenés que sincronizar ambos lados. Evitá las escrituras dobles ingenuas: el Outbox Pattern que vimos antes es una buena forma de propagar cambios de forma confiable.
  • Usá una capa anticorrupción: el modelo del monolito suele estar lleno de decisiones históricas. Una capa que traduzca entre ambos modelos evita que esas decisiones contaminen el servicio nuevo.
  • Combiná el enrutamiento con porcentajes: en lugar de pasar el 100% del tráfico de una vez, empezá con un 5%, mirá errores y latencias, y subí gradualmente. Los Feature Flags son una excelente herramienta para esto.
  • Herramientas reales para la fachada: Spring Cloud Gateway, Nginx, Kong, o un API Gateway de tu proveedor cloud permiten hacer este enrutamiento por ruta con configuración declarativa.
  • No migres por moda: si el monolito funciona bien y tu equipo es chico, un monolito bien organizado puede ser mejor opción que diez microservicios. Migrá cuando tengas un dolor concreto, como despliegues lentos o equipos que se pisan.

Conclusión

La reescritura total promete un futuro limpio pero esconde un riesgo enorme concentrado en un solo día.

Strangler Fig reparte ese riesgo en decenas de cambios pequeños, cada uno reversible, mientras el negocio sigue funcionando con normalidad.

Es menos emocionante que empezar de cero, pero en producción lo aburrido y reversible casi siempre gana.

Recordá suscribirte aquí para recibir los próximos posts directamente en tu correo.


Referencias:
Martin Fowler — Strangler Fig Application: https://martinfowler.com/bliki/StranglerFigApplication.html
Monolith to Microservices — Sam Newman

Leave a Reply

Your email address will not be published. Required fields are marked *