StackPractices
advanced Por Mathias Paulenko

Diseño de API Gateway: Resiliencia, Enrutamiento y Seguridad

Guía práctica para diseñar API gateways: enrutamiento, autenticación, rate limiting, circuit breakers, caché y patrones de despliegue para APIs resilientes.

Descripción General

Toda arquitectura de microservicios acaba necesitando 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 un agujero de seguridad. Un gateway bien diseñado se encarga del enrutamiento, la autenticación, el rate limiting, los circuit breakers, la caché y la observabilidad para que cada servicio se centre en su lógica de negocio.

Esta guía recorre qué le pasa a una petición dentro del gateway, qué responsabilidades merece la pena centralizar ahí (y cuáles no), configuraciones funcionales para Envoy, NGINX, Kong y código de middleware, y los patrones de despliegue que evitan que el propio gateway sea lo que tumbe tu plataforma.

Una Petición a Través del Gateway

Antes de elegir tecnologías ayuda ver el pipeline. Una petición típica atraviesa el gateway en un orden fijo — cada etapa o la rechaza pronto o la decora y la pasa adelante:

Pipeline de una petición dentro del gateway: terminación TLS, luego autenticación, luego rate limiting, luego consulta a caché, luego enrutamiento al servicio upstream a través de un circuit breaker.

Dos propiedades de ese pipeline importan al diseñar para el fallo. Primera, el orden es deliberado: la autenticación va antes del rate limiting para que los límites se apliquen por identidad, no por una IP que puede ser un NAT compartido. Segunda, la caché va antes del circuit breaker para que un upstream caído pueda seguir sirviendo respuestas envejecidas pero válidas en lugar de una cascada de 503.

Responsabilidades Principales

PreocupaciónEnfoque de implementaciónTecnología clave
EnrutamientoRouting por path, header u host hacia servicios upstreamNGINX, Kong, Envoy, AWS API Gateway
Rate LimitingToken bucket o leaky bucket por cliente, IP o API keyRedis + Lua, Envoy, Kong
AutenticaciónValidar JWT, API keys u OAuth2 en el bordeKeycloak, Auth0, middleware propio
Circuit BreakerMedir tasas de fallo y fallar rápido con upstreams enfermosResilience4j, Envoy, Polly
Balanceo de cargaRound-robin, least connections o consistent hashingEnvoy, NGINX, service mesh
Caché de respuestasCachear GET por clave o rutaKong proxy-cache, NGINX proxy_cache, Redis
ObservabilidadTrazas distribuidas, métricas y logs de accesoOpenTelemetry, Prometheus, Fluentd
Terminación TLSDescargar TLS en el gateway para quitar CPU a los serviciosNGINX, HAProxy, load balancers cloud
TransformaciónInyección de headers, reescritura de payloads, traducción de protocolosPlugins de Kong, filtros de Envoy, middleware

La línea divisoria que conviene respetar: el gateway posee las preocupaciones idénticas para todos los servicios (auth, límites, routing, trazas). Todo lo que exija conocer el negocio — reglas de precio, control de inventario, validación ligada a un dominio concreto — va en el servicio. Los equipos que difuminan esta línea acaban con un “monolito distribuido” donde desplegar lógica de negocio exige tocar la configuración del gateway.

Gateway vs Load Balancer vs BFF vs Service Mesh

Estos cuatro se confunden constantemente, y elegir el equivocado sale caro:

ComponenteQué haceCuándo lo necesitas
Load balancerReparte tráfico entre instancias del mismo servicioSiempre — normalmente debajo del propio gateway
API gatewayEnruta a servicios diferentes; posee auth, límites, transformaciónMúltiples servicios, APIs externas o de socios
BFF (Backend for Frontend)Capa de agregación por tipo de cliente (payloads web vs móvil)Clientes con necesidades de datos muy distintas
Service meshTráfico este-oeste entre servicios: mTLS, reintentos, telemetríaComunicación interna servicio-a-servicio a escala

Un gateway suele usar un load balancer interno por upstream — son complementarios, no alternativas. Una arquitectura habitual en producción es gateway para tráfico norte-sur más un mesh para este-oeste; el error es colgar un gateway externo en llamadas servicio-a-servicio, donde añade un salto y un dominio de fallo sin beneficio alguno.

Enrutamiento

Las decisiones de routing deben ser sin estado y deterministas para que cualquier instancia del gateway atienda cualquier petición. La afinidad de sesión es una mala señal — si los upstreams necesitan sticky sessions, prefiere consistent hashing sobre afinidad por IP, o arregla el servicio para que externalice su estado.

# 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 }

Las mismas rutas en NGINX se ven más simples cuando no necesitas el ecosistema de filtros de Envoy:

upstream user_service {
    server user-service:3000;
}

server {
    listen 80;

    location /api/users/ {
        proxy_pass http://user_service;
        proxy_set_header Host $host;
        proxy_set_header X-Request-ID $request_id;
    }

    location / {
        return 404;  # las rutas sin match mueren en el borde, no upstream
    }
}

Autenticación en el Borde

El gateway valida quién es el llamante y reenvía el resultado como headers — los servicios no deberían reverificar firmas ni reparsear tokens. Lo que no debe hacer es decidir qué puede hacer el llamante: la autorización fina vive en los servicios que poseen las reglas de negocio.

// gateway/middleware/auth.ts — Validar JWT antes de llegar al backend
import jwt from 'jsonwebtoken';

interface AuthConfig {
  jwtSecret: string;
  publicPaths: string[];  // rutas que saltan la auth
}

function authMiddleware(config: AuthConfig) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (config.publicPaths.some(path => req.path.startsWith(path))) {
      return next();
    }

    const token = req.headers.authorization?.replace('Bearer ', '');
    if (!token) {
      return res.status(401).json({ error: 'Missing authorization header' });
    }

    try {
      const payload = jwt.verify(token, config.jwtSecret) as JwtPayload;

      // Inyectar identidad para los servicios upstream — confían en estos headers
      req.headers['x-user-id'] = payload.sub;
      req.headers['x-user-roles'] = payload.roles.join(',');
      req.headers['x-user-scopes'] = payload.scopes.join(',');

      next();
    } catch (error) {
      if (error instanceof jwt.TokenExpiredError) {
        return res.status(401).json({ error: 'Token expired' });
      }
      return res.status(401).json({ error: 'Invalid token' });
    }
  };
}

Dos detalles de endurecimiento que vale la pena copiar: rechaza antes de hacer trabajo (la comprobación de ruta pública y de token ausente ocurre antes de jwt.verify), y valida contra la clave pública rotada del endpoint JWKS de tu proveedor de identidad en lugar de un secreto compartido siempre que puedas — los secretos simétricos en la config del gateway tienden a sobrevivir a su calendario de rotación.

Para clientes máquina-a-máquina, las API keys son más simples: buscas la clave, compruebas caducidad, aplicas su tier de rate limiting e inyectas x-api-key-id más x-tenant-id para uso upstream. Las claves viven en un almacén rotable, no en archivos de config — para la superficie completa de auth, ver la checklist de seguridad de APIs.

Rate Limiting

El rate limiting debe ser distribuido en un despliegue de gateway multi-instancia. Los contadores en memoria por instancia permiten que un cliente haga round-robin entre réplicas y multiplique su cuota — el bug de rate limiting más común en producción. Guarda los contadores en Redis (o el almacén compartido de tu gateway) y aplica los límites por identidad, no solo por IP.

# Rate limiter tipo token bucket respaldado por 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

Los tiers suelen mapear a planes comerciales — por ejemplo free 100 req/min, pro 1.000 req/min, enterprise 10.000 req/min — y la clave del limiter debería combinar identidad y tier, no solo un ID de cliente. Al rechazar, devuelve 429 con Retry-After y X-RateLimit-Remaining en las respuestas exitosas; los clientes que ven su presupuesto se comportan mejor. Para estrategias dedicadas (leaky bucket, ventana deslizante, cuota vs picos), ver la guía de API rate limiting.

Resiliencia: Circuit Breakers y Timeouts

Los circuit breakers evitan fallos en cascada. Cuando un upstream sufre, el gateway debería fallar rápido en lugar de encolar peticiones que todas expirarán — esto protege tanto al servicio que sufre como a los propios pools de hilos del gateway.

// 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));

El estado half-open es la parte que la gente hace mal: tras el enfriamiento (waitDurationInOpenState), solo pasan unas pocas peticiones sonda — no todo el tráfico — para que un upstream inestable no oscile el breaker. Combina los breakers con timeouts explícitos por upstream (por servicio, no un default global, porque un endpoint de búsqueda y un health check tienen perfiles de latencia distintos), y reintenta solo peticiones idempotentes con backoff — reintentar un POST /payments es como se cobra dos veces a los clientes.

Caché y Transformación

Cachear respuestas GET en el borde reduce carga upstream y mejora latencia, pero hazlo por clave o ruta con TTLs explícitos — y nunca caches respuestas con datos por usuario sin incluir la identidad en la clave:

// gateway/middleware/cache.ts — Cachear respuestas GET con claves por usuario
function cacheMiddleware(cache: ResponseCache, defaultTtl = 60) {
  return async (req: Request, res: Response, next: NextFunction) => {
    if (req.method !== 'GET') return next();

    const cacheKey = `${req.headers['x-user-id']}:${req.path}:${JSON.stringify(req.query)}`;
    const cached = await cache.get(cacheKey);

    if (cached) {
      res.setHeader('X-Cache', 'HIT');
      return res.status(cached.status).send(cached.body);
    }

    const originalSend = res.send;
    res.send = function (data: any) {
      if (res.statusCode === 200) {
        const ttl = parseInt(res.getHeader('Cache-Control')?.toString().match(/max-age=(\d+)/)?.[1] || '') || defaultTtl;
        cache.set(cacheKey, data.toString(), res.statusCode, ttl);
        res.setHeader('X-Cache', 'MISS');
      }
      return originalSend.call(this, data);
    };

    next();
  };
}

La transformación es la otra preocupación de borde que merece centralizarse — request IDs inyectados para trazas, /api/v2/users reescrito a un header x-api-version más un path sin versión, envelopes de respuesta estandarizados. La traducción de protocolos (REST dentro, gRPC fuera) sigue el mismo principio: mapea pares método+path a llamadas gRPC en el borde para que los servicios internos se mantengan agnósticos de protocolo. Mantén la tabla de mapeo explícita en config — las reglas de traducción implícitas se convierten en arqueología al cabo de un año.

Observabilidad en el Borde

El gateway es el único punto que ve todas las peticiones, lo que lo convierte en el lugar más barato de instrumentar de todo el sistema. Tres cosas que recoger ahí:

  • Métricas RED por ruta y por upstream: tasa de peticiones, tasa de errores e histogramas de duración. Un pico en la latencia p99 de /api/orders te dice dónde mirar antes que cualquier dashboard a nivel de servicio.
  • IDs de petición de extremo a extremo: genera X-Request-ID en el borde si no viene, devuélvelo en la respuesta y reenvíalo al upstream para que un ticket de soporte se traduzca en un único ID presente en todas las líneas de log.
  • Propagación de contexto de traza: respeta las cabeceras traceparent/tracestate (W3C Trace Context) o genera un span raíz — el span del gateway es el padre natural de todo lo que ocurre después.

Los access logs merecen mención aparte. Emite JSON estructurado — timestamp, ruta, upstream, status, latencia, bytes, request ID, identidad del cliente — y mantén fuera los tokens de autenticación y las query strings con credenciales. Un solo access_token filtrado en un bucket de logs compartido es un incidente de seguridad, no una línea de log.

Dos fallos a vigilar: cardinalidad sin límite (etiquetas por usuario matarán tu backend de métricas — limita las labels a ruta, upstream y clase de status) y un muestreo demasiado agresivo en el borde, que hace invisibles los upstreams lentos justo cuando más importan.

Dimensionado y Planificación de Capacidad

Los gateways suelen estar limitados por CPU — handshakes TLS, verificación de JWT, reescritura de payloads — y no por memoria, así que dimensiónalos como proxies, no como servidores de aplicación. Un método aproximado:

  1. Mide el pico de RPS en el borde, no por servicio; el gateway lo ve todo, reintentos incluidos.
  2. Mide el RPS sostenible de una réplica con el set real de plugins activado. El coste de los plugins se acumula rápido: una verificación JWT es barata; una transformación Lua por petición, no.
  3. Opera como máximo al 60–70 % de ese benchmark en el pico y mantén capacidad N+1 para que perder una réplica no desencadene un efecto cascada.
  4. Vigila el estado de las conexiones por separado: cada réplica mantiene conexiones a los upstreams, así que una tormenta de reintentos puede agotar los file descriptors antes que la CPU.

Las conexiones de larga duración cambian estas cuentas por completo, por eso merecen tratamiento aparte.

WebSockets y Server-Sent Events

Las conexiones persistentes rompen varias suposiciones del gateway. Los timeouts ajustados para tráfico request/response cortarán las sesiones WebSocket a mitad de camino — la mayoría de gateways necesita un read_timeout de minutos u horas en las rutas WS, además de gestionar explícitamente las cabeceras Upgrade/Connection para que el handshake de upgrade sobreviva. El balanceo también cambia: round-robin reparte bien las peticiones de upgrade, pero cada socket queda anclado a una instancia del upstream, así que drenar esa instancia requiere soporte de migración de conexiones o clientes que reconecten limpiamente. El rate limiting sigue aplicando al handshake de upgrade, no a cada mensaje del socket — aplicar límites por mensaje en el gateway añade latencia sin aportar valor; esa lógica pertenece a la aplicación.

Versionado y Deprecación

El versionado de APIs en el gateway es sobre todo un problema de enrutamiento con una capa de política encima. Los modelos habituales:

  • Por path (/api/v1/users, /api/v2/users): el más explícito y el más fácil de enrutar — cada prefijo de versión mapea a un upstream distinto. También es el más sencillo de cachear y de documentar.
  • Por cabecera (Accept-Version: 2 o un media type como application/vnd.acme.v2+json): mantiene las URLs limpias, pero hace que cada regla de enrutamiento dependa de inspeccionar cabeceras y complica el caché y la depuración por parte del cliente.
  • Por query (?version=2): poco frecuente por buenas razones — fácil de perder accidentalmente y problemático para el caché.

La deprecación es donde el gateway se gana su sitio. Un flujo de retirada que funciona en la práctica: enruta /api/v1/* al upstream legacy, añade las cabeceras Sunset y Deprecation en las respuestas desde el borde, emite un contador deprecated_routes por client ID y contacta a los rezagados antes de devolver 410 Gone en la ruta. Como los datos de tráfico ya viven en el gateway, sabes exactamente qué clientes siguen llamando a la versión antigua — sin encuestas ni suposiciones. El error a evitar es mantener v1 viva “por si acaso” sin cabeceras ni métricas: las versiones nunca mueren solas, mueren cuando puedes ver quién las sigue usando.

Escenario de Producción: Kong para E-commerce

Una arquitectura de referencia concreta: seis servicios (usuarios, catálogo, pedidos, pagos, inventario, notificaciones) detrás de Kong en Kubernetes, con Keycloak para OIDC y Redis para rate limiting. El pipeline de middleware de esta guía — auth, token bucket, caché, circuit breaker sobre un router de prefijos — es ejecutable desde el repositorio companion.

Configuración 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" }

Flujo de autenticación (Keycloak OIDC):
  1. El cliente obtiene JWT: POST /realms/ecommerce/protocol/openid-connect/token
  2. El cliente envía: Authorization: Bearer <token>
  3. Kong valida el JWT con la clave pública del realm
  4. Resultado de validación cacheado en Redis (TTL: 5 min)
  5. Válido → enrutado al servicio; inválido → 401 en el borde

Rate limiting distribuido:
  kong.conf: rate_limiting = redis (redis.default.svc:6379)
  Tiers: Free 100/min · Pro 1.000/min · Enterprise 10.000/min

Observabilidad:
  - Plugin Prometheus → /metrics
  - Correlation-id en cada petición para trazas distribuidas
  - Grafana: QPS, latencia p50/p95/p99, tasa de error por ruta

Despliegue en Kubernetes:
  - Kong vía Helm: 3 réplicas en nodos distintos
  - HPA: min 3, max 10, CPU objetivo 70%
  - PodDisruptionBudget: minAvailable 2
  - Salud: endpoint /status · Apagado graceful: 30s de drain

Para una arquitectura de este tamaño, unos SLOs ilustrativos serían: latencia añadida por el gateway por debajo de 2ms en p95, hit rate de la caché de auth por encima del 90%, tasa de error por debajo del 0,1% — tómalos como objetivos contra los que instrumentar, no como resultados garantizados.

Patrones de Despliegue

El propio gateway no puede ser el punto único de fallo que construiste para eliminar. Mínimo tres réplicas, repartidas entre nodos (o zonas de disponibilidad), detrás de un load balancer:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-gateway
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: gateway
          image: registry.io/api-gateway:latest
          ports:
            - containerPort: 8080
          resources:
            limits: { cpu: 1000m, memory: 512Mi }
            requests: { cpu: 500m, memory: 256Mi }
          livenessProbe:
            httpGet: { path: /health, port: 8080 }
            periodSeconds: 10
          readinessProbe:
            httpGet: { path: /ready, port: 8080 }
            periodSeconds: 5

Detalles que importan más que el YAML: separa /health (liveness — el proceso vive) de /ready (readiness — config cargada, upstreams alcanzables); drena conexiones al apagar para que las peticiones en vuelo terminen; mantén un PodDisruptionBudget para que el mantenimiento de nodos nunca tumbe todas las réplicas a la vez; y versiona tu config declarativa en git — tanto deck de Kong como el xDS de Envoy tratan la config como artefactos desplegables que deberían salir como código.

Mejores Prácticas

  • Mantén el gateway sin estado — datos de sesión en Redis o tokens del cliente, nunca en memoria del gateway.
  • Usa I/O asíncrona y pools de conexiones hacia upstreams para evitar agotar hilos y overhead de handshake TCP.
  • Propaga request IDs inyectados en el borde por todos los servicios para trazas distribuidas.
  • Cachea resultados de autenticación (con TTLs cortos) para no golpear al proveedor de identidad en cada petición.
  • Separa gateways internos y externos cuando los esquemas de auth y rate limits diverjan — una talla no le sirve a nadie.
  • Pon límites de tamaño de petición en el borde para que payloads gigantes nunca lleguen a la memoria del servicio.
  • Trata la config del gateway como código — versionada, revisada y desplegada con la misma disciplina que los deploys de aplicación.

Errores Comunes

  • Lógica de negocio en el gateway: reglas de precio y validación de dominio en plugins del gateway crean un monolito distribuido que despliega a la par.
  • Rate limiting por instancia: contadores locales permiten esquivar límites al caer en réplicas distintas — siempre estado compartido.
  • Timeouts de upstream ausentes: un servicio lento agota los hilos del gateway y mata rutas sanas.
  • Reintentos en métodos no idempotentes: reintentar escrituras sin claves de idempotencia duplica efectos secundarios.
  • Cambios rompedores sin versionar: retira versiones de API con headers Sunset y meses de aviso, no un 404 una mañana.
  • Ignorar las métricas del gateway: el gateway es infraestructura en el camino crítico — necesita el mismo rigor de alertas que tus bases de datos.

Cuándo Usar

  • Al migrar de un monolito a microservicios y necesitar un punto de entrada unificado — combina de forma natural con el enfoque strangler fig.
  • Clientes hablando directo con servicios y duplicando auth, reintentos y logging en cada SDK.
  • Fallos en cascada, auth inconsistente o puntos ciegos de observabilidad entre servicios.
  • Múltiples tipos de cliente (API pública, socios, herramientas internas) que necesitan políticas distintas en el borde.

Cuándo NO Usar

  • Un monolito o un par de servicios — el gateway es un salto extra y un dominio de fallo sin beneficio.
  • Llamadas internas servicio-a-servicio — para eso está un service mesh o mTLS directo.
  • Equipos que no pueden operar el gateway en sí — un gateway mal llevado es peor que ninguno; las opciones gestionadas (AWS API Gateway, Apigee) cambian control por carga operativa.

Solución de Problemas

  • Las peticiones esquivan el rate limit de forma intermitente: bug clásico de contador por instancia — el cliente cae en réplicas distintas. Mueve los contadores a Redis y verifica que todas las réplicas comparten el almacén.
  • 401s repentinos tras una rotación de claves: el gateway cacheó el JWKS viejo o el resultado de validación demasiado tiempo. Acorta el TTL de la caché de auth durante rotaciones y obtén las claves del endpoint JWKS, no de config estática.
  • La latencia añadida por el gateway pica bajo carga: busca cadenas síncronas — llamada de auth, luego rate limit, luego consulta de caché en serie. Paraleliza etapas independientes y usa I/O asíncrona.
  • Los headers desaparecen entre gateway y servicio: filtros allowlist/denylist agresivos eliminan los X-*. Audita los plugins de transformación; el sospechoso habitual es una regla por defecto de “strip unknown headers”.
  • Un upstream inestable lo tumba todo: umbrales del circuit breaker demasiado permisivos o timeouts ausentes. Pon timeouts por servicio y verifica que el breaker abre con un test de caos, no solo en el archivo de config.
  • Los reintentos amplifican la caída en vez de absorberla: reintentos sin jitter llegan como oleadas sincronizadas sobre un servicio que se está recuperando. Limita el número de reintentos a 1–2, añade backoff exponencial con jitter y fija un presupuesto global de reintentos para que el gateway nunca multiplique la carga más allá de un pequeño porcentaje de la base.
  • Los cambios de config llegan a las réplicas en momentos distintos: desplegar un cambio de ruta réplica a réplica deja a los clientes contra comportamientos mezclados durante minutos. Empuja la config por un pipeline versionado y condiciona el rollout a los health checks, o acéptalo conscientemente solo para cambios no disruptivos.

Lecturas Adicionales

Preguntas frecuentes

¿Debo usar un gateway o varios?

Empieza con uno — se mantiene simple hasta que deja de serlo. Divide cuando audiencias distintas necesiten políticas fundamentalmente diferentes: un gateway público para clientes externos, uno interno para servicio-a-servicio, quizá uno B2B para socios. El disparador práctico es cuando la config de auth o rate limits de una audiencia empieza a filtrarse en las rutas de otra.

¿Qué diferencia hay entre un API gateway y un load balancer?

Un load balancer reparte tráfico entre instancias del mismo servicio. Un API gateway enruta a servicios diferentes y posee las preocupaciones transversales (auth, límites, transformación). Un gateway suele usar un load balancer por upstream internamente — son capas complementarias, no competidoras.

¿Cómo manejo conexiones WebSocket a través de un gateway?

Los WebSockets necesitan routing consciente de conexión: el gateway mantiene la conexión TCP y fija los frames al mismo upstream. Envoy y NGINX lo soportan de forma nativa; AWS API Gateway v2 soporta WebSockets vía integraciones Lambda o HTTP. A gran escala, monta un gateway WebSocket dedicado en lugar de mezclar conexiones persistentes en el pool de conexiones de tu gateway HTTP.

¿Qué impacto en rendimiento tiene añadir un gateway?

Un gateway bien afinado añade aproximadamente 0,5–2ms por salto para routing simple. La terminación TLS puede incluso reducir la latencia total porque los upstreams se ahorran los handshakes. El riesgo real es la mala configuración — routing con mucho regex, I/O bloqueante, transformación excesiva. Haz benchmark de forma independiente con k6 o vegeta antes de llevarlo a producción.

¿Debería el gateway encargarse también de la autorización?

Autenticación sí, autorización fina no. El gateway responde "¿quién es?" y reenvía headers de identidad; los servicios responden "¿qué puede hacer?" porque poseen las reglas. Comprobaciones gruesas (admin vs usuario normal) pueden vivir en el borde si el JWT ya lleva roles.

¿Cómo versiono APIs en el gateway?

El versionado por path (/v1/users, /v2/users) es el default pragmático — el gateway enruta cada versión al upstream correspondiente y reescribe el path. Retira versiones con un header Sunset en la respuesta y al menos seis meses de aviso; nunca devuelvas un 404 a una versión que los clientes siguen llamando.