Práctica: Diseñar una API Modular con Dependencias Personalizadas

Lectura
25 min~5 min lectura

Concepto clave

En el desarrollo de APIs escalables, la modularidad no es un lujo, es una necesidad de supervivencia. Imagina una fábrica donde cada máquina realiza una tarea específica y puede reemplazarse sin detener toda la producción. Así funcionan las dependencias personalizadas en FastAPI: componentes reutilizables que encapsulan lógica común como autenticación, validación de datos o conexiones a bases de datos.

La arquitectura modular con dependencias permite separar responsabilidades claramente. En lugar de tener código repetido en cada endpoint, defines una función o clase que se inyecta automáticamente donde se necesita. Esto no solo reduce errores, sino que facilita pruebas unitarias y mantenimiento. En proyectos reales, esta práctica diferencia entre un código que escala y uno que colapsa bajo su propio peso.

Cómo funciona en la práctica

El proceso comienza identificando patrones repetitivos en tus endpoints. Por ejemplo, si varios endpoints requieren verificar tokens JWT y extraer información del usuario, eso es candidato a convertirse en una dependencia. FastAPI trata las dependencias como funciones normales que pueden recibir parámetros y devolver valores, los cuales se pasan automáticamente a los endpoints que las declaran.

Paso a paso: primero defines la función de dependencia con Depends() de FastAPI. Luego, en los endpoints, la incluyes como parámetro. FastAPI se encarga de ejecutarla antes del endpoint y pasar su resultado. Puedes anidar dependencias (una dependencia que usa otra) y configurarlas con parámetros para mayor flexibilidad. Este enfoque transforma código espagueti en una arquitectura limpia y predecible.

Código en acción

Veamos un ejemplo real de refactorización. Antes: endpoints con lógica duplicada de autenticación.

# Antes: código duplicado
from fastapi import FastAPI, HTTPException
from typing import Optional

app = FastAPI()

def verify_token(token: str) -> Optional[dict]:
    # Lógica simplificada de verificación
    if token == "valid_token":
        return {"user_id": 123, "role": "admin"}
    return None

@app.get("/users/me")
async def get_current_user(authorization: str = Header(None)):
    if not authorization:
        raise HTTPException(status_code=401, detail="Token faltante")
    user_data = verify_token(authorization)
    if not user_data:
        raise HTTPException(status_code=401, detail="Token inválido")
    # Lógica del endpoint
    return {"user": user_data}

@app.get("/admin/dashboard")
async def get_dashboard(authorization: str = Header(None)):
    if not authorization:
        raise HTTPException(status_code=401, detail="Token faltante")
    user_data = verify_token(authorization)
    if not user_data:
        raise HTTPException(status_code=401, detail="Token inválido")
    if user_data["role"] != "admin":
        raise HTTPException(status_code=403, detail="Permiso denegado")
    # Lógica del endpoint
    return {"dashboard": "data"}

Después: arquitectura modular con dependencias personalizadas.

# Después: código modular
from fastapi import FastAPI, Depends, HTTPException, Header
from typing import Optional
from pydantic import BaseModel

app = FastAPI()

# Modelo para datos de usuario
token
class UserData(BaseModel):
    user_id: int
    role: str

# Dependencia para verificación de token
def get_current_user(authorization: str = Header(None)) -> UserData:
    if not authorization:
        raise HTTPException(status_code=401, detail="Token faltante")
    # Lógica de verificación (en producción, usaría JWT real)
    if authorization == "valid_token":
        return UserData(user_id=123, role="admin")
    raise HTTPException(status_code=401, detail="Token inválido")

# Dependencia para verificación de rol admin
def require_admin(user: UserData = Depends(get_current_user)) -> UserData:
    if user.role != "admin":
        raise HTTPException(status_code=403, detail="Se requiere rol admin")
    return user

# Endpoints limpios y enfocados
@app.get("/users/me")
async def read_current_user(user: UserData = Depends(get_current_user)):
    return {"user": user}

@app.get("/admin/dashboard")
async def read_dashboard(admin_user: UserData = Depends(require_admin)):
    return {"dashboard": "datos confidenciales", "user": admin_user}

# Nueva ruta que reutiliza la misma dependencia
@app.get("/users/{user_id}/profile")
async def get_user_profile(
    user_id: int,
    current_user: UserData = Depends(get_current_user)
):
    # Verificar permisos específicos
    if current_user.user_id != user_id and current_user.role != "admin":
        raise HTTPException(status_code=403, detail="Acceso denegado")
    return {"profile": f"Perfil del usuario {user_id}"}

Errores comunes

  • Crear dependencias demasiado específicas: Si una dependencia solo se usa en un lugar, probablemente no debería ser dependencia. Busca patrones de uso en al menos 2-3 endpoints.
  • Ignorar el manejo de errores en dependencias: Las excepciones en dependencias deben ser HTTPException con códigos de estado apropiados, no excepciones genéricas que devuelvan 500.
  • Anidar dependencias en exceso: Más de 3-4 niveles de anidación hace el código difícil de depurar. Mantén la jerarquía plana cuando sea posible.
  • No documentar las dependencias: En Swagger/OpenAPI, las dependencias aparecen como parámetros. Usa docstrings claros para explicar su propósito y requisitos.
  • Confundir dependencias con middlewares: Las dependencias se ejecutan por endpoint, los middlewares por cada request. Usa dependencias para lógica específica de endpoint, middlewares para cross-cutting concerns como logging.

Checklist de dominio

  1. Identifiqué al menos tres patrones repetitivos en mi API actual que pueden convertirse en dependencias.
  2. Implementé una dependencia que acepta parámetros configurables para diferentes casos de uso.
  3. Creé una dependencia anidada que reutiliza otra dependencia base.
  4. Documenté mis dependencias con docstrings que aparecen claramente en la documentación automática.
  5. Probé mis dependencias de forma aislada usando el cliente de prueba de FastAPI.
  6. Refactoricé endpoints existentes para usar dependencias, reduciendo líneas de código duplicadas en al menos 30%.
  7. Configuré inyección de dependencias para diferentes entornos (desarrollo, testing, producción).

Refactorización de API de E-commerce con Dependencias Personalizadas

Transforma una API monolítica de carrito de compras en una arquitectura modular usando dependencias personalizadas. Sigue estos pasos:

  1. Analiza el código base: Descarga el proyecto inicial desde [URL_SIMULADA/repo-inicial]. Identifica al menos 4 lugares con lógica duplicada relacionada con: verificación de inventario, cálculo de impuestos, validación de cupones y auditoría de operaciones.
  2. Diseña las dependencias: Crea un diagrama de dependencias mostrando cómo se conectarán. Define interfaces claras para cada dependencia, incluyendo parámetros de entrada y valores de retorno.
  3. Implementa la dependencia de inventario: Crea check_inventory(product_id, quantity) que verifique stock y devuelva disponibilidad. Debe lanzar HTTP 400 si no hay stock suficiente.
  4. Implementa la dependencia de cálculo de impuestos: Crea calculate_taxes(subtotal, user_state) que aplique impuestos según ubicación. Hazla configurable para diferentes tasas por estado.
  5. Crea dependencia compuesta: Implementa process_checkout(cart_data, user) que use las dependencias anteriores más validación de cupón y auditoría.
  6. Refactoriza endpoints: Modifica los endpoints /cart/checkout, /cart/add-item y /admin/report para usar las nuevas dependencias.
  7. Prueba la implementación: Ejecuta los tests existentes y asegúrate de que pasen. Agrega 2 tests nuevos para verificar el comportamiento de las dependencias de forma aislada.

Entrega: Código refactorizado en un repositorio Git con commits descriptivos y un README explicando las decisiones arquitectónicas.

Pistas
  • Usa clases para dependencias complejas que necesiten mantener estado entre llamadas
  • Considera usar caché en dependencias costosas como cálculo de impuestos para mejorar rendimiento
  • Prueba las dependencias con valores límite: stock cero, subtotal negativo, estados no soportados

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.