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
- Strangler Fig Pattern - Martin Fowler - La definición original
- ASGI/WSGI Middleware - Para montar WSGI apps en ASGI
- Feature Flags Best Practices - Feature flags para migraciones
- nginx Reverse Proxy - Para proxy en producción
- API Gateway Pattern - El patrón más robusto para routing
- Blue-Green Deployments - Complemento del strangler fig