Arquitecturas de datos: cómo diseñar tolerancia a fallos cuando dependes de APIs externas
Arquitecturas de datos: cómo diseñar tolerancia a fallos cuando dependes de APIs externas
Hay un problema que aparece tarde, siempre en el peor momento: una API externa responde con un 503, tu pipeline se detiene, y el sistema que la consume no sabe qué hacer. No hay cola, no hay fallback, no hay estado persistido. Solo un error sin tratamiento y un equipo que tiene que explicar por qué los datos no llegaron.
La mayoría de los equipos técnicos construyen la integración feliz primero. Funciona en staging, pasa los tests, llega a producción. Y allí es donde el sistema topa con la realidad: timeouts variables, rate limits no documentados, respuestas parciales y ventanas de mantenimiento sin previo aviso. Diseñar para ese escenario no es optimización prematura. Es la diferencia entre un sistema que dura y uno que obliga a intervención manual cada semana.
Este post no habla de qué API elegir. Habla de cómo estructurar tu sistema para que sea indiferente —en la medida de lo posible— a lo que pase en la capa que no controlas.
El error más común: tratar la API externa como si fuera tu base de datos
Una base de datos interna tiene latencia predecible, está bajo tu control operacional y puedes tunear su comportamiento. Una API externa de terceros no cumple ninguna de esas condiciones.
El error estructural es tratarlas igual: llamada síncrona en el flujo principal, sin buffer, sin reintentos con backoff, sin degradación planificada. Cuando la API falla, el sistema falla. Punto.
La corrección no es añadir un try/catch y logear el error. Es rediseñar el contrato interno entre tu pipeline y la fuente externa. La llamada a la API no debe bloquear el flujo principal. Los datos que llegan deben escribirse en una capa de almacenamiento intermedio antes de ser procesados. Y el procesamiento debe poder continuar con los últimos datos disponibles si la fuente no responde.
Esto implica decisiones de arquitectura, no de código defensivo.
Tres patrones que funcionan en producción
1. Cola de mensajes como desacoplamiento real
El patrón más robusto para integraciones con APIs externas es interponer una cola entre el productor (la llamada a la API) y el consumidor (tu lógica de negocio). La llamada a la API escribe en la cola. El consumidor lee de la cola. Si la API falla, el consumidor sigue procesando mensajes pendientes. Si el consumidor falla, los mensajes esperan.
Herramientas como Kafka, RabbitMQ o incluso SQS resuelven este desacoplamiento. La elección depende de tu volumen, de si necesitas ordenación garantizada y de si los mensajes deben persistir días o segundos. Lo que no es opcional es el desacoplamiento en sí.
Un detalle crítico: define explícitamente qué ocurre con los mensajes que no se pueden procesar. Una dead-letter queue no es un lujo —es el lugar donde el sistema te avisa de que algo estructural está roto.
2. Circuit breaker: corta antes de que el daño se extienda
El patrón circuit breaker es sencillo en concepto y frecuentemente omitido en práctica. Cuando una API externa empieza a fallar de forma repetida, el sistema debe dejar de intentar llamarla durante un tiempo determinado. No para ignorar el problema, sino para evitar que el backlog de reintentos colapse recursos internos y amplifique el fallo.
El circuito tiene tres estados: cerrado (flujo normal), abierto (se cortan las llamadas, se devuelve un valor por defecto o se escala una alerta) y semiabierto (se prueba con tráfico reducido si el servicio externo ha recuperado estabilidad).
Librerías como resilience4j en Java, pybreaker en Python o opossum en Node.js implementan este patrón. No es necesario reinventarlo. Sí es necesario configurarlo con umbrales reales basados en el comportamiento observado de la API con la que trabajas, no con valores por defecto genéricos.
3. Caché con TTL diferenciado según criticidad del dato
No todos los datos que consume tu sistema tienen la misma caducidad. Las señales de tendencia de las últimas horas tienen una ventana de valor distinta a los metadatos estructurales de una fuente.
Diseña una estrategia de caché que diferencie por tipo de dato. Los datos de alta frecuencia y baja criticidad pueden servirte en caché aunque tengan 30 minutos de antigüedad si la API está caída. Los datos de alta criticidad deben forzar un reintento con backoff y, si siguen sin llegar, escalar una alerta antes de servir el valor cacheado.
Esto requiere clasificar los datos en tu modelo de dominio, no en el código de infraestructura. Si tu equipo no ha discutido todavía qué datos son críticos y cuáles son "best effort", esa conversación debe ocurrir antes de tocar la arquitectura de caché.
Monitorización: lo que el sistema debe saber decirte solo
Una arquitectura tolerante a fallos no es la que nunca falla. Es la que falla de forma visible, controlada y sin propagación al usuario final.
Eso requiere instrumentación explícita. No basta con métricas de latencia agregada. Necesitas:
- Tasa de éxito por fuente externa, no global. Un 98% de éxito global puede esconder una fuente específica que falla el 40% de las veces.
- Tiempo en estado "circuit open" por dependencia. Si una API externa pasa 6 horas al día con el circuito abierto, el problema no es operacional —es contractual o de arquitectura.
- Volumen de mensajes en dead-letter queue con tendencia temporal. Un pico puntual es un incidente. Una tendencia creciente es un síntoma estructural.
Plataformas como FeedScale, que exponen datos del universo público de Internet vía API REST, publican métricas de disponibilidad y documentan comportamientos esperados ante sobrecarga. Aun así, el sistema que las consume debe asumir que cualquier dependencia externa puede degradarse. La monitorización es responsabilidad del integrador, no del proveedor.
Qué debe quedar documentado antes de que falle algo
Cuando el sistema falla a las 2 de la mañana, el equipo de guardia no tiene tiempo de razonar sobre la arquitectura. Necesita un runbook con respuestas concretas:
- ¿Cuánto tiempo puede operar el sistema con datos en caché antes de que el impacto sea visible para el negocio?
- ¿Qué fuentes tienen fallback y cuáles no?
- ¿Cuál es el umbral a partir del cual hay que escalar al equipo de producto?
- ¿Dónde se configura el circuito breaker y cómo se resetea manualmente si es necesario?
Ese documento no existe hasta que alguien lo escribe. Y normalmente nadie lo escribe hasta que ocurre el primer incidente grave. Adelantarse a eso es una decisión de arquitectura tanto como lo es elegir una cola de mensajes.
Diseñar para el fallo no es pesimismo técnico. Es aceptar que los sistemas distribuidos con dependencias externas fallan, que los patrones para gestionarlo están documentados y probados, y que la fragilidad suele ser una consecuencia de construir demasiado rápido la integración feliz sin pensar en lo que ocurre cuando el escenario deja de ser feliz.
La pregunta no es si tu arquitectura va a encontrar un fallo en producción. Es si cuando lo encuentre, sabrás exactamente qué hará el sistema y por qué.