Introducción práctica a los circuit breakers y al diseño de fallback en FastAPI: patrones del mundo real para evitar que los fallos de APIs externas se conviertan en fallos de todo el sistema
Resumen
- Un circuit breaker es un patrón de diseño que detiene temporalmente las llamadas a una dependencia externa después de fallos repetidos, con el fin de evitar que toda tu API se vuelva lenta o falle al verse arrastrada por esa dependencia. Martin Fowler describe su forma básica como la apertura del breaker tras cierto número de fallos, después de lo cual las llamadas posteriores fallan de inmediato en lugar de intentarse.
- FastAPI es fuerte en E/S asíncrona y funciona muy bien con integraciones de APIs externas, pero si una dependencia lenta se prolonga, el event loop y los procesos worker pueden verse afectados en toda la aplicación. Por eso es importante pensar de forma conjunta en timeouts, retries, connection pooling y aislamiento de fallos.
- HTTPX ofrece funciones como instancias compartidas de
AsyncClient,Timeout,Limitsy soporte a nivel de transporte para retries de conexión, lo que lo convierte en una buena base para diseñar clientes de APIs externas. Los timeouts se dividen enconnect,read,writeypool, y el número de conexiones también puede controlarse explícitamente. - En Python existen bibliotecas como PyBreaker que pueden usarse para implementar el patrón Circuit Breaker. PyBreaker se describe a sí misma como una “implementación en Python del patrón Circuit Breaker”.
- Un fallback es la idea de que, en lugar de simplemente fallar cuando ocurre una caída, devuelves datos en caché, degradadas la funcionalidad o pospones el reintento para más tarde a través de algún camino alternativo. En la práctica, combinar un circuit breaker con comportamiento de fallback y monitoreo tiende a producir sistemas más estables tanto para la experiencia de usuario como para la operación que usar un circuit breaker por sí solo.
Quién se beneficiará de leer esto
Desarrolladores individuales y personas que están aprendiendo
Esto es para personas que han empezado a usar una o dos APIs externas y están lidiando con situaciones como “a veces es lenta” o “a veces falla”.
Es especialmente útil si estás llamando directamente a httpx.get() o AsyncClient.get() dentro de FastAPI y has experimentado casos en los que tu propia API se vuelve lenta simplemente porque el servicio externo no está sano. FastAPI se adapta muy bien al código async y facilita el diseño para E/S externa, pero a menos que añadas mecanismos para absorber los retrasos de las dependencias externas, esa fortaleza es difícil de aprovechar por completo.
Ingenieros backend en equipos pequeños
Esto es para equipos que llaman a múltiples servicios externos como envíos, pagos, notificaciones o plataformas de autenticación desde FastAPI.
Si te estás haciendo preguntas como “¿Hasta dónde deberíamos reintentar?”, “Ahora mismo, cuando un servicio externo se cae, todo lo que podemos hacer es devolver 500.” o “Los timeouts y límites de conexión cambian según quién escribió el código”, este artículo te ayudará a organizar esos problemas desde la perspectiva de los circuit breakers y los fallbacks. HTTPX ofrece AsyncClient, controles de timeout, controles de límites de conexión y configuración de transporte, lo que hace que encaje de forma natural en una capa de cliente compartida.
Equipos SaaS y startups
Esto es para equipos en los que los problemas con APIs externas afectan directamente a los SLO de tu producto y al volumen de tickets de soporte.
Si estás en la etapa en la que quieres decir: “Aunque una API externa no esté sana, no queremos que eso se convierta en una caída de todo el sistema,” o “Queremos sobrevivir devolviendo resultados en caché o degradando el servicio con elegancia,” o “También queremos una observabilidad adecuada durante los fallos,” entonces vale la pena tratar los circuit breakers y los fallbacks como infraestructura central, al mismo nivel que la autorización, la auditoría y las colas de trabajos. Circuit Breaker es ampliamente conocido como un patrón representativo para aislar fallos, y también se discute con frecuencia como un patrón importante en arquitecturas de microservicios.
Notas de accesibilidad
- El artículo comienza con un resumen y luego avanza paso a paso por “por qué esto es necesario”, “cómo diseñarlo” y “cómo implementarlo”.
- Los términos técnicos se explican brevemente la primera vez que aparecen, y luego se usa la misma terminología de forma consistente para que el flujo sea más fácil de seguir.
- Los ejemplos de código se dividen en secciones cortas para que cada bloque muestre una sola responsabilidad.
- Cada capítulo está escrito para que pueda leerse de forma independiente, con el contexto necesario proporcionado cuando corresponde.
- El nivel objetivo es aproximadamente equivalente a las expectativas de legibilidad de WCAG AA.
1. ¿Qué es un circuit breaker?
Un circuit breaker es un mecanismo que detiene temporalmente las llamadas a una dependencia externa cuando los fallos continúan ocurriendo.
Martin Fowler explica la forma básica como envolver una llamada de función protegida con un objeto breaker, y cuando el número de fallos alcanza un umbral, el breaker abre el circuito y deja de hacer más llamadas, devolviendo errores de inmediato en su lugar. En la práctica, normalmente se considera junto con monitoreo y alertas.
La analogía eléctrica hace que esto sea fácil de entender.
Si algo está a punto de cortocircuitarse, no sigues dejando pasar corriente hasta que todo se queme. Lo cortas una vez para evitar que el daño se propague. Lo mismo se aplica a las aplicaciones. Si una API externa tarda decenas de segundos en responder, devuelve 5xx de forma continua o provoca que se acumulen conexiones, entonces si sigues llamándola, toda tu aplicación FastAPI puede verse arrastrada. El patrón Circuit Breaker existe específicamente para detener esa reacción en cadena.
2. Por qué esto importa especialmente en FastAPI
FastAPI está construido en torno a async def y E/S asíncrona, por lo que es muy adecuado para trabajo ligado a E/S, como APIs externas y bases de datos.
Al mismo tiempo, si una API externa se vuelve muy lenta o empieza a fallar repetidamente, aumenta el número de tareas esperando en el event loop, y esto puede afectar a los pools de conexiones y al rendimiento percibido de toda la aplicación. La documentación de FastAPI explica que el procesamiento async es especialmente útil para cargas de trabajo ligadas a E/S, y las llamadas a APIs externas son exactamente ese tipo de trabajo.
La funcionalidad de lifespan de FastAPI también es muy adecuada para gestionar recursos al arrancar y cerrar la aplicación, lo que la convierte en un lugar natural para crear y limpiar clientes HTTP compartidos. Su sistema de inyección de dependencias también facilita ensamblar clientes de APIs externas compartidos como dependencias comunes. En otras palabras, FastAPI ya proporciona la base necesaria para colocar circuit breakers y fallbacks de forma limpia.
3. La visión general que conviene entender primero: timeout, retry, breaker, fallback
Un circuit breaker por sí solo no basta para que tu sistema sea resistente a fallos de APIs externas.
En la práctica, resulta mucho más fácil razonar sobre esto si piensas en estas cuatro piezas juntas.
- Timeout
- Decidir cuánto tiempo estás dispuesto a esperar desde el principio
- Retry
- Reintentar brevemente si el fallo parece temporal
- Circuit breaker
- Detener temporalmente las llamadas a una dependencia que sigue fallando
- Fallback
- Decidir qué devolver cuando no puedes llamarla
HTTPX te da timeouts, controles de conexión y soporte de retries a nivel de transporte. Tenacity encaja bien para diseñar retries con exponential backoff. PyBreaker puede usarse para el propio patrón Circuit Breaker. Así que el ecosistema alrededor de FastAPI ya es bastante maduro.
4. Empieza con la base: mantén un AsyncClient compartido en lifespan
Antes incluso de llegar a los circuit breakers, es más fácil ampliar tu diseño después si primero construyes una base compartida para clientes de APIs externas.
HTTPX recomienda usar AsyncClient en entornos asíncronos y reutilizar la instancia del cliente. También desaconseja explícitamente crear clientes repetidamente dentro de bucles calientes.
from contextlib import asynccontextmanager
import httpx
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
timeout = httpx.Timeout(connect=2.0, read=5.0, write=5.0, pool=1.0)
limits = httpx.Limits(
max_keepalive_connections=20,
max_connections=100,
keepalive_expiry=5.0,
)
app.state.http_client = httpx.AsyncClient(
timeout=timeout,
limits=limits,
)
try:
yield
finally:
await app.state.http_client.aclose()
app = FastAPI(lifespan=lifespan)
Tanto Timeout como Limits usados aquí son funciones oficiales de HTTPX. Los timeouts se dividen en connect, read, write y pool, y los límites de conexión pueden ajustarse con max_connections y configuraciones relacionadas.
5. Inyecta el cliente compartido a través de una dependencia
Usando las dependencias de FastAPI, puedes pasar de forma natural el AsyncClient compartido a cada clase de cliente.
FastAPI describe su sistema de dependencias como potente e intuitivo, y funciona muy bien para componentes reutilizables y testabilidad.
import httpx
from fastapi import Request
def get_http_client(request: Request) -> httpx.AsyncClient:
return request.app.state.http_client
Sobre esto, puedes crear una clase para un servicio externo específico.
import httpx
class BillingClient:
def __init__(self, client: httpx.AsyncClient, base_url: str, api_key: str):
self.client = client
self.base_url = base_url.rstrip("/")
self.api_key = api_key
Una vez que lo estructuras así, puedes mantener la lógica del circuit breaker y del fallback dentro de la capa cliente.
Eso evita que los detalles específicos de HTTPX queden dispersos por routers y capas de servicio, y hace mucho más fáciles las mejoras posteriores.
6. Define timeouts explícitamente: la defensa mínima antes de un breaker
HTTPX habilita timeouts por defecto, pero en la práctica es más seguro definirlos explícitamente.
La documentación oficial explica los cuatro tipos de timeouts: connect, read, write y pool. Por ejemplo, el timeout de pool significa cuánto esperar por una conexión disponible del pool.
import httpx
DEFAULT_TIMEOUT = httpx.Timeout(
connect=1.5,
read=3.0,
write=3.0,
pool=0.5,
)
Lo importante es reconocer que distintas APIs externas merecen distintos tiempos de espera.
Un servicio interno rápido puede justificar un timeout mucho más corto, mientras que una API de IA generativa o una API de reporting pesado podría necesitar algo más de tiempo. Pero un diseño que espera indefinidamente, o simplemente “durante mucho tiempo”, es peligroso incluso antes de considerar circuit breakers. Si sigues esperando a una dependencia lenta, tu propia aplicación también se vuelve lenta.
7. Convierte las excepciones de HTTPX en tus propios tipos de excepción
HTTPX tiene una jerarquía de excepciones bien estructurada que incluye RequestError, HTTPStatusError y TimeoutException.
En lugar de dejar que estas suban tal cual, es mejor convertirlas en tus propias excepciones de aplicación para poder manejarlas de forma consistente. Eso también facilita definir condiciones para circuit breakers y fallbacks. La guía rápida y la documentación de excepciones de HTTPX muestran cómo usar raise_for_status() y manejar esas excepciones.
class ExternalAPIError(Exception):
pass
class ExternalAPITimeoutError(ExternalAPIError):
pass
class ExternalAPIUnavailableError(ExternalAPIError):
pass
class ExternalAPIBadResponseError(ExternalAPIError):
pass
import httpx
async def safe_request(client: httpx.AsyncClient, method: str, url: str, **kwargs) -> httpx.Response:
try:
response = await client.request(method, url, **kwargs)
response.raise_for_status()
return response
except httpx.TimeoutException as exc:
raise ExternalAPITimeoutError(str(exc)) from exc
except httpx.HTTPStatusError as exc:
raise ExternalAPIBadResponseError(str(exc)) from exc
except httpx.RequestError as exc:
raise ExternalAPIUnavailableError(str(exc)) from exc
Una vez que tus excepciones están organizadas a este nivel, se vuelve mucho más fácil expresar decisiones como “abre el breaker después de tantos timeouts” o “no abras el breaker en errores 4xx”.
8. Entiende los estados del breaker: Closed / Open / Half-Open
Es más fácil razonar sobre un circuit breaker si piensas en estos tres estados.
Esta es la forma básica común usada por muchas implementaciones y explicaciones, y encaja con la descripción de Martin Fowler.
- Closed
- Estado normal. Las llamadas a la API externa están permitidas
- Open
- Los fallos han continuado, así que el breaker ahora bloquea las llamadas. Las solicitudes fallan de inmediato
- Half-Open
- Se permite un pequeño número de llamadas de prueba para comprobar si la dependencia se ha recuperado
Gracias a estos tres estados, evitas ambos extremos:
no “detenerlo para siempre porque falló una vez”,
y no “esperar el timeout completo en cada llamada para siempre”.
En su lugar, obtienes un comportamiento de recuperación equilibrado.
9. Una idea mínima de implementación usando PyBreaker
PyBreaker se describe a sí misma como una “implementación en Python del patrón Circuit Breaker”.
También soporta cosas como almacenamiento de estado respaldado por Redis, lo que se ve en la descripción del paquete y en patrones de uso comunes.
Conceptualmente, envuelves las llamadas arriesgadas con un breaker así:
import pybreaker
billing_breaker = pybreaker.CircuitBreaker(
fail_max=5,
reset_timeout=30,
)
Sin embargo, es importante señalar que PyBreaker se usa con más frecuencia en contextos de llamadas síncronas.
Si quieres usarla directamente en FastAPI con llamadas asíncronas de HTTPX, necesitas pensar un poco más cuidadosamente en la implementación que la rodea. En la práctica, uno de estos dos enfoques suele funcionar mejor.
- Gestionar solo el estado del breaker mientras envuelves llamadas async a través de una abstracción compatible
- Empezar con una pequeña implementación personalizada de “simple breaker” que sea fácil de reemplazar después
En este artículo, para que la idea sea más fácil de entender en FastAPI, mostraré primero el segundo enfoque.
10. Diseñando un circuit breaker simple para FastAPI
Si quieres empezar poco a poco, incluso un breaker mínimo hecho por ti puede ser muy útil.
La idea central es esta:
- Contar fallos
- Una vez superado el umbral, pasar a Open durante un tiempo fijo
- Mientras esté Open, fallar inmediatamente
- Después de que expire el período Open, permitir una solicitud de prueba
- Si la prueba tiene éxito, volver a Closed
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
@dataclass
class SimpleCircuitBreaker:
fail_max: int
reset_timeout_sec: int
failure_count: int = 0
opened_at: datetime | None = None
def is_open(self) -> bool:
if self.opened_at is None:
return False
now = datetime.now(timezone.utc)
return now < self.opened_at + timedelta(seconds=self.reset_timeout_sec)
def allow_request(self) -> bool:
return not self.is_open()
def record_success(self) -> None:
self.failure_count = 0
self.opened_at = None
def record_failure(self) -> None:
self.failure_count += 1
if self.failure_count >= self.fail_max:
self.opened_at = datetime.now(timezone.utc)
Esto no es una implementación completa lista para producción, pero es más que suficiente para entender el comportamiento básico de un circuit breaker.
Incluso solo añadir la idea de que deberías dejar de golpear seriamente una dependencia que sigue fallando ayuda mucho a prevenir caídas de todo el sistema.
11. Coloca el breaker dentro de la capa cliente
A continuación, usa el simple breaker dentro de tu cliente de API externa.
class CircuitOpenError(Exception):
pass
class ShippingClient:
def __init__(self, client, base_url: str, api_key: str, breaker: SimpleCircuitBreaker):
self.client = client
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self.breaker = breaker
async def get_quote(self, payload: dict) -> dict:
if not self.breaker.allow_request():
raise CircuitOpenError("shipping circuit is open")
try:
response = await safe_request(
self.client,
"POST",
f"{self.base_url}/quotes",
json=payload,
headers={"Authorization": f"Bearer {self.api_key}"},
)
self.breaker.record_success()
return response.json()
except (ExternalAPITimeoutError, ExternalAPIUnavailableError):
self.breaker.record_failure()
raise
Una vez que haces esto, decisiones como
“¿Qué excepciones cuentan como fallos?”
o “¿Debería un HTTP 4xx abrir el breaker?”
pueden quedarse dentro de la capa cliente.
Por ejemplo, si un 400 es causado por un error de entrada del usuario, eso no es una caída de la dependencia.
Así que en muchos casos, tratar cada HTTPStatusError como un fallo del breaker no sería el diseño correcto.
12. ¿Qué es fallback? Decide qué hacer cuando no puedes llamar a la dependencia
Un circuit breaker es un mecanismo defensivo que dice “no la llames”, pero fallback es la idea de decidir cómo debería comportarse tu sistema cuando no puede llamarla.
Las opciones típicas de fallback incluyen:
- Devolver el último resultado exitoso en caché
- Devolver una respuesta degradada
- Omitir parte de la información pero mantener usable la pantalla o el endpoint
- Decir claramente “actualmente no disponible” mientras ofreces una forma de intentarlo más tarde
- Dejar de hacer el trabajo de forma síncrona y cambiar a envío a una cola o job en segundo plano
El patrón Circuit Breaker, tal como lo describe Martin Fowler, también asume que se combinará con monitoreo y comportamiento operativo, no solo con lógica de bloqueo pura. En contextos de microservicios suele discutirse junto con cosas como timeouts y bulkheads, y el fallback es la expresión práctica de esa idea a nivel de aplicación.
13. Ejemplo de implementación de fallback 1: devolver una respuesta en caché
Una de las formas más prácticas de fallback es devolver el resultado exitoso más reciente durante un tiempo limitado.
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
@dataclass
class CacheEntry:
value: dict
expires_at: datetime
class SimpleResponseCache:
def __init__(self):
self.data: dict[str, CacheEntry] = {}
def get(self, key: str) -> dict | None:
entry = self.data.get(key)
if not entry:
return None
if datetime.now(timezone.utc) > entry.expires_at:
return None
return entry.value
def set(self, key: str, value: dict, ttl_sec: int) -> None:
self.data[key] = CacheEntry(
value=value,
expires_at=datetime.now(timezone.utc) + timedelta(seconds=ttl_sec),
)
class ShippingClient:
def __init__(self, client, base_url: str, api_key: str, breaker: SimpleCircuitBreaker, cache: SimpleResponseCache):
self.client = client
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self.breaker = breaker
self.cache = cache
async def get_quote_with_fallback(self, payload: dict) -> dict:
cache_key = f"quote:{payload.get('zip')}:{payload.get('weight')}"
if not self.breaker.allow_request():
cached = self.cache.get(cache_key)
if cached is not None:
return {"source": "cache", "data": cached}
raise CircuitOpenError("shipping circuit is open")
try:
response = await safe_request(
self.client,
"POST",
f"{self.base_url}/quotes",
json=payload,
headers={"Authorization": f"Bearer {self.api_key}"},
)
data = response.json()
self.cache.set(cache_key, data, ttl_sec=60)
self.breaker.record_success()
return {"source": "live", "data": data}
except (ExternalAPITimeoutError, ExternalAPIUnavailableError):
self.breaker.record_failure()
cached = self.cache.get(cache_key)
if cached is not None:
return {"source": "cache", "data": cached}
raise
Con este diseño, incluso si la API de cotización de envíos está temporalmente no saludable, la pantalla puede seguir siendo usable si existe un resultado exitoso reciente.
Dicho eso, siempre necesitas decidir cuidadosamente si devolver datos ligeramente desactualizados es aceptable en ese caso de uso específico.
14. Ejemplo de implementación de fallback 2: dejar de hacerlo de forma síncrona y moverlo a un job
Algunos tipos de APIs externas no encajan bien con un fallback basado en caché.
Por ejemplo, la “generación de informes”, la “agregación externa pesada” o la “conversión de archivos grandes” puede que no se completen lo suficientemente rápido para flujos síncronos de request-response.
En esos casos, un fallback puede ser renunciar a la finalización síncrona y cambiar a un modelo asíncrono basado en jobs.
- Durante operación normal
- Llamar a la API externa durante la solicitud y devolver el resultado
- Durante caídas
- Devolver “aceptado para procesamiento” y reintentar más tarde mediante un job
Esto suele ser más amable desde la perspectiva de UX que un fallo total, y también protege mejor tu API cuando la dependencia no está sana.
FastAPI incluye BackgroundTasks, que puede usarse para pequeñas tareas posteriores a la respuesta. Para trabajo más pesado, una cola de jobs dedicada encaja mejor.
15. La relación con los retries: añadir un breaker no hace innecesarios los retries
Añadir un circuit breaker no significa que los retries ya no sean necesarios.
En la práctica, sus roles son distintos.
- Retry
- Absorber pequeños fallos temporales
- Circuit breaker
- Dejar de llamar a una dependencia que está fallando de forma continua
- Fallback
- Decidir qué devolver cuando no puedes llamarla
Los retries a nivel de transporte de HTTPX pueden usarse para ConnectError y ConnectTimeout. Para comportamiento de retry más amplio o exponential backoff, Tenacity suele ser más apropiado.
Un modelo mental práctico sería algo así:
- Primero, deja que HTTPX haga un único retry de conexión
- Si eso aún falla, haz retries limitados con Tenacity
- Si los fallos continúan, abre el breaker
- Mientras el breaker esté abierto, redirige las solicitudes a un fallback
Esto te permite construir resiliencia gradualmente, sin saltar de inmediato a algo extremo.
16. Monitoreo y métricas: un breaker es peligroso si nadie puede ver cuándo se abre
Los circuit breakers son útiles, pero se vuelven peligrosos si se abren y nadie se da cuenta.
Como mínimo, conviene hacer visibles métricas como estas:
- Tasa de éxito por API externa
- Tasa de timeouts
- Número de retries
- Número de veces que se abre el breaker
- Número de veces que se usa el fallback
- Tasa de devoluciones desde caché
La descripción de Martin Fowler también señala que cuando un breaker se abre, normalmente quieres monitoreo y alertas. En otras palabras, un circuit breaker no es algo que “añades y olvidas”. Debe ir de la mano de la observabilidad.
Del lado de FastAPI, si ya estás usando logs estructurados y métricas, dejar campos como circuit_state="open" o fallback="cache" hace mucho más fácil el diagnóstico posterior.
17. Diseño de logging: registra las transiciones de estado del breaker
Tus logs deberían capturar no solo los errores de la propia API externa, sino también los cambios en el estado del breaker.
import logging
logger = logging.getLogger("circuit_breaker")
def log_breaker_open(name: str) -> None:
logger.warning("circuit opened", extra={"circuit": name})
def log_breaker_closed(name: str) -> None:
logger.info("circuit closed", extra={"circuit": name})
Si puedes ver cuándo se abrió el breaker, cuándo se recuperó desde Half-Open y cuándo una respuesta se sirvió mediante fallback,
se vuelve mucho más fácil responder preguntas como:
“¿Por qué el usuario recibió aquí una respuesta degradada?”
18. Estrategia de testing: los casos mínimos que merece la pena proteger
Los circuit breakers y los fallbacks son frágiles si solo pruebas el happy path.
Como mínimo, merece la pena probar estos casos:
- El breaker se abre tras timeouts repetidos
- Mientras está Open, la API externa no se llama realmente, y el sistema falla de inmediato o usa fallback
- Si existe caché de fallback, esta se devuelve
- Tras la recuperación, una solicitud de prueba exitosa devuelve el breaker a Closed
- Los errores provocados por el usuario, como 4xx, no abren el breaker
El código cercano a la lógica de política, como record_failure() o allow_request(), se vuelve mucho más fácil de probar si lo mantienes en clases pequeñas o funciones casi puras.
19. Patrones comunes de fallo
19.1 Añadir solo un breaker, pero sin timeouts
Si un fallo tarda demasiado en reconocerse, todo el sistema puede volverse lento antes de que el breaker tenga oportunidad de ayudar. Es más seguro configurar primero los timeouts de HTTPX.
19.2 Contar tanto 4xx como 5xx como el mismo tipo de fallo
Si los errores del usuario o de autorización también cuentan para el breaker, este puede abrirse incluso cuando la propia dependencia está sana.
Normalmente es más natural contar solo los errores que realmente representan inestabilidad de la dependencia.
19.3 Usar fallbacks descuidados que devuelven datos obsoletos con demasiada libertad
Devolver valores en caché es poderoso, pero no todo tipo de datos puede estar desactualizado de forma segura.
Para cosas como el estado de pagos o el inventario, la frescura importa mucho más.
19.4 Dejar que el breaker se abra sin que nadie se entere
Sin alertas o métricas, puedes acabar en un estado en el que la funcionalidad ha estado degradada silenciosamente durante mucho tiempo. Fowler también trata el monitoreo del breaker como una parte importante del patrón.
19.5 Reglas distintas para cada cliente
Si tu API de envíos, tu API de pagos y tu API de notificaciones se comportan todas de formas completamente diferentes, la operación del equipo se vuelve dolorosa.
Las excepciones están bien, pero tu patrón básico debería seguir siendo consistente.
20. Una hoja de ruta según el tipo de lector
Desarrolladores individuales y personas que están aprendiendo
- Primero añade un
AsyncClientcompartido, timeouts y límites de conexión - Convierte las excepciones de APIs externas en tus propias excepciones
- Introduce un breaker pequeño y simple para un solo cliente
- Prueba un fallback basado en caché en un lugar
- Deja los resultados en logs
Ingenieros en equipos pequeños
- Haz un inventario de cada API externa según importancia y requisitos de frescura
- Construye una capa cliente compartida
- Alinea reglas de timeout, retry y breaker en todo el equipo
- Convierte el número de aperturas de breaker y usos de fallback en métricas
- Revisa las operaciones de escritura teniendo en cuenta idempotencia y opciones con colas de jobs
Equipos SaaS y startups
- Define políticas por dependencia para “fallo duro”, “modo degradado” y “devolución desde caché”
- Centraliza la lógica de circuit breaker y fallback en la capa cliente
- Construye logs de auditoría, alertas y dashboards
- Ejecuta simulacros de caída o pruebas tipo chaos para verificar el comportamiento cuando las dependencias se caen
- Si hace falta, evoluciona hacia un estado compartido del breaker respaldado por Redis o implementaciones más preparadas para producción
Enlaces de referencia
-
FastAPI
-
HTTPX
-
Circuit Breaker
-
Retry
Conclusión
- Un circuit breaker es un patrón muy práctico para evitar que toda tu aplicación FastAPI se vea arrastrada por llamar repetidamente a una API externa que sigue fallando. Como explica Martin Fowler, abrir el circuito después de alcanzar un umbral de fallos y detener las llamadas posteriores ayuda a prevenir daños en cascada.
- En FastAPI, un enfoque realista es construir sobre un
AsyncClientcompartido, configuraciones de timeout, límites de conexión y conversión de excepciones, y luego ir incorporando gradualmente retries, breakers y fallbacks. HTTPX ya proporciona gran parte de la base para esto. - Un fallback no es simplemente “tragarse errores”. Es una decisión de diseño que protege tanto la experiencia de usuario como la estabilidad del sistema mediante cosas como respuestas en caché, operación degradada o traspaso a jobs en segundo plano. En la práctica, los circuit breakers son mucho más sólidos cuando se diseñan junto con fallbacks y monitoreo.
- No necesitas una implementación perfecta desde el primer día. Incluso si empiezas con un solo cliente de API externa, añadir timeouts, conversión de excepciones, un breaker simple y un fallback basado en caché hará que el diseño sea mucho más tangible y fácil de entender.
Un siguiente artículo natural después de este sería algo como “Patrones de diseño para APIs internas de administración en FastAPI” o “Diseño de colas de jobs y degradación elegante en FastAPI”.

