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

ComponenteComplejidadEquivalente FastAPIOrden sugerido
GET /healthBajaDirecto1
GET /usersBajaDirecto2
POST /usersMediaPydantic validation3
Auth middlewareMediaDepends()4
POST /ordersAltaPydantic + Depends5
BlueprintsMediaAPIRouter6

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

CriterioBig BangIncrementalStrangler Fig
VelocidadRápido al inicio, lento al finalConstanteConstante
RiesgoMuy altoBajoMuy bajo
RollbackTodo o nadaPor endpointPor endpoint + routing
CoexistenciaNoTemporalDiseñada
TestingAl finalEn cada pasoEn cada paso
Cuándo usarNunca (en este contexto)Proyectos internosAPIs 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:

  1. GET /api/status — retorna {"status": "ok"}
  2. POST /api/auth/login — valida credenciales, genera JWT
  3. GET /api/users — lista usuarios con paginación
  4. POST /api/orders — valida, calcula, cobra, guarda
  5. GET /api/products/:id — busca por ID
  6. PUT /api/users/:id — actualiza con validación
Ver solución
  1. GET /api/status (Baja — sin lógica)
  2. GET /api/products/:id (Baja — solo lectura)
  3. GET /api/users (Media-Baja — paginación)
  4. PUT /api/users/:id (Media — validación + update)
  5. POST /api/auth/login (Media-Alta — auth, JWT)
  6. 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

  1. Strangler Fig - Martin Fowler - El patrón de migración gradual
  2. FastAPI vs Flask - Comparison - Diferencias técnicas oficiales
  3. Migration Patterns - ThoughtWorks - Patrones de migración de la industria
  4. Feature Toggles - Martin Fowler - Toggles para migraciones graduales
  5. Blue-Green Deployments - Estrategia de deployment para migraciones
  6. Canary Releases - Releases graduales que complementan migraciones