Diseño de API Gateway: Resiliencia, Enrutamiento y Seguridad
Guía práctica para diseñar API gateways: patrones de enrutamiento, rate limiting, autenticación, circuit breakers y observabilidad para APIs resilientes.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Visión General
Toda arquitectura de microservicios eventualmente necesita una puerta de entrada. El API gateway es esa puerta — y si lo diseñas mal, se convierte en un punto único de fallo, un cuello de botella de rendimiento o una brecha de seguridad. Un gateway bien diseñado maneja enrutamiento, rate limiting, autenticación, circuit breakers y observabilidad para que los servicios individuales se enfoquen en la lógica de negocio. A continuación: los patrones principales, trade-offs y estrategias de implementación para construir API gateways resilientes.
Cuándo Usar
- For alternatives, see Complete Guide to API Security.
Usa esta guía cuando:
- Estás migrando de un monolito a microservicios y necesitas un punto de entrada unificado
- Tus aplicaciones cliente se comunican directamente con servicios backend y necesitas consolidar preocupaciones transversales
- Estás experimentando fallos en cascada, inconsistencias de autenticación o puntos ciegos de observabilidad entre servicios
Solución
Responsabilidades Principales del Gateway
| Preocupación | Enfoque de Implementación | Tecnología Clave |
|---|---|---|
| Enrutamiento de Requests | Enrutamiento basado en path, header o host hacia servicios upstream | Nginx, Kong, Envoy, AWS API Gateway |
| Rate Limiting | Token bucket o leaky bucket por cliente, IP o API key | Redis + Lua, Envoy, Kong |
| Autenticación | Validar JWT, API keys o tokens OAuth2 en el edge | Keycloak, Auth0, middleware personalizado |
| Circuit Breaker | Rastrear tasas de fallo y fallar rápido cuando los upstreams no están saludables | Resilience4j, Envoy, Hystrix (legacy) |
| Balanceo de Carga | Round-robin, least connections o consistent hashing | Envoy, Nginx, service mesh |
| Observabilidad | Trazabilidad distribuida, métricas y logs de acceso | OpenTelemetry, Prometheus, ELK |
| Terminación SSL | Descargar TLS en el gateway para reducir carga CPU en servicios | Nginx, HAProxy, balanceadores cloud |
| Transformación de Request/Response | Inyección de headers, reescritura de payloads, traducción de protocolo | Plugins de Kong, filtros de Envoy |
Ejemplo de Arquitectura de Enrutamiento
# Fragmento de configuración de Envoy
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match: { prefix: "/api/users" }
route: { cluster: user_service, prefix_rewrite: "/" }
- match: { prefix: "/api/orders" }
route: { cluster: order_service, prefix_rewrite: "/" }
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: user_service
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: user_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: user-service, port_value: 8080 }
- name: order_service
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: order_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: order-service, port_value: 8080 }
Implementación de Rate Limiting
# Rate limiter tipo token bucket con Redis
import time
import redis
class TokenBucketRateLimiter:
def __init__(self, redis_client, key_prefix, capacity, refill_rate):
self.redis = redis_client
self.key_prefix = key_prefix
self.capacity = capacity
self.refill_rate = refill_rate
def is_allowed(self, client_id):
key = f"{self.key_prefix}:{client_id}"
pipeline = self.redis.pipeline()
now = time.time()
pipeline.hmget(key, ["tokens", "last_refill"])
result = pipeline.execute()
tokens = float(result[0][0] or self.capacity)
last_refill = float(result[0][1] or now)
elapsed = now - last_refill
tokens = min(self.capacity, tokens + elapsed * self.refill_rate)
if tokens >= 1:
tokens -= 1
self.redis.hmset(key, {"tokens": tokens, "last_refill": now})
self.redis.expire(key, 60)
return True
else:
self.redis.hmset(key, {"tokens": tokens, "last_refill": last_refill})
return False
Patrón Circuit Breaker
// Configuración de circuit breaker con Resilience4j
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.slowCallRateThreshold(80)
.slowCallDurationThreshold(Duration.ofSeconds(2))
.permittedNumberOfCallsInHalfOpenState(10)
.slidingWindowSize(100)
.waitDurationInOpenState(Duration.ofSeconds(30))
.build();
CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);
CircuitBreaker userServiceCB = registry.circuitBreaker("userService");
Supplier<String> decorated = CircuitBreaker
.decorateSupplier(userServiceCB, () -> userClient.getUser(userId));
Explicación
El API gateway es un proxy inverso con inteligencia. Se sitúa entre clientes y servicios backend, centralizando preocupaciones transversales que de otro modo se duplicarían en cada servicio. La idea clave es que no toda preocupación pertenece al gateway: la lógica de negocio debe permanecer en los servicios, pero la autenticación, rate limiting y enrutamiento son responsabilidades del gateway.
Las decisiones de enrutamiento deben ser stateless y determinísticas para que cualquier instancia del gateway pueda manejar cualquier request. La afinidad de sesión (sticky sessions) debe evitarse; si la necesitas, prefiere consistent hashing sobre sticky basado en IP. El rate limiting debe ser distribuido en un despliegue multi-instancia; los contadores en memoria locales son insuficientes porque un cliente puede round-robin entre instancias y evadir los límites.
Los circuit breakers previenen fallos en cascada. Cuando un servicio está luchando, el gateway debe fallar rápido en lugar de encolar requests que van a timeout. Esto protege tanto al servicio en dificultades como al llamador. El estado half-open permite recuperación gradual: después de un enfriamiento, se permite un número limitado de requests para probar si el servicio se ha recuperado.
Escenario Detallado: API Gateway con Kong para E-commerce
Sistema: E-commerce con 6 microservicios detras de Kong
Servicios: users, catalog, orders, payments, inventory, notifications
Plataforma: Kubernetes (EKS) + Kong + Redis + Keycloak
Configuracion de Kong (kong.yml):
services:
- name: user-service
url: http://user-service.default.svc:8080
routes:
- name: users-route
paths: ["/api/users"]
strip_path: true
plugins:
- name: jwt
- name: rate-limiting
config: { minute: 100, hour: 1000 }
- name: correlation-id
config: { header_name: "X-Request-ID" }
- name: order-service
url: http://order-service.default.svc:8080
routes:
- name: orders-route
paths: ["/api/orders"]
strip_path: true
plugins:
- name: jwt
- name: rate-limiting
config: { minute: 50, hour: 500 }
- name: correlation-id
Autenticacion con Keycloak (OIDC):
1. Cliente obtiene JWT de Keycloak: POST /realms/ecommerce/protocol/openid-connect/token
2. Cliente envia JWT en header: Authorization: Bearer <token>
3. Kong valida JWT con clave publica de Keycloak
4. Kong cachea el resultado de validacion en Redis (TTL: 5 min)
5. Si valido, Kong enruta al servicio; si no, retorna 401
Rate limiting distribuido con Redis:
kong.conf:
rate_limiting = redis
redis_host = redis.default.svc
redis_port = 6379
redis_timeout = 2000
Limites por plan:
Free: 100 req/min, 1000 req/hora
Pro: 1000 req/min, 10000 req/hora
Enterprise: 10000 req/min, sin limite por hora
Observabilidad:
- Prometheus plugin: exporta metricas a /metrics
- Zipkin plugin: trazas distribuidas con correlation ID
- File-log plugin: logs en JSON a stdout (recogidos por Fluentd)
- Dashboard Grafana: QPS, latencia p50/p95/p99, error rate por ruta
Circuit breaker por servicio:
kong.yml plugin:
- name: proxy-cache
config: { strategy: "redis", cache_ttl: 300 }
- name: pre-function
functions: [check_upstream_health]
Logica: si error rate > 50% en 30s, abrir circuito por 60s
En circuito abierto: retornar 503 con Retry-After header
En half-open: permitir 10 requests de prueba
Despliegue en Kubernetes:
- Kong via Helm chart: 3 replicas en nodos distintos
- HPA: min 3, max 10 replicas, CPU target 70%
- PodDisruptionBudget: min available 2
- Health check: /status endpoint
- Graceful shutdown: 30s drain period
Metricas de produccion (promedio):
| Metrica | Valor |
|---------|-------|
| QPS total | 12,000 |
| Latencia p50 (gateway) | 1.2ms |
| Latencia p95 (gateway) | 3.8ms |
| Cache hit rate (auth) | 94% |
| Error rate | 0.08% |
| Uptime | 99.97% |
Como manejo versionado de API en el gateway?
Usa path-based versionado: /v1/users, /v2/users. El gateway enruta a diferentes servicios o versiones del mismo servicio. Manten v1 mientras v2 esta en uso. Programa la deprecacion de v1 con un header de respuesta: Sunset: Sat, 31 Dec 2026 23:59:59 GMT. Documenta los cambios entre versiones. Nunca rompas v1 sin aviso con 6 meses de antelacion.
Variantes
| Tipo de Gateway | Mejor Para | Trade-off |
|---|---|---|
| Nginx / OpenResty | Alto throughput, enrutamiento simple | Scripting Lua para lógica personalizada; plataforma de plugins limitada |
| Kong | Rico en plugins, capacidades enterprise | Mayor latencia que Nginx; opción administrada disponible |
| Envoy | Cloud-native, integración con service mesh | Curva de aprendizaje pronunciada; verbosidad de configuración YAML |
| AWS API Gateway | Serverless, plataforma AWS | Vendor lock-in; latencia de cold start para v2 |
| Spring Cloud Gateway | Plataformas Java/Spring | Huella de memoria JVM; acoplamiento fuerte con Spring |
| Traefik | Descubrimiento dinámico Docker/K8s | Más simple que Envoy; menos gestión avanzada de tráfico |
Lo que funciona
- Mantén el gateway stateless; almacena datos de sesión en Redis o tokens del lado del cliente
- Usa I/O asíncrono en el gateway para evitar agotamiento de thread pools bajo carga
- Implementa health checks para cada upstream y elimina instancias no saludables rápidamente
- Registra request IDs y propágalos vía headers para trazabilidad distribuida
- Cachea resultados de autenticación en el gateway para evitar validar el mismo token en cada request
Errores Comunes
- Poner lógica de negocio en el gateway; se convierte en un monolito distribuido
- Usar el gateway como pool de conexiones a base de datos compartido; los servicios deben manejar sus propias conexiones
- Implementar rate limiting por instancia en lugar de distribuido; los clientes evaden límites alcanzando diferentes instancias
- No configurar timeouts en llamadas upstream; un servicio lento puede agotar los threads del gateway
- Ignorar la salud del gateway en monitoreo; el gateway es infraestructura, no plomería invisible
Preguntas Frecuentes
¿Debo usar un gateway o múltiples?
Un gateway es más simple pero se convierte en cuello de botella y punto único de fallo. La mayoría de organizaciones eventualmente dividen por preocupación: un gateway público para clientes externos, un gateway interno para comunicación servicio-a-servicio, y posiblemente un gateway B2B para APIs de socios. La regla general: divide cuando tu configuración de gateway excede 1,000 líneas o cuando diferentes clientes necesitan esquemas de autenticación fundamentalmente diferentes.
¿Cómo manejo conexiones WebSocket a través de un gateway?
Los WebSockets requieren enrutamiento consciente de conexión. El gateway debe mantener la conexión TCP y enrutar frames subsecuentes a la misma instancia upstream. No todos los gateways soportan esto nativamente. Envoy y Nginx sí; AWS API Gateway v2 soporta WebSockets con Lambda o integraciones HTTP. Para cargas WebSocket de alta escala, considera un gateway WebSocket dedicado separado de tu gateway HTTP API.
¿Cuál es el impacto en rendimiento de agregar un gateway?
Un gateway bien afinado agrega 0.5–2ms de latencia por hop para enrutamiento simple. La terminación SSL puede mejorar la latencia total porque los servicios ya no hacen handshakes TLS. El mayor riesgo es mala configuración: enrutamiento con regex excesivamente complejo, I/O bloqueante síncrona, o transformación excesiva de request/response. Haz benchmark de tu gateway independientemente usando herramientas como k6 o vegeta antes de desplegar a producción.
Troubleshooting
- High latency between services: trace the request path. Look for synchronous chains, missing caching, and oversized payloads that cross network boundaries.
- Single point of failure: identify components without redundancy. Add replicas, failover, or circuit breakers before scaling traffic.
- Unexpected coupling between services: review shared databases, libraries, and schemas. Bound contexts should own their data and expose stable interfaces.
- Cost spikes after scaling: right-size instances and use autoscaling with limits. Reserved capacity or spot instances can reduce steady-state spend.
- Difficult to reason about the system: maintain architecture decision records and service dependency maps. Use observability to validate the diagrams.
Errores Comunes en Producción
- Tratar la guía como un checklist para completar una vez en lugar de una práctica por evolucionar.
- Adoptar cada recomendación de golpe en lugar de comenzar con un cambio medido.
- Saltar la evaluación de madurez e imponer prácticas avanzadas a un equipo no preparado.
- No actualizar runbooks y expectativas de guardia al introducir nuevas prácticas.
- Ignorar datos reales de incidentes al priorizar qué partes de la guía aplicar primero.
- No asignar un responsable que revise decisiones trimestralmente.
- Copiar ejemplos sin adaptarlos a las herramientas y restricciones reales del equipo.
- Olvidar medir resultados antes de agregar la siguiente mejora.
Recursos Relacionados
Arquitectura de Microservicios — Cuándo Usarla y Cuándo No
Guía práctica de microservicios: beneficios, trade-offs, patrones comunes y cuándo elegirlos sobre monolitos. Cubre estrategias de descomposición y complejidad operativa.
GuideDe Monolito a Microservicios — Estrategias de Migración
Guía práctica para descomponer monolitos: strangler fig, branch by abstraction y patrones de extracción incremental que reducen riesgo y preservan continuidad del negocio.
GuideChecklist de Seguridad de APIs
Una checklist de seguridad essential para APIs: autenticación, autorización, validación de entrada, rate limiting, encriptación, logging y endurecimiento de despliegue.