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
- Identifiqué al menos tres patrones repetitivos en mi API actual que pueden convertirse en dependencias.
- Implementé una dependencia que acepta parámetros configurables para diferentes casos de uso.
- Creé una dependencia anidada que reutiliza otra dependencia base.
- Documenté mis dependencias con docstrings que aparecen claramente en la documentación automática.
- Probé mis dependencias de forma aislada usando el cliente de prueba de FastAPI.
- Refactoricé endpoints existentes para usar dependencias, reduciendo líneas de código duplicadas en al menos 30%.
- 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:
- 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.
- 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.
- 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. - 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. - Crea dependencia compuesta: Implementa
process_checkout(cart_data, user)que use las dependencias anteriores más validación de cupón y auditoría. - Refactoriza endpoints: Modifica los endpoints
/cart/checkout,/cart/add-itemy/admin/reportpara usar las nuevas dependencias. - 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.