Módulo 5: Migración de Frameworks y Lenguajes
Migration Strategy — Plan, Test, Migrate, Validate
Migration Strategy — Plan, Test, Migrate, Validate
Descripción de la cápsula
Antes de cambiar una sola línea de código, necesitas una estrategia. Las migraciones que fracasan no fracasan por problemas técnicos — fracasan por falta de planificación. Un endpoint migrado que rompe producción no es un bug; es una migración sin plan de rollback. Un cambio de framework que toma 6 meses en vez de 6 semanas no es un problema de velocidad; es un problema de scope sin checkpoints.
En esta cápsula vas a aprender el ciclo de migración que previene estos problemas: plan → safety net (tests) → migrate incrementally → validate en cada paso. Es el mismo principio del refactoring (tests primero), pero aplicado a escala de framework.
Claude Code acelera cada fase del ciclo, pero la decisión estratégica — qué migrar primero, cuáles son los checkpoints, cuál es el plan B — es tuya.
El Ciclo de Migración
Los 4 pasos que se repiten
PLAN → TEST → MIGRATE → VALIDATE
↑ |
└─────────────────────────┘
(repetir para cada componente)
PLAN: Decidir qué migrar en esta iteración, en qué orden, y cuál es el criterio de éxito.
TEST: Escribir tests que capturan el comportamiento actual del componente a migrar. Si no hay tests, escribirlos antes de tocar nada.
MIGRATE: Convertir el componente al nuevo framework. Un componente a la vez.
VALIDATE: Ejecutar tests de equivalencia. ¿El componente nuevo produce los mismos resultados que el viejo? Si sí, continuar. Si no, arreglar o revertir.
Fase 1: Assessment — Inventario del Codebase
Qué necesitas saber antes de planificar
# Prompt a Claude Code:
> "Analiza esta aplicación Flask y produce un inventario
de migración:
1. Lista todos los endpoints (method, path, handler)
2. Lista todas las dependencias (pip packages)
3. Lista middleware/decorators usados
4. Identifica patrones Flask-específicos (Blueprint,
g object, before_request, etc.)
5. Evalúa cobertura de tests existente
6. Identifica los endpoints más y menos complejos"
# Output esperado:
# Endpoints: 12 (4 GET, 6 POST, 2 PUT)
# Dependencies: flask, flask-login, flask-cors, sqlalchemy, ...
# Middleware: auth_required (custom), cors, logging
# Flask patterns: Blueprint (3), g.user, before_request
# Tests: 8 tests (67% coverage de endpoints)
# Más complejo: POST /orders (validación + cálculo + pago)
# Menos complejo: GET /health (retorna status)
Clasificar componentes por complejidad
| Componente | Complejidad | Equivalente FastAPI | Orden sugerido |
|---|---|---|---|
| GET /health | Baja | Directo | 1 |
| GET /users | Baja | Directo | 2 |
| POST /users | Media | Pydantic validation | 3 |
| Auth middleware | Media | Depends() | 4 |
| POST /orders | Alta | Pydantic + Depends | 5 |
| Blueprints | Media | APIRouter | 6 |
Regla: migra de menor a mayor complejidad. Los primeros endpoints te dan confianza y momentum. Los complejos al final, cuando ya dominas el nuevo framework.
Fase 2: Migration Plan
Estructura del plan
# Migration Plan: Flask → FastAPI
## Scope
- Migrar 12 endpoints de Flask a FastAPI
- Migrar 3 Blueprints a APIRouter
- Migrar middleware custom a Depends()
- Mantener SQLAlchemy (no cambia)
## Phases
### Phase 1: Foundation (Day 1)
- Setup FastAPI project alongside Flask
- Migrate GET /health (smoke test)
- Checkpoint: FastAPI app runs, /health responds
### Phase 2: Simple Endpoints (Day 2-3)
- Migrate 4 GET endpoints
- Migrate 2 simple POST endpoints
- Checkpoint: 6/12 endpoints migrados, tests green
### Phase 3: Complex Endpoints (Day 4-5)
- Migrate auth middleware to Depends()
- Migrate POST /orders (más complejo)
- Migrate remaining endpoints
- Checkpoint: 12/12 endpoints migrados, tests green
### Phase 4: Cleanup (Day 6)
- Remove Flask code
- Update dependencies
- Final test suite run
- Checkpoint: Solo FastAPI, all tests pass
## Rollback Strategy
- Cada phase tiene rollback a la phase anterior
- Git tags en cada checkpoint
- Si Phase 3 falla: revert a Phase 2, investigar
- Worst case: revert a pre-migration (Flask funcional)
## Dependencies to Change
- flask → fastapi + uvicorn
- flask-cors → fastapi-cors (middleware)
- flask-login → custom JWT con python-jose
- Mantener: sqlalchemy, pytest, etc.
Generando el plan con Claude Code
# Prompt:
> "Basándote en el inventario de esta app Flask, genera
un migration plan a FastAPI. Organiza en phases de
complejidad creciente. Cada phase tiene checkpoint,
criterio de éxito, y rollback strategy. Incluye
mapping de dependencias Flask→FastAPI."
Fase 3: Safety Net — Tests Antes de Migrar
Tests de equivalencia
La pregunta central de toda migración: "¿El código nuevo hace exactamente lo mismo que el viejo?"
# tests/test_migration_equivalence.py
import pytest
from flask_app import app as flask_app
from fastapi_app import app as fastapi_app
from fastapi.testclient import TestClient
from flask.testing import FlaskClient
flask_client = flask_app.test_client()
fastapi_client = TestClient(fastapi_app)
class TestMigrationEquivalence:
"""Verifica que Flask y FastAPI producen las mismas respuestas."""
def test_get_health_equivalence(self):
flask_response = flask_client.get("/health")
fastapi_response = fastapi_client.get("/health")
assert flask_response.status_code == fastapi_response.status_code
assert flask_response.json == fastapi_response.json()
def test_create_user_equivalence(self):
data = {"name": "Ana", "email": "ana@test.com"}
flask_response = flask_client.post("/users", json=data)
fastapi_response = fastapi_client.post("/users", json=data)
assert flask_response.status_code == fastapi_response.status_code
# Comparar structure, no IDs (pueden diferir)
flask_body = flask_response.json
fastapi_body = fastapi_response.json()
assert flask_body["name"] == fastapi_body["name"]
assert flask_body["email"] == fastapi_body["email"]
def test_invalid_input_equivalence(self):
"""Ambas versiones rechazan el mismo input inválido."""
bad_data = {"name": "", "email": "not-email"}
flask_response = flask_client.post("/users", json=bad_data)
fastapi_response = fastapi_client.post("/users", json=bad_data)
# Ambas deben retornar 4xx
assert flask_response.status_code == fastapi_response.status_code
Fase 4: Migrate — Un Componente a la Vez
El anti-patrón: Big Bang
# PELIGROSO: migrar todo de una vez
Day 1: Reescribir toda la app en FastAPI
Day 2: "¿Por qué nada funciona?"
Day 3-30: Debugging interminable
El patrón correcto: Incremental
# SEGURO: migrar endpoint por endpoint
Day 1: GET /health → FastAPI ✅ (test green)
Day 1: GET /users → FastAPI ✅ (test green)
Day 2: POST /users → FastAPI ✅ (test green)
Day 2: GET /users/:id → FastAPI ✅ (test green)
...
Day 5: POST /orders → FastAPI ✅ (test green)
Day 6: Eliminar Flask ✅ (all tests green)
Cada paso es reversible. Cada paso tiene verificación. Si el día 3 algo falla, sabes que es el endpoint que migraste el día 3 — no tienes que buscar en 12 endpoints.
Comparación: Estrategias de Migración
| Criterio | Big Bang | Incremental | Strangler Fig |
|---|---|---|---|
| Velocidad | Rápido al inicio, lento al final | Constante | Constante |
| Riesgo | Muy alto | Bajo | Muy bajo |
| Rollback | Todo o nada | Por endpoint | Por endpoint + routing |
| Coexistencia | No | Temporal | Diseñada |
| Testing | Al final | En cada paso | En cada paso |
| Cuándo usar | Nunca (en este contexto) | Proyectos internos | APIs públicas, prod |
Conexión con Proyecto
En el Proyecto del Módulo (cápsula 06) vas a ejecutar todo este ciclo: assessment → plan → tests → migrate → validate. El plan que diseñas aquí es el blueprint de la ejecución práctica.
Troubleshooting
Problema 1: No sé en qué orden migrar
Solución: Complejidad creciente: health check → GETs simples → POSTs simples → endpoints con auth → endpoints con lógica compleja.
Problema 2: El plan es demasiado detallado para 6 endpoints
Solución: Proporcional a la complejidad. 6 endpoints = plan de 1 página. 60 endpoints = plan de 5 páginas con phases.
Problema 3: ¿Cuándo hago el cutover de Flask a FastAPI?
Solución: Cuando el 100% de los endpoints están migrados Y el 100% de los tests de equivalencia pasan. No antes.
Ejercicios
Ejercicio 1: Clasificar endpoints por complejidad (Fácil)
Clasifica estos 6 endpoints de menor a mayor complejidad de migración:
- GET /api/status — retorna {"status": "ok"}
- POST /api/auth/login — valida credenciales, genera JWT
- GET /api/users — lista usuarios con paginación
- POST /api/orders — valida, calcula, cobra, guarda
- GET /api/products/:id — busca por ID
- PUT /api/users/:id — actualiza con validación
Ver solución
- GET /api/status (Baja — sin lógica)
- GET /api/products/:id (Baja — solo lectura)
- GET /api/users (Media-Baja — paginación)
- PUT /api/users/:id (Media — validación + update)
- POST /api/auth/login (Media-Alta — auth, JWT)
- POST /api/orders (Alta — validación + cálculo + pago)
Orden de migración: 1→2→3→4→5→6
Ejercicio 2: Diseñar migration plan (Medio)
Escribe un migration plan para una app Flask con 8 endpoints, 2 Blueprints, y auth middleware. Usa el formato de la sección "Estructura del plan".
Ver solución
# Migration Plan: Flask → FastAPI
## Phase 1: Foundation (Day 1)
- Setup FastAPI + routers
- Migrate GET /health
- Checkpoint: FastAPI serves /health
## Phase 2: Read Endpoints (Day 2)
- Migrate 3 GET endpoints
- Migrate Blueprint 1 → APIRouter 1
- Checkpoint: 4/8 migrados, tests green
## Phase 3: Write + Auth (Day 3-4)
- Migrate auth middleware → Depends()
- Migrate 3 POST endpoints
- Migrate Blueprint 2 → APIRouter 2
- Checkpoint: 7/8 migrados, tests green
## Phase 4: Complex + Cleanup (Day 5)
- Migrate POST /orders (más complejo)
- Remove Flask, update deps
- Checkpoint: 8/8, all tests green
## Rollback: git tag en cada checkpoint
Ejercicio 3: Escribir test de equivalencia (Medio)
Escribe un test de equivalencia para POST /api/users que verifica que Flask y FastAPI producen la misma respuesta para input válido e inválido.
Ver solución
class TestUserCreationEquivalence:
def test_valid_user_same_response(self):
data = {"name": "Ana", "email": "ana@test.com", "password": "secure123"}
flask_r = flask_client.post("/api/users", json=data)
fastapi_r = fastapi_client.post("/api/users", json=data)
assert flask_r.status_code == fastapi_r.status_code
assert flask_r.json["name"] == fastapi_r.json()["name"]
assert flask_r.json["email"] == fastapi_r.json()["email"]
def test_invalid_email_same_error(self):
data = {"name": "Ana", "email": "bad", "password": "secure123"}
flask_r = flask_client.post("/api/users", json=data)
fastapi_r = fastapi_client.post("/api/users", json=data)
assert flask_r.status_code == fastapi_r.status_code
# Both should return 400/422
def test_missing_field_same_error(self):
data = {"name": "Ana"} # missing email and password
flask_r = flask_client.post("/api/users", json=data)
fastapi_r = fastapi_client.post("/api/users", json=data)
assert flask_r.status_code == fastapi_r.status_code
Resumen
- El ciclo de migración: PLAN → TEST → MIGRATE → VALIDATE (repetir)
- Assessment primero: inventario de endpoints, dependencias, complejidad
- Plan con phases y checkpoints: cada phase tiene criterio de éxito y rollback
- Safety net: tests de equivalencia ANTES de migrar
- Incremental siempre: un endpoint a la vez, tests en cada paso
- Big bang nunca: demasiado riesgo, debugging imposible
Próxima cápsula: Flask→FastAPI Step-by-Step. Vas a ejecutar la migración práctica con Claude Code.
Recursos Adicionales
- Strangler Fig - Martin Fowler - El patrón de migración gradual
- FastAPI vs Flask - Comparison - Diferencias técnicas oficiales
- Migration Patterns - ThoughtWorks - Patrones de migración de la industria
- Feature Toggles - Martin Fowler - Toggles para migraciones graduales
- Blue-Green Deployments - Estrategia de deployment para migraciones
- Canary Releases - Releases graduales que complementan migraciones