El Dolor: Una API Monolítica que Frenaba el Negocio

La API de ventas era el corazón de la operación, y también nuestro mayor cuello de botella. Un monolito Java con años de deuda técnica. Cada despliegue era un evento de ‘cruza los dedos’, la latencia era impredecible y escalar horizontalmente era un dolor de cabeza que consumía recursos masivos. Modificar una simple lógica de negocio podía tomar semanas entre pruebas de regresión y el miedo a romper algo. El time-to-market era terrible. Sabíamos que necesitábamos un cambio radical, pero apagar y reemplazar no era una opción; la operación no podía detenerse ni un segundo.

La Estrategia: El Patrón Strangler Fig al Rescate

En lugar de una reescritura masiva (el famoso ‘big bang’ que casi siempre falla), optamos por una migración gradual y controlada. La elección fue el patrón Strangler Fig (Estrangulador). La idea es simple pero poderosa: construir la nueva funcionalidad en microservicios alrededor del viejo monolito, desviando gradualmente las llamadas de la API una por una hacia los nuevos servicios hasta que el monolito, finalmente, se queda sin responsabilidades y puede ser ‘estrangulado’ y retirado.

Para orquestar esto, pusimos un proxy inverso inteligente al frente de todo. Aquí es donde Apache Camel brilló.

El Stack Técnico: Velocidad y Resiliencia

Seleccionamos un stack pensado para el alto rendimiento y la nube nativa:

  • Quarkus: Lo elegimos sobre Spring Boot por su increíblemente bajo consumo de memoria y su tiempo de arranque casi instantáneo. Esto es oro puro en Kubernetes, donde los pods pueden crearse y destruirse constantemente. Nos permitió tener un escalado automático mucho más rápido y eficiente.
  • Apache Camel: Lo usamos como el cerebro del routing. Implementamos un componente que actuaba como API Gateway, inspeccionando cada request entrante y decidiendo si debía enviarlo al viejo monolito o al nuevo microservicio en Quarkus.
  • Kubernetes (sobre GKE): El orquestador ideal para gestionar el ciclo de vida de nuestros nuevos microservicios, manejar despliegues canary y auto-escalado.
  • Redis: Lo usamos para almacenar la configuración del routing de Camel de forma centralizada y para cachear respuestas, reduciendo aún más la carga sobre los servicios.

Paso 1: Identificar y Aislar el Primer Endpoint

Comenzamos con un endpoint de bajo riesgo pero de alto tráfico: la consulta de catálogo de productos. Era mayormente de lectura, lo que simplificaba la migración al no tener que lidiar con transacciones complejas al principio.

Paso 2: Construir el Microservicio con Quarkus

Creamos un servicio Quarkus dedicado exclusivamente a gestionar el catálogo. La experiencia de desarrollo fue increíblemente fluida gracias a la recarga en caliente y las herramientas de Quarkus.

// ProductResource.java en nuestro nuevo microservicio

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/products")
public class ProductResource {

    // Inyectamos nuestro servicio de catálogo (lógica de negocio)
    private final ProductService productService;

    public ProductResource(ProductService productService) {
        this.productService = productService;
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getProducts() {
        // Lógica para obtener productos desde la nueva fuente de datos
        List<Product> products = productService.findAll();
        return Response.ok(products).build();
    }
}

Paso 3: Configurar el Routing Dinámico en Apache Camel

Esta fue la pieza clave. Creamos una ruta en Camel que, basada en una configuración externa (almacenada en Redis para poder cambiarla en caliente), decidía a dónde enviar la petición /api/products.

// StranglerRoute.java en nuestro Gateway

import org.apache.camel.builder.RouteBuilder;

public class StranglerRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        from("undertow:http://0.0.0.0:8080/api/products?matchOnUriPrefix=true")
            .routeId("product-strangler-route")
            .choice()
                // Leemos una bandera de una property o de Redis
                .when(simple("${config:features.newProductApi.enabled} == 'true'"))
                    .log("Routing to new Quarkus microservice")
                    // Redirigimos al nuevo servicio desplegado en K8s
                    .to("http://product-service.default.svc.cluster.local/products?bridgeEndpoint=true")
                .otherwise()
                    .log("Routing to legacy monolith")
                    // Enviamos la petición al viejo monolito
                    .to("http://legacy-monolith-api/api/products?bridgeEndpoint=true")
            .end();
    }
}

Paso 4: Despliegue Canary y Monitoreo

Usando Kubernetes, desplegamos el nuevo servicio y el gateway. Inicialmente, la bandera en Redis estaba en ‘false’, enviando el 100% del tráfico al monolito. Luego, usando un despliegue canary, empezamos a desviar un 1%, 10%, 50% y finalmente el 100% del tráfico al nuevo microservicio, monitoreando constantemente los dashboards en Grafana para detectar cualquier anomalía en latencia o tasa de errores. Todo el proceso fue completamente transparente para los usuarios.

Lecciones Aprendidas y Conclusión

Migrar un monolito crítico es un maratón, no un sprint. El patrón Strangler Fig nos salvó de un proyecto de alto riesgo. Mis conclusiones clave para tu equipo:

  • No subestimes el routing: El API Gateway es el componente más crítico de esta arquitectura. Dedícale tiempo y asegúrate de que sea robusto y configurable en caliente.
  • Quarkus es un game-changer para microservicios en JVM: La eficiencia en recursos y velocidad de arranque se traduce en ahorro de costos de infraestructura y mayor agilidad.
  • La observabilidad no es negociable: Sin un buen monitoreo (logs, métricas, trazas) con herramientas como Grafana y Prometheus, estarás volando a ciegas.
  • Empieza pequeño: Elige un ‘bounded context’ bien definido y de bajo riesgo para tu primer microservicio. La confianza que ganas con esa primera victoria es fundamental para el equipo.

Partir el monolito no solo resolvió nuestros problemas técnicos; desbloqueó la capacidad del equipo para innovar y entregar valor mucho más rápido. Y eso, al final, es lo que importa.