Módulo 3 · Diseño de APIs

Lección 16 — gRPC, GraphQL, WebSockets y SSE

Cuándo REST no basta y qué pagas con cada alternativa.

Publicada
En esta lección
  1. Ejercicio 1 — Matriz
  2. Ejercicio 2 — SSE
  3. Ejercicio 3 — Del commit al push
  4. Ejercicio 4 — Carga
  5. Ejercicio 5 — ADR-0007 (esqueleto)
  6. Resumen del profesor

Ejercicio 1 — Matriz

(a) REST + OpenAPI: universidad de clientes, caché, curl. (b) gRPC: contratos binarios estrictos entre servicios tuyos, streaming, sin caché necesario. (c) GraphQL podría (dashboards configurables) — pero si el dashboard tiene 3 formas fijas, REST con BFF ligero gana; GraphQL se justifica con combinatoria real. (d) WebSocket: bidireccional real. (e) SSE o push de cola (notificación): unidireccional, "listo" ocurre una vez — ni WS ni GraphQL.

  1. Hoy ninguno es urgente: polling cacheado de disponibilidad resuelve hasta tener usuarios reales mirando mapas simultáneos. El push se compra cuando el negocio pide el instante — ADR-0007 lo registra.

Ejercicio 2 — SSE

  1. curl -N muestra los eventos llegando (event: availability, data: {"free": 41}): la conexión permanece abierta.
  2. El PUBLISH desde redis-cli aparece al instante: el stream es un canal, el estado vive en Redis/BD.
  3. Sin tráfico, intermediarios (proxy LB, Nginx buffers) asumen conexión muerta y la cierran (30-60s típico). El keepalive cada <timeout mantiene NAT/proxies vivos; proxy_buffering off evita que Nginx acumule el stream en buffer y lo entregue en ráfagas (o nunca, si el buffer nunca llena).

Ejercicio 3 — Del commit al push

  1. En el código: transaction.on_commit(lambda: redis.publish(...)) — Django lo ejecuta solo si el commit fue real.
  2. Si publicas dentro de atomic() y luego hay rollback, el cliente recibió "asiento vendido" que nunca pasó (el rollback restauró): el push MIENTE. La regla general de la 30 (outbox): solo publicar tras commit; on_commit es la versión simple.

Ejercicio 4 — Carga

  1. 200 ESTAB y el proceso aguanta (I/O async). Memoria por conexión de Uvicorn: KB, no MB. Con keepalive 1s vs 15s: solo cambia el tráfico del ping (no el número de conexiones); más ping = más CPU trivial y menos proxies que corten.
  2. Con 2.000 conexiones: ulimit -n (ficheros abiertos por proceso — cada socket es un fd) salta con el default 1024: se sube (systemd LimitNOFILE) y se dimensiona LB/keepalive. La lección de operación: el límite que importa no es CPU, es fds y memoria por conexión — la 05/55.

Ejercicio 5 — ADR-0007 (esqueleto)

Contexto: compradores miran mapas y los asientos se venden bajo sus pies; polling cacheado genera consultas duplicadas y el "instante" reduce frustración de checkout. Decisión: SSE con Redis pubsub + Last-Event-ID; polling como fallback. Alternativas: polling (no instantáneo), WS (sobrecoste bidireccional sin caso). Consecuencias: conexiones persistentes (dimensionar fds/LB), keepalive y buffering off en Nginx, duplicados en reconexión (client dedup por evento), y una pieza nueva de operación (Redis pubsub ya está: el broker de la 29 lo usa).


Resumen del profesor

  • REST público, gRPC interno si hay microservicios, GraphQL solo con combinatoria real, push solo con negocio que exige el instante.
  • SSE unidireccional + reconnect nativo cubre disponibilidad en vivo; WS espera bidireccionalidad.
  • Publica tras commit (on_commit): un push de un rollback es una mentira al cliente.
  • El límite real de streams: fds y proxies (keepalive/buffering), no CPU.

Después: Lección 17 — Webhooks y APIs de terceros.