Módulo 5: Migración de Frameworks y Lenguajes

Strangler Fig Pattern — Coexistencia Old/New

Strangler Fig Pattern — Coexistencia Old/New

Descripción de la cápsula

En las cápsulas anteriores migraste endpoints de Flask a FastAPI de forma secuencial. Pero en un proyecto real con tráfico en producción, no puedes pausar el servicio mientras migras. Necesitas que old y new coexistan: algunos endpoints sirven desde Flask, otros desde FastAPI, y el tráfico se mueve gradualmente hasta que Flask se puede eliminar.

Este patrón se llama Strangler Fig (higuera estranguladora), inspirado en plantas tropicales que crecen alrededor de un árbol existente hasta que eventualmente lo reemplazan. Tu app FastAPI crece alrededor de la app Flask hasta que Flask ya no es necesaria.

En esta cápsula vas a implementar coexistencia real: un proxy que enruta requests a Flask o FastAPI según el estado de la migración. Es el patrón más seguro para migraciones en producción.


El Concepto

Cómo funciona Strangler Fig

Fase 1: Todo va a Flask
┌─────────┐     ┌──────────┐
│  Proxy   │────▶│  Flask   │  (100% Flask)
└─────────┘     └──────────┘

Fase 2: Algunos endpoints migrados
┌─────────┐     ┌──────────┐
│  Proxy   │──┬─▶│  Flask   │  (60% Flask)
└─────────┘  │  └──────────┘
              │  ┌──────────┐
              └─▶│ FastAPI  │  (40% FastAPI)
                 └──────────┘

Fase 3: Migración completa
                 ┌──────────┐
                 │ FastAPI  │  (100% FastAPI)
                 └──────────┘

Implementación con un proxy simple

# proxy.py — Enrutador de migración
from fastapi import FastAPI, Request
import httpx

app = FastAPI(title="Migration Proxy")

# Configuración: qué endpoints van a qué backend
FASTAPI_ROUTES = {
    "GET /health",
    "GET /users",
    "GET /users/{id}",
    # Agregar endpoints a medida que se migran
}

FLASK_URL = "http://localhost:5000"
FASTAPI_URL = "http://localhost:8000"

@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def proxy(request: Request, path: str):
    route_key = f"{request.method} /{path}"
    
    # Determinar backend
    target = FASTAPI_URL if route_key in FASTAPI_ROUTES else FLASK_URL
    
    # Forward request
    async with httpx.AsyncClient() as client:
        response = await client.request(
            method=request.method,
            url=f"{target}/{path}",
            headers=dict(request.headers),
            content=await request.body(),
        )
    
    return Response(
        content=response.content,
        status_code=response.status_code,
        headers=dict(response.headers),
    )

Implementación Paso a Paso

Paso 1: Setup de coexistencia

# Terminal 1: Flask (puerto 5000)
flask run --port 5000

# Terminal 2: FastAPI (puerto 8000)
uvicorn fastapi_app:app --port 8000

# Terminal 3: Proxy (puerto 3000 — el que ven los clientes)
uvicorn proxy:app --port 3000

Paso 2: Migrar un endpoint y registrarlo

# Después de migrar GET /health a FastAPI:
FASTAPI_ROUTES = {
    "GET /health",  # ← Agregar aquí
}
# El proxy ahora envía GET /health a FastAPI
# Todo lo demás sigue yendo a Flask

Paso 3: Verificar y mover al siguiente

# Test de equivalencia via proxy:
> "Envía GET /health al proxy (puerto 3000) y verifica
   que la respuesta viene de FastAPI"

# Si funciona, migrar el siguiente endpoint
# Si falla, revertir: quitar de FASTAPI_ROUTES

Approach Simplificado (Sin Proxy)

Para proyectos internos o de menor escala, puedes usar un approach más simple: montar ambas apps en el mismo proceso.

# combined_app.py — Flask y FastAPI juntas
from fastapi import FastAPI
from flask import Flask
from a2wsgi import WSGIMiddleware

# FastAPI como app principal
fastapi_app = FastAPI()

# Flask como fallback para endpoints no migrados
flask_app = Flask(__name__)

# Montar Flask dentro de FastAPI
fastapi_app.mount("/legacy", WSGIMiddleware(flask_app))

# Endpoints migrados van directo en FastAPI
@fastapi_app.get("/health")
def health():
    return {"status": "healthy", "framework": "fastapi"}

# Endpoints no migrados siguen en Flask (bajo /legacy)
@flask_app.route("/orders", methods=["POST"])
def create_order():
    # ... lógica Flask original
    pass

Estrategias de Cutover

Cuándo eliminar Flask

Checklist para el cutover final:

  • 100% de endpoints migrados a FastAPI
  • 100% de tests de equivalencia pasan
  • Proxy envía 0 requests a Flask (monitoreo)
  • No hay código que dependa de Flask-specific features
  • Team alignment: todos saben que Flask se elimina

El cutover

# Paso 1: Verificar que todo el tráfico va a FastAPI
> "Verifica que FASTAPI_ROUTES contiene todos los endpoints
   y que no queda ningún endpoint servido por Flask."

# Paso 2: Eliminar Flask
> "Elimina flask_app.py, el proxy, y las dependencias
   de Flask del requirements.txt. FastAPI es la única app."

# Paso 3: Tests finales
> "Ejecuta toda la suite de tests. Confirma green."

Conexión con Proyecto

En el Proyecto del Módulo (cápsula 06), implementas strangler fig si el proyecto tiene endpoints que no puedes migrar todos de golpe. Para la escala del proyecto (4-6 endpoints), puede ser opcional — pero para tu trabajo profesional es esencial.


Troubleshooting

Problema 1: El proxy agrega latencia

Solución: Normal. En desarrollo es aceptable. En producción, usa un reverse proxy como nginx o un API gateway.

Problema 2: Headers se pierden en el proxy

Solución: Forward todos los headers:

headers=dict(request.headers)

Problema 3: No sé cuándo hacer el cutover

Solución: Cuando monitoreo muestra 0 requests a Flask por 24 horas+ y todos los tests pasan.


Ejercicios

Ejercicio 1: Diseñar un plan de strangler fig (Fácil)

Tienes 8 endpoints. Diseña el orden de migración y en qué fase agregas cada uno a FASTAPI_ROUTES.

Ver solución
Phase 1: GET /health → FASTAPI_ROUTES
Phase 2: GET /users, GET /products → FASTAPI_ROUTES
Phase 3: POST /users, PUT /users → FASTAPI_ROUTES
Phase 4: POST /orders (más complejo) → FASTAPI_ROUTES
Phase 5: DELETE /users, DELETE /orders → FASTAPI_ROUTES
Phase 6: Cutover → eliminar Flask

Ejercicio 2: Implementar routing condicional (Medio)

Modifica el proxy para que use feature flags en vez de lista estática. Si MIGRATE_USERS=true, los endpoints de users van a FastAPI.

Ver solución
import os

FEATURE_FLAGS = {
    "users": os.getenv("MIGRATE_USERS", "false") == "true",
    "orders": os.getenv("MIGRATE_ORDERS", "false") == "true",
}

def get_backend(path: str) -> str:
    if path.startswith("/users") and FEATURE_FLAGS["users"]:
        return FASTAPI_URL
    if path.startswith("/orders") and FEATURE_FLAGS["orders"]:
        return FASTAPI_URL
    return FLASK_URL

Feature flags permiten activar/desactivar la migración sin deploy.


Errores Comunes en Strangler Fig

Error 1: Migrar el endpoint más complejo primero

Síntoma: El primer endpoint migrado bloquea la migración por semanas porque tiene dependencias enredadas.

Por qué pasa: El instinto es "atacar el problema grande primero". En migraciones, el orden óptimo es lo más simple primero — /health, /version, endpoints sin dependencias. Esto valida el setup del proxy y construye confianza antes de tocar lógica de negocio.

Cómo corregir: Lista todos los endpoints. Ordena por complejidad (sin dependencias → con DB → con servicios externos → con state). Migra en ese orden.

Error 2: No monitorear qué backend sirve cada request

Síntoma: Crees que migraste /users pero el proxy sigue mandando 30% del tráfico a Flask por una regla mal configurada.

Por qué pasa: Sin métricas, el FASTAPI_ROUTES puede tener un bug y nunca te enteras hasta el cutover. Y en el cutover ya es tarde.

Cómo corregir: Agrega logging al proxy con backend usado por request. Después dashboarda: requests_to_flask y requests_to_fastapi por endpoint. Antes del cutover, los counts a Flask deben ser cero por días, no minutos.

Error 3: Usar el proxy como solución permanente

Síntoma: La migración "terminó" hace 3 meses pero el proxy sigue corriendo "por si acaso". Latencia extra y deuda operativa.

Por qué pasa: El cutover requiere decisión y un poco de coraje. Mantener el proxy se siente "seguro" pero acumula complejidad.

Cómo corregir: El cutover es parte del plan, no opcional. Define explícitamente la fecha límite ("dos semanas con 0 requests a Flask = cutover") y cúmplela. La cápsula 05 te da los criterios de equivalencia para tomar la decisión con datos.

Error 4: Compartir state entre Flask y FastAPI sin pensarlo

Síntoma: Sessions inconsistentes, race conditions, datos que aparecen en una app pero no en otra.

Por qué pasa: Si Flask y FastAPI comparten DB pero usan distintos session managers, ORMs o caches, pueden tener vistas inconsistentes del mismo data.

Cómo corregir: Decide explícitamente: comparten DB con la misma config, comparten cache, o cada uno tiene el suyo. Documenta esa decisión. Tests de equivalencia (cápsula 05) deben cubrir state-sharing.

Error 5: No tener un plan de rollback

Síntoma: Migraste /orders, algo falla en producción, y no sabes cómo volver a Flask en 5 minutos.

Por qué pasa: El proxy hace la migración fácil — pero el rollback también debe ser fácil. Sin proceso, en una crisis todos improvisan.

Cómo corregir: Documenta el rollback como un cambio de una línea: FASTAPI_ROUTES = FASTAPI_ROUTES - {"GET /orders"}. Practica el rollback antes de migrar el endpoint a producción. Si tarda más de 5 minutos, el plan está incompleto.


Resumen

  • Strangler Fig permite que old y new coexistan durante la migración
  • Proxy-based: un router decide qué backend sirve cada request
  • Simplified: montar Flask dentro de FastAPI con WSGIMiddleware
  • Cutover cuando 100% del tráfico va a la nueva app y todos los tests pasan
  • Es el patrón más seguro para migraciones en producción
  • El orden importa: lo más simple primero, lo más complejo al final
  • Monitoreo activo + plan de rollback son no-negociables

Próxima cápsula: Migration Testing — tests que verifican equivalencia entre Flask y FastAPI.


Recursos Adicionales

  1. Strangler Fig Pattern - Martin Fowler - La definición original
  2. ASGI/WSGI Middleware - Para montar WSGI apps en ASGI
  3. Feature Flags Best Practices - Feature flags para migraciones
  4. nginx Reverse Proxy - Para proxy en producción
  5. API Gateway Pattern - El patrón más robusto para routing
  6. Blue-Green Deployments - Complemento del strangler fig