Repassa el que vam escriure al mòdul 4: un limitador de peticions amb Redis, la validació de tokens JWT i de tokens OAuth per JWKS, una llista blanca d'orígens per a CORS, capçaleres de memòria cau amb ETag i 304, compressió, mètriques a /metriques. Sis peces d'infraestructura, amb el seu codi, les seves proves i el seu manteniment.
Ara la incomoditat: gairebé totes existeixen resoltes una capa per damunt. Un API gateway fa les sis, amb configuració en lloc de codi, i les fa per a totes les teves APIs alhora. La pregunta òbvia és si vam perdre el temps. La resposta és que no —entendre un mecanisme és el que permet decidir si delegar-lo i depurar-lo quan falli—, però la pregunta següent sí que és difícil i és la que ocupa mitja lliçó: què es delega al gateway i què s'ha de quedar a l'aplicació.
I hi ha una segona meitat. Tenim una API excel·lent, documentada i desplegada. Però CataBox, l'aplicació de tercers que s'integra per OAuth, no té ni idea de com començar: on registrar-se, com aconseguir credencials, on provar sense trencar res, quins límits té, quan canviarà alguna cosa. Això és un portal de desenvolupador, i determina si la teva API es fa servir o s'abandona la primera hora.
Aquesta lliçó tanca el mòdul 5 amb les dues capes que envolten l'API: la que la protegeix i la que l'explica.
Contingut
- Què és un API gateway i quin problema resol
- Les funcions que assumeix, comparades amb el mòdul 4
- Què delegar i què no delegar mai
- L'arquitectura completa: CDN, gateway, serveis
- El patró backend for frontend
- El service mesh és una altra cosa
- Els productes i com es comparen
- Configuració de Kong per a la Botiga Aroma
- L'alternativa amb NGINX
- Quins middlewares podríem retirar i quins no
- Els riscos del gateway
- Què és un portal de desenvolupador
- Què conté un bon portal
- Registre d'aplicacions i credencials OAuth
- El temps fins a la primera crida amb èxit
- Cicle de vida i inventari d'APIs
- Monetització, com a nota
- Balanç del mòdul 5
- Què és un API gateway i quin problema resol
Un API gateway és un servidor que se situa davant d'una o diverses APIs i actua com a porta única: rep tot el trànsit extern, aplica polítiques transversals i reenvia les peticions al servei que correspongui.
El problema que resol es veu millor amb l'escenari que li dona sentit. Imagina't que la Botiga Aroma creix:
Sense gateway Amb gateway
───────────────────────────── ─────────────────────────────
api.botigaaroma.example api.botigaaroma.example
→ API de botiga → GATEWAY
· rate limiting propi · rate limiting (una vegada)
· CORS propi · CORS (una vegada)
· JWT propi · JWT (una vegada)
· mètriques (una vegada)
inventari.intern ↓
→ API d'inventari /v1/cafes → API de botiga
· rate limiting propi /v1/comandes → API de botiga
· CORS propi /v1/estoc → API d'inventari
· JWT propi (igual?) /v1/suggeriments → recomanacions
recomanacions.intern
→ API de recomanacions
· rate limiting propi (o no?)Amb una API, el gateway aporta poc: estàs movent de lloc codi que ja funciona. Amb cinc, la diferència és enorme: sense ell, cada equip reimplementa el rate limiting a la seva manera, tres dels cinc ho fan malament, la política de CORS divergeix, i no hi ha un lloc on respondre «quantes peticions rebem en total?».
La formulació precisa: un gateway centralitza les preocupacions transversals del trànsit. I com tot el que està centralitzat, aporta consistència i crea un punt únic de fallada. Totes dues coses alhora.
- Les funcions que assumeix, comparades amb el mòdul 4
| Funció | Com ho vam fer nosaltres | Què fa el gateway | És delegable? |
|---|---|---|---|
| Rate limiting | express-rate-limit + Redis (04-04) |
Plugin amb límits per consumidor, ruta i nivell | Sí, totalment |
| Terminació TLS | Delegada al proxy | Certificats, renovació, versions de TLS | Sí |
| Validació de JWT | middleware/autenticacio.js (03-06) |
Verifica signatura, exp, iss, aud i rebutja abans d'arribar a tu |
Sí, la validació |
| OAuth i JWKS | autenticacio-oauth.js + jose (04-03) |
Descarrega i desa el JWKS a la memòria cau, valida àmbits | Sí |
| CORS | cors(opcionsCors) (04-05) |
Llista blanca declarativa, preflight | Sí |
| Memòria cau de respostes | ETag + Redis cache-aside (04-06) |
Memòria cau de respostes per URI i Vary |
Parcialment |
| Compressió | compression |
gzip i brotli, normalment més ben implementat | Sí |
| Encaminament i versionat | app.use('/v1', rutesV1) |
Ruta a servei; /v1 i /v2 a destins diferents |
Sí |
| Transformació | Mapejadors (03-03) | Afegir, treure o reanomenar capçaleres i camps | Sí, amb cura |
| Agregació | No la fem | Combinar diverses crides en una resposta | Amb molta cura |
| Quotes per client | No les fem | 10.000 crides al mes per pla | Sí |
| Mètriques i logs | prom-client + pino (04-07) |
Mètriques per consumidor i ruta, sense tocar codi | Sí, com a complement |
| mTLS | No ho fem | Certificats de client per a socis | Sí |
| Llistes negres | No les fem | Bloqueig per IP, país, patró o reputació | Sí |
| Reintents i circuit breaker | Parcial (04-04) | Reintents, timeouts i curtcircuit per destí | Sí |
| Validació d'esquema | Zod (03-04) | Validació contra el JSON Schema del contracte | Parcialment |
| Autorització per recurs | exigirRol, comprovació de propietat |
No ho pot saber | MAI |
| Lògica de negoci | Serveis | No pot | MAI |
| Validació semàntica | Serveis | No pot | MAI |
Les tres últimes files són l'assumpte de l'apartat següent.
- Què delegar i què no delegar mai
El criteri, en una frase: el gateway sap qui crida i on; només l'aplicació sap què hi ha a dins i què significa.
D'aquí se'n dedueix tota la resta:
Es delega bé el que depèn únicament de la petició i de la identitat: rate limiting, terminació TLS, verificació criptogràfica del token, CORS, compressió, encaminament. Són decisions que no requereixen consultar la teva base de dades.
No es delega mai, i convé ser taxatiu:
1. L'autorització a nivell de recurs. El gateway pot comprovar que el token és vàlid i que té l'àmbit comandes.llegir. No pot comprovar que com_5001 pertany a cli_842, perquè això exigeix consultar la base de dades. I aquella comprovació és precisament la que evita la fallada número u de l'OWASP API Top 10 (04-02), el BOLA. Si deixes de fer-la a l'aplicació perquè «ja ho mira el gateway», tens una vulnerabilitat crítica.
// src/serveis/comandes.js — això es queda a l'aplicació, SEMPRE.
export async function obtenirComanda(comandaId, usuari) {
const comanda = await repositoriComandes.cercarPerId(comandaId);
if (!comanda) throw errors.comandaNoTrobada(comandaId);
// El gateway ja ha validat el token i l'àmbit. El que NO pot saber
// és de qui és aquesta comanda: només la base de dades ho sap.
const esSeva = comanda.clientId === usuari.id;
const esPersonal = ['empleat', 'administrador'].includes(usuari.rol);
if (!esSeva && !esPersonal) throw errors.permisosInsuficients();
return comanda;
}2. La lògica de negoci. Que una comanda pagat no pugui tornar a pendent_pagament, que no es vengui més estoc del disponible, que el termini de devolució siguin 14 dies. Ficar regles de negoci a la configuració del gateway crea un segon lloc on viu el domini, sense proves, sense tipus i sense revisió de codi. És l'error més car que es comet amb aquestes eines.
3. La validació semàntica. El gateway pot rebutjar un cos que no compleixi el JSON Schema —tipus, rangs, camps obligatoris— i és una defensa útil. No pot validar que cafeId existeixi, que preuMin sigui menor que preuMax, o que el client pugui comprar aquell cafè.
4. La defensa en profunditat. Encara que el gateway validi el token, l'aplicació ha de continuar validant-lo. Motiu: si algú arriba al teu servei esquivant el gateway —una regla de xarxa mal posada, un desplegament nou, un atacant dins de la xarxa— la teva API quedaria totalment oberta. La regla és zero trust: el servei no confia que el trànsit vingui d'on sembla.
Una taula que resumeix l'assignació:
| Pregunta | Qui respon |
|---|---|
| Aquest token està signat per qui diu i no ha caducat? | Gateway (i també l'app) |
| Aquest consumidor ha superat la seva quota? | Gateway |
| Aquest origen pot fer peticions des del navegador? | Gateway |
Aquest token té l'àmbit comandes.escriure? |
Gateway (i també l'app) |
| Aquest usuari pot veure aquesta comanda? | Només l'aplicació |
| Hi ha estoc suficient per a aquesta línia? | Només l'aplicació |
| La comanda està en un estat que admet pagament? | Només l'aplicació |
- L'arquitectura completa: CDN, gateway, serveis
graph LR
SPA[SPA<br/>botigaaroma.example] --> CDN
MOB[Aroma Mòbil] --> CDN
PAN[Panell intern] --> CDN
CAT[CataBox<br/>OAuth] --> CDN
RE[RàpidEnviaments<br/>mTLS] --> CDN
CDN[CDN i WAF<br/>TLS, DDoS, estàtics, memòria cau de vora] --> GW
GW[API Gateway<br/>rate limiting, JWT, CORS,<br/>encaminament, quotes, mètriques]
GW --> API[API Botiga Aroma<br/>rutes /v1/cafes i /v1/comandes]
GW --> INV[Servei d'inventari]
GW --> REC[Servei de recomanacions]
API -.gRPC.-> INV
API -.gRPC.-> REC
API --> BD[(SQLite o PostgreSQL)]
API --> R[(Redis)]
Què fa cada capa i per què és on és:
| Capa | Responsabilitat | Per què allà |
|---|---|---|
| CDN / WAF | TLS, mitigació de DDoS, estàtics, memòria cau de vora, bloqueig geogràfic | Tan a prop com sigui possible de l'usuari i tan lluny com sigui possible de la teva infraestructura. El trànsit maliciós es filtra abans de consumir recursos teus. |
| Gateway | Autenticació, rate limiting, CORS, encaminament, quotes, mètriques per consumidor | Un sol punt que coneix tots els consumidors i tots els serveis |
| Aplicació | Lògica de negoci, autorització per recurs, validació semàntica, persistència | És l'única cosa que coneix el domini |
| Est-oest (gRPC) | Comunicació entre serveis interns | No passa pel gateway: seria una volta innecessària i un coll d'ampolla |
Les fletxes puntejades del diagrama són importants: el trànsit nord-sud (de fora cap a dins) passa pel gateway; el trànsit est-oest (entre serveis interns) no. Fer que les crides internes surtin i tornin a entrar pel gateway multiplica la latència i converteix el gateway en el coll d'ampolla de tot el sistema.
- El patró backend for frontend
L'Aroma Mòbil té un problema que la SPA no té: per pintar la pantalla d'inici necessita el catàleg destacat, les comandes recents del client i les seves preferències. Amb l'API tal qual, són tres peticions, sobre una xarxa mòbil amb 150 ms de latència i una bateria que es gasta.
Un BFF és una API intermèdia dedicada a un consumidor concret, que agrega i adapta:
// bff-mobil/src/rutes/inici.js
// El BFF de l'Aroma Mòbil: UNA crida del mòbil, tres a la xarxa interna (ràpides).
router.get('/inici', autenticar, asincron(async (req, res) => {
const [destacats, comandes, preferencies] = await Promise.all([
apiBotiga.obtenirCafes({ destacat: true, limit: 6 }),
apiBotiga.obtenirComandesDeClient(req.usuari.id, { limit: 3 }),
apiBotiga.obtenirPreferencies(req.usuari.id),
]);
// A més d'agregar, APRIMA: el mòbil no necessita notesTast ni _links
// a la pantalla d'inici, i cada byte compta en una xarxa mòbil.
res.json({
destacats: destacats.dades.map((c) => ({
id: c.id, nom: c.nom, preuEuros: c.preuEuros, imatge: c._links.imatge.href,
})),
comandesRecents: comandes.dades.map((c) => ({
id: c.id, estat: c.estat, totalEuros: c.totalEuros, data: c.dataCreacio,
})),
torrefaccioPreferida: preferencies.torrefaccio,
});
}));| API general | BFF | |
|---|---|---|
| Consumidors | Tots | Un |
| Qui el manté | Equip de plataforma | L'equip del client que serveix |
| Pot canviar | Amb cicle de deprecació (02-07) | Quan vulgui, juntament amb el seu client |
| Risc | — | Duplicar lògica de negoci a cada BFF |
L'avantatge polític del BFF és tan important com el tècnic: l'equip mòbil pot canviar el seu BFF sense negociar amb ningú ni esperar un cicle de versionat, perquè és l'únic consumidor. El parany és que un BFF acabi amb regles de negoci pròpies que divergeixen de l'API. Regla: el BFF agrega, filtra i adapta formats; no decideix res del domini.
Alguns gateways ofereixen agregació per configuració, sense escriure un BFF. Funciona per a casos trivials i es torna inmanejable tan bon punt hi ha lògica condicional. Si necessites un if, escriu un BFF.
- El service mesh és una altra cosa
Es confonen constantment, així que convé separar-los:
| API gateway | Service mesh | |
|---|---|---|
| Trànsit | Nord-sud: de fora cap a dins | Est-oest: entre serveis interns |
| On viu | Un punt d'entrada centralitzat | Un sidecar al costat de cada servei |
| Què resol | Autenticació externa, quotes, exposició pública | mTLS intern, reintents, repartiment de trànsit, observabilitat |
| Exemples | Kong, Apigee, AWS API Gateway | Istio, Linkerd, Consul |
| Quan cal | Tan bon punt exposes una API a tercers | Amb molts serveis interns i equips que els operen |
No són alternatives: són complementaris, i moltes arquitectures madures tenen tots dos. Per a la Botiga Aroma avui, amb una API i dos serveis interns, un service mesh és clarament prematur: el seu cost operatiu només s'amortitza amb desenes de serveis.
- Els productes i com es comparen
| Producte | Model | Extensibilitat | Cost | Portal inclòs | Encaix amb la Botiga Aroma |
|---|---|---|---|---|---|
| Kong | Autogestionat (OSS) o gestionat | Alta: plugins en Lua, JS, Python, Go | Gratis (OSS) / de pagament | Sí, a l'edició de pagament | Bona opció: potent i amb sortida cap al gestionat |
| NGINX | Autogestionat | Mitjana: mòduls, Lua amb OpenResty | Gratis / NGINX Plus | No | Si ja el tens de proxy i necessites poc més |
| Traefik | Autogestionat | Mitjana | Gratis / de pagament | No | Excel·lent a Docker i Kubernetes: descobreix serveis sol |
| AWS API Gateway | Gestionat | Mitjana: Lambda com a autoritzador | Per petició | Sí, bàsic | Natural si ja ets a AWS; el cost escala amb el trànsit |
| Apigee (Google) | Gestionat | Molt alta | Alt | Sí, molt complet | Empresa gran amb monetització i molts socis |
| Azure API Management | Gestionat | Alta: polítiques XML | Mitjà-alt | Sí, molt complet | Ecosistema Microsoft |
| Tyk | Tots dos | Alta | Gratis (OSS) / de pagament | Sí | Alternativa sòlida a Kong, portal inclòs a OSS |
| Cloudflare / Fastly | Gestionat, a la vora | Mitjana: Workers | Baix-mitjà | Parcial | Ja el tens com a CDN; moltes funcions de gateway a la vora |
Criteris d'elecció, que s'assemblen molt als de 05-03:
- Qui l'operarà? Un Kong autogestionat en alta disponibilitat és un sistema distribuït més per mantenir, apedaçar i monitorar. Si l'equip és petit, l'opció gestionada gairebé sempre guanya.
- On és la resta? Si ja ets a AWS amb ECS, AWS API Gateway estalvia integració. Si ja fas servir Cloudflare, bona part de la feina es pot fer allà.
- Necessites un portal? Si tindràs tercers com CataBox, el portal és la meitat del producte. Kong OSS no el porta; Tyk sí; Apigee i Azure APIM porten els més complets.
- Quant costa per petició? Els gestionats cobren per milió de peticions. Amb volums alts, el compte pot superar el cost d'operar-lo tu.
- Quin model de configuració? La configuració declarativa en YAML versionada a Git (Kong amb
decK, Traefik, Gateway API de Kubernetes) és molt superior a configurar per interfície gràfica: es revisa en un pull request i es desplega amb la canalització de 05-05.
- Configuració de Kong per a la Botiga Aroma
Configuració declarativa completa, versionable al repositori com a gateway/kong.yaml:
# gateway/kong.yaml — configuració declarativa del gateway de la Botiga Aroma.
# S'aplica amb: deck gateway sync gateway/kong.yaml
# Viu al repositori i es desplega des de la canalització de CI (05-05).
_format_version: "3.0"
# ---------------------------------------------------------------------------
# SERVEIS: els destins interns. El gateway no els exposa directament.
# ---------------------------------------------------------------------------
services:
- name: api-botiga-aroma
url: http://api-botiga-aroma.intern:3000
retries: 2 # reintents davant de fallada de connexió
connect_timeout: 2000
write_timeout: 10000
read_timeout: 10000
routes:
# Ruta principal: tot /v1 va a l'API. El versionat a la ruta (02-07)
# permet que demà /v2 apunti a un altre servei sense tocar res més.
- name: v1
paths: ["/v1"]
strip_path: false # l'API espera rebre /v1: NO l'hi treiem
protocols: ["https"] # només HTTPS; l'HTTP es redirigeix abans
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
# L'inici de sessió té la seva pròpia ruta per poder aplicar-li un límit diferent.
- name: v1-sessions
paths: ["/v1/sessions"]
strip_path: false
protocols: ["https"]
methods: ["POST"]
# La documentació (05-02) és pública i no porta autenticació.
- name: documentacio
paths: ["/docs"]
strip_path: false
protocols: ["https"]
methods: ["GET", "HEAD"]
# ---------------------------------------------------------------------------
# CONSUMIDORS: qui crida. Permet límits i quotes per client.
# ---------------------------------------------------------------------------
consumers:
- username: spa-botigaaroma
tags: ["intern", "primera-part"]
- username: aroma-mobil
tags: ["intern", "primera-part"]
- username: panel-intern
tags: ["intern"]
- username: rapidenviaments
tags: ["soci"]
- username: catabox
tags: ["tercer", "pla-gratuit"]
# ---------------------------------------------------------------------------
# PLUGINS GLOBALS: s'apliquen a tot el trànsit.
# ---------------------------------------------------------------------------
plugins:
# --- Rate limiting: els mateixos límits de 04-04, ara declaratius ---------
- name: rate-limiting
config:
minute: 600 # el límit global de 04-04
hour: 20000
policy: redis # estat compartit entre instàncies del gateway
redis:
host: redis.intern
port: 6379
database: 1 # base diferent de la de l'aplicació
fault_tolerant: true # si Redis cau, DEIXA PASSAR en lloc de bloquejar
hide_client_headers: false
limit_by: consumer # per consumidor autenticat, no per IP
error_message: '{"error":{"codi":"limit_peticions","missatge":"Has superat el límit de peticions.","detalls":[]}}'
# --- Correlació de traces: la capçalera de 04-07 --------------------------
- name: correlation-id
config:
header_name: Aroma-Traca-Id
generator: uuid
echo_downstream: true # també es retorna al client
# --- Observabilitat: mètriques per servei, ruta i consumidor --------------
- name: prometheus
config:
per_consumer: true # mètriques segmentades per consumidor
status_code_metrics: true
latency_metrics: true
# Compte amb la cardinalitat (04-07): per_consumer està bé perquè
# els consumidors són desenes; MAI etiquetar per usuari final.
- name: http-log
config:
http_endpoint: http://colleccio-logs.intern:9880/kong
custom_fields_by_lua:
traca_id: "return kong.request.get_header('Aroma-Traca-Id')"
# --- Compressió i capçaleres de resposta ---------------------------------
- name: response-transformer
config:
remove:
headers: ["Server", "X-Powered-By"] # no revelar la pila (04-02)
add:
headers:
- "Strict-Transport-Security: max-age=31536000; includeSubDomains"
- "X-Content-Type-Options: nosniff"
# ---------------------------------------------------------------------------
# PLUGINS PER RUTA
# ---------------------------------------------------------------------------
# --- CORS: la llista blanca de 04-05, ara al gateway ---------------------
- name: cors
route: v1
config:
origins:
- https://botigaaroma.example
- https://panel.botigaaroma.example
# MAI "*" juntament amb credentials: true. És la regla d'or de 04-05.
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
headers:
- Authorization
- Content-Type
- Idempotency-Key
- If-Match
- If-None-Match
exposed_headers: # sense això, el navegador no les veu
- ETag
- Link
- Location
- Retry-After
- Aroma-Traca-Id
- Aroma-RateLimit-Limit
- Aroma-RateLimit-Restants
- Aroma-RateLimit-Reinici
credentials: true
max_age: 3600 # desa el preflight a la memòria cau una hora
preflight_continue: false # el gateway respon l'OPTIONS: l'app ni se n'assabenta
# --- Validació de JWT i OAuth (03-06 i 04-03) ----------------------------
- name: jwt-signer # valida contra el JWKS del proveïdor OIDC
route: v1
config:
access_token_issuer: https://auth.botigaaroma.example
access_token_jwks_uri: https://auth.botigaaroma.example/.well-known/jwks.json
access_token_leeway: 5 # tolerància de rellotge, en segons
verify_access_token_signature: true
verify_access_token_expiry: true
verify_access_token_issuer: true
# Propaga cap a l'API la identitat ja verificada, en una capçalera pròpia.
# L'aplicació CONTINUA validant el token: defensa en profunditat.
upstream_access_token_header: Authorization
# --- Límit específic de l'inici de sessió: molt més estricte (04-04) ------
- name: rate-limiting
route: v1-sessions
config:
minute: 5 # cinc intents per minut contra la força bruta
policy: redis
redis: { host: redis.intern, port: 6379, database: 1 }
limit_by: ip # aquí sí per IP: encara no hi ha consumidor autenticat
fault_tolerant: false # si Redis cau, MILLOR BLOQUEJAR que deixar passar
error_message: '{"error":{"codi":"limit_peticions","missatge":"Massa intents d''inici de sessió.","detalls":[]}}'
# --- La documentació no porta autenticació -------------------------------
- name: request-termination
route: documentacio
enabled: false # marcador: aquí NO s'aplica jwt-signer
# --- Quota mensual per a tercers (CataBox) -------------------------------
- name: rate-limiting-advanced
consumer: catabox
config:
limit: [100, 10000]
window_size: [60, 2592000] # 100/minut i 10.000/mes: el pla gratuït
identifier: consumer
strategy: redis
sync_rate: 1
# --- Soci logístic: límits amplis i mTLS ---------------------------------
- name: mtls-auth
consumer: rapidenviaments
config:
ca_certificates: ["<id-de-la-ca-de-socis>"]
skip_consumer_lookup: false
revocation_check_mode: STRICTQuatre punts d'aquella configuració que mereixen comentari:
fault_tolerantdiferent segons la ruta. Al límit global éstrue: si Redis cau, preferim deixar passar trànsit a tirar l'API sencera. A l'inici de sessió ésfalse: si Redis cau, preferim bloquejar a quedar-nos sense protecció contra la força bruta. És una decisió de disponibilitat davant de seguretat que s'ha de prendre conscientment per a cada cas, i el gateway obliga a explicitar-la.limit_by: consumerdavant delimit_by: ip. Limitar per IP castiga tots els usuaris darrere d'un NAT corporatiu i no protegeix d'un atacant amb moltes IP. Per consumidor autenticat és el correcte… tret de l'inici de sessió, on encara no hi ha consumidor.exposed_headers. L'error de CORS més frustrant de 04-05: el navegador repETagperò JavaScript no el pot llegir si no és aAccess-Control-Expose-Headers. Aquí queda declarat d'una vegada per a tota l'API.preflight_continue: false. El gateway respon elsOPTIONSi l'aplicació no els veu. Estalvia latència i una part del trànsit, però significa que el teu middleware de CORS deixa d'executar-se per al preflight: si algun dia treus el gateway, recorda que aquella peça hi era.
La configuració s'aplica des de la canalització de 05-05:
# Validar abans d'aplicar (al pull request)
deck gateway validate gateway/kong.yaml
# Veure què canviaria (al pull request: el diff es comenta al PR)
deck gateway diff gateway/kong.yaml
# Aplicar (després de l'aprovació, al desplegament)
deck gateway sync gateway/kong.yamlAixò és infraestructura com a codi aplicada al gateway, i és el que evita el problema clàssic: algú toca la configuració a la interfície gràfica un dimarts, ningú no ho recorda, i a la reconstrucció següent de l'entorn la política desapareix.
- L'alternativa amb NGINX
Si l'equip ja opera NGINX i no necessita gestió de consumidors ni portal, bona part de l'anterior s'aconsegueix amb configuració directa:
# gateway/nginx.conf — versió mínima amb NGINX
# Zones de memòria compartida per als limitadors. 10 MB ≈ 160.000 IP.
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
upstream api_botiga_aroma {
server api-1.intern:3000 max_fails=3 fail_timeout=10s;
server api-2.intern:3000 max_fails=3 fail_timeout=10s;
keepalive 32; # connexions persistents: menys latència
}
server {
listen 443 ssl http2;
server_name api.botigaaroma.example;
ssl_certificate /etc/ssl/certs/botigaaroma.crt;
ssl_certificate_key /etc/ssl/private/botigaaroma.key;
ssl_protocols TLSv1.2 TLSv1.3; # res per sota de l'1.2 (04-02)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
server_tokens off; # no revelar la versió d'NGINX
client_max_body_size 100k; # el mateix límit que express.json (04-02)
# Traça de correlació: es genera si no ve, i es propaga (04-07)
set $traca_id $http_aroma_traca_id;
if ($traca_id = "") { set $traca_id $request_id; }
location /v1/sessions {
limit_req zone=login burst=3 nodelay;
limit_req_status 429;
proxy_pass http://api_botiga_aroma;
include /etc/nginx/proxy_comu.conf;
}
location /v1/ {
limit_req zone=general burst=20 nodelay;
limit_req_status 429;
gzip on;
gzip_types application/json;
gzip_min_length 1024;
proxy_pass http://api_botiga_aroma;
include /etc/nginx/proxy_comu.conf;
}
location /docs {
proxy_pass http://api_botiga_aroma;
include /etc/nginx/proxy_comu.conf;
}
}# /etc/nginx/proxy_comu.conf — capçaleres que SEMPRE cal propagar
proxy_http_version 1.1;
proxy_set_header Connection ""; # habilita keepalive
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Aroma-Traca-Id $traca_id;
proxy_read_timeout 10s;
proxy_connect_timeout 2s;La capçalera X-Forwarded-For connecta directament amb el trust proxy 1 de la posició 1 de src/app.js. Sense X-Forwarded-For, Express veu la IP del gateway a totes les peticions i el rate limiting per IP limita el gateway sencer. I amb trust proxy mal configurat —confiant en més salts dels que hi ha— un atacant pot falsificar la seva IP afegint la capçalera ell mateix. El número ha de coincidir exactament amb la quantitat de proxies de confiança que hi ha al davant.
El que NGINX no dona i Kong sí: gestió de consumidors amb credencials, quotes mensuals, validació de JWT sense recórrer a Lua, portal de desenvolupador i configuració per API en lloc de per fitxer. Per a una API amb socis i tercers, aquelles absències pesen.
- Quins middlewares podríem retirar i quins no
La pregunta pràctica: amb el gateway de dalt al davant, què queda a src/app.js?
Posició a src/app.js |
Middleware | Retirar? | Raó |
|---|---|---|---|
| 1 | disable('x-powered-by') + trust proxy |
No, i trust proxy és més necessari |
Sense ell, totes les IP són la del gateway |
| 2 | assignarTracaId |
No, adaptar | Ha de respectar l'Aroma-Traca-Id que arriba i generar-lo només si falta |
| 3 | capcaleresSeguretat (helmet) |
No | Defensa en profunditat; el cost és nul |
| 4 | cors(opcionsCors) |
Sí, amb condicions | El gateway ho fa i respon el preflight. Mantenir-lo si l'app també s'exposa sense gateway (desenvolupament local) |
| 5 | registrarPeticions (pino) |
No | Els logs del gateway no tenen context de negoci: usuari, comanda, consulta |
| 6 | metriquesMiddleware |
No | Les del gateway són de trànsit; les teves són de negoci (aroma_comandes_creades_total) |
| 7 | limitGlobal |
Es pot simplificar | Delegar el global; mantenir un límit de seguretat més laxe per si algú esquiva el gateway |
| 8 | compression |
Sí | El gateway comprimeix millor i allibera CPU de l'aplicació |
| 9 | express.json({limit}) |
No | El límit de cos s'aplica als dos: defensa en profunditat |
| 10-11 | /salut i /salut/preparat |
No | Els fa servir l'orquestrador (05-05), no el gateway |
| 12 | /metriques protegit |
No | Prometheus rasca la instància, no el gateway |
| 13 | etagCondicional |
No | L'ETag depèn del contingut: només l'app el sap generar |
| 14 | autenticar a les rutes |
No, mai | Zero trust: si algú esquiva el gateway, això és l'única cosa que queda |
| 14 | exigirRol / exigirAmbit |
No, mai | Autorització: mai no es delega |
| 14 | exigirClauIdempotencia |
No | Requereix estat de negoci (03-05) |
| 14 | validar(esquema) |
No | La validació semàntica és de l'aplicació |
| 15-16 | gestorNoTrobat, gestorErrors |
No | El format del catàleg (02-04) és teu |
Balanç honest: es retiren un o dos middlewares de setze. Aquest és el resultat real i convé dir-ho sense adorns, perquè contradiu la promesa comercial d'aquestes eines.
El valor del gateway no és aprimar la teva aplicació. És en tres coses diferents:
- Consistència entre diversos serveis. Amb cinc APIs, les polítiques s'escriuen una vegada en lloc de cinc.
- Gestió de consumidors. Quotes per pla, credencials, mTLS amb socis, llistes negres. Això no existia al nostre projecte i construir-ho seria costós.
- Canviar polítiques sense desplegar. Ajustar un límit o bloquejar un client abusiu és una línia de YAML i trenta segons, en lloc d'un desplegament complet.
I una última observació important: la configuració del gateway s'ha de provar igual que el codi. El recorregut de Newman de 05-01 s'ha d'executar contra el gateway, no contra l'API directament. Si no, un CORS mal configurat o un strip_path equivocat es descobreix en producció. Amb deck gateway diff al pull request, el canvi de política es revisa com qualsevol altre.
- Els riscos del gateway
1. Punt únic de fallada. Si el gateway cau, totes les teves APIs cauen alhora, encara que estiguin perfectament sanes. Mitigació: diverses instàncies, comprovacions de salut, i —el que gairebé ningú no fa— un pla documentat per exposar temporalment els serveis sense ell.
2. Coll d'ampolla i latència afegida. Cada petició travessa un salt de xarxa i un processament addicional: entre 1 i 10 ms típicament. Amb un pressupost de latència de 200 ms (04-06) és assumible; si el teu p99 objectiu són 20 ms, és un 25 % del pressupost i cal mesurar-ho, no suposar-ho.
3. Configuració duplicada i divergent. El cas més perniciós: CORS configurat al gateway i a l'aplicació amb llistes diferents. Un origen funciona en desenvolupament i falla en producció, o a l'inrevés, i depurar-ho és un infern perquè cada capa diu que està bé. Regla: una política, un amo, documentat en un ADR (04-01).
4. Lògica de negoci al gateway. El risc més car. Comença amb una transformació innocent i acaba amb regles de preus en un script Lua sense proves, sense control de versions efectiu i sense ningú que sàpiga que hi són. La regla de l'apartat 3 no admet excepcions.
5. Dependència del proveïdor. Les polítiques d'Apigee o d'Azure APIM no es migren a Kong. Com més lògica fiquis al gateway, més car és canviar-lo. Un altre argument per mantenir-lo prim.
6. Falsa sensació de seguretat. «El gateway valida els tokens, així que l'app pot confiar.» No. Zero trust: l'aplicació valida sempre.
7. Depuració més difícil. Un 403 pot venir del gateway o de l'aplicació, i de vegades no és evident quin. Mitigació: que el gateway marqui els seus propis errors —una capçalera pròpia, o un camp a detalls— i que l'Aroma-Traca-Id travessi totes dues capes, que és exactament per al que serveix el plugin correlation-id.
- Què és un portal de desenvolupador
Canviem de tema i d'audiència. Un portal de desenvolupador és el lloc web on qui vol integrar-se amb la teva API aprèn a fer-ho, es registra, obté credencials, prova i s'assabenta dels canvis.
La pregunta que respon: CataBox vol integrar-se amb la Botiga Aroma un dimarts al matí. Què passa?
| Sense portal | Amb portal |
|---|---|
| Cerca a Google i troba un PDF del 2024 | Troba developers.botigaaroma.example |
| Escriu a suport demanant la documentació | Llegeix la referència generada d'openapi.yaml |
| Espera dos dies que algú respongui | Es registra i crea una aplicació en cinc minuts |
| Demana credencials per correu | Obté client_id i un entorn de proves a l'instant |
| Algú crea el client OAuth a mà | Prova a la consola interactiva sense escriure codi |
Descobreix els límits quan li retornen 429 |
Els llegeix a la pàgina de plans |
| S'assabenta d'una deprecació quan alguna cosa es trenca | Està subscrit al changelog |
Qui el necessita: qualsevol API amb consumidors que no siguin al teu equip. Amb una API estrictament interna i dos equips, /docs amb Swagger UI i un canal de xat poden bastar. Tan bon punt hi ha tercers, socis o més de quatre o cinc equips interns, el portal deixa de ser un luxe.
- Què conté un bon portal
| Secció | Contingut | D'on surt |
|---|---|---|
| Inici | Què fa l'API, per a qui, un exemple en 30 segons | Escrit a mà |
| Guia d'inici | De zero a la primera crida amb èxit | Escrit a mà — la secció més important |
| Referència | Tots els endpoints, paràmetres, esquemes i errors | Generada d'openapi.yaml (05-02) |
| Autenticació | JWT davant d'OAuth, fluxos, àmbits, renovació | Escrit, amb els securitySchemes com a base |
| Guies temàtiques | Paginació, idempotència, webhooks, errors, reintents | Escrit a mà |
| Consola interactiva | «Prova-ho ara» contra l'entorn de proves | Swagger UI, Scalar o Stoplight Elements |
| Les meves aplicacions | Registre, credencials, àmbits, URL de retorn | Gateway o portal |
| Entorn de proves | Dades fictícies, credencials de prova, targetes de prova | Infraestructura |
| Límits i plans | Quotes, preus, com demanar-ne més | Escrit |
| Changelog | Què ha canviat i quan; avisos de deprecació | Del cicle de 02-07 |
| Estat del servei | Incidències i finestres de manteniment | Monitoratge (04-07) |
| Suport | Com demanar ajuda i quina informació aportar | Escrit |
| Termes d'ús | Què es pot fer amb les dades; RGPD | Legal |
Tres apartats solen faltar i són els que més s'agraeixen:
- Guies temàtiques, no només referència. La referència diu que
POST /v1/comandesacceptaIdempotency-Key. Una guia explica per què, què passa en reintentar, quant de temps es conserva la clau i com generar-la. La referència respon «què»; la guia respon «com i per què», i és el que evita les integracions mal fetes. - La pàgina d'errors. El catàleg complet de 02-04 amb què significa cada codi, si convé reintentar-ho i què fer. Amb
429iRetry-After, amb409d'idempotència, amb412d'If-Match. És la pàgina que més visites rep d'un portal madur, i la seva absència es tradueix directament en tiquets de suport. - Exemples executables.
curlcopiable, i la col·lecció de Postman de 05-01 amb un botó d'importar. Que algú pugui enganxar una ordre i veure una resposta real en trenta segons val més que deu pàgines de prosa.
I una guia d'inici que funciona té aquesta forma, sense adorns:
# Primers passos amb l'API de la Botiga Aroma
## 1. Crea el teu compte i la teva aplicació (2 minuts)
Registra't i crea una aplicació a "Les meves aplicacions".
Obtindràs un `client_id` i, si és una aplicació confidencial, un `client_secret`.
## 2. Obtén un token de prova (1 minut)
curl -X POST https://auth-proves.botigaaroma.example/oauth/token \
-d "grant_type=client_credentials" \
-d "client_id=EL_TEU_CLIENT_ID" \
-d "client_secret=EL_TEU_CLIENT_SECRET" \
-d "scope=cafes.llegir"
## 3. La teva primera crida (30 segons)
curl https://api-proves.botigaaroma.example/v1/cafes?limit=3 \
-H "Authorization: Bearer EL_TEU_TOKEN"
Hauries de veure tres cafès del catàleg de proves. **Ja està.**
## 4. Passos següents
- [Filtrar, ordenar i paginar](/guies/consultes)
- [Crear comandes amb idempotència](/guies/comandes)
- [Rebre webhooks signats](/guies/webhooks)
- [Gestionar errors i reintents](/guies/errors)Quatre passos, tres minuts, zero ambigüitat. Aquest és l'estàndard.
- Registre d'aplicacions i credencials OAuth
El flux pel qual un tercer es converteix en consumidor:
sequenceDiagram
participant D as Desenvolupador de CataBox
participant P as Portal
participant G as Gateway
participant A as Servidor d'autorització
D->>P: Es registra i crea l'aplicació CataBox
D->>P: Declara URL de retorn i àmbits sol·licitats
P->>A: Crea el client OAuth amb aquelles dades
A-->>P: client_id i client_secret si és confidencial
P->>G: Dona d'alta el consumidor amb el seu pla i quota
P-->>D: Mostra les credencials, el secret una sola vegada
D->>A: Sol·licita un token amb Client Credentials o PKCE
A-->>D: access_token amb els àmbits concedits
D->>G: GET /v1/cafes amb el token
G->>G: Valida signatura, àmbit i quota del consumidor
G-->>D: 200 amb el catàleg
Decisions de disseny d'aquest flux, totes amb conseqüències de seguretat (04-02 i 04-03):
- El
client_secretes mostra una sola vegada. Es desa com a hash, igual que una contrasenya. Si es perd, es rota; no es recupera. - Aplicacions públiques davant de confidencials. Una SPA o una app mòbil no poden desar un secret: són públiques i han de fer servir Authorization Code amb PKCE. El portal ha de preguntar el tipus i no oferir secret a les públiques.
- Àmbits mínims. Que CataBox demani
cafes.llegiriressenyes.escriure, no tots. El portal ha d'explicar què permet cada àmbit amb llenguatge clar, perquè aquell text és el que veurà el client final a la pantalla de consentiment. - Àmbits sensibles amb revisió manual.
ressenyes.moderaroenviaments.escriureno es concedeixen automàticament: es demanen i algú els aprova. - Aprovació separada per a producció. L'entorn de proves és immediat; l'accés a producció exigeix revisar l'aplicació. És el que evita que un desenvolupador curiós arribi a dades reals.
- Rotació de credencials sense tall: poder tenir dos secrets vàlids alhora durant la rotació. Sense això, rotar implica un tall, i la conseqüència és que ningú no rota mai.
- El temps fins a la primera crida amb èxit
Hi ha una mètrica que resumeix la qualitat de l'experiència d'integració: TTFHW (time to first hello world), el temps des que algú arriba al teu portal fins que rep la seva primera resposta 200.
Per què importa tant: els primers minuts, qui avalua la teva API decideix si continua o busca una altra cosa. Un TTFHW de trenta minuts amb tres correus a suport pel mig és una barrera comercial real, no un detall d'experiència d'usuari.
| Temps | Valoració | Què implica |
|---|---|---|
| < 5 min | Excel·lent | Registre automàtic, credencials immediates, exemple copiable |
| 5-15 min | Bo | Algun pas manual o documentació una mica dispersa |
| 15-60 min | Millorable | Documentació confusa o alta fricció al registre |
| > 1 dia | Dolent | Aprovació manual, credencials per correu, tiquets |
Com es mesura de debò: asseu-te amb algú que no conegui l'API, dona-li l'enllaç del portal i cronometra sense ajudar-lo. Anota cada punt on dubta, s'equivoca o s'atura. Aquells punts són la llista de tasques, ordenada per impacte. És una prova que costa una hora i produeix millor informació que qualsevol enquesta.
Els frens habituals, que es repeteixen a gairebé totes les APIs:
- El registre demana dades innecessàries al primer pas (raó social, telèfon, adreça fiscal).
- Les credencials requereixen aprovació humana fins i tot per a l'entorn de proves.
- L'exemple de la documentació no funciona copiat i enganxat (falta una capçalera, la URL està desactualitzada).
- L'entorn de proves no té dades: el primer
GETretorna una llista buida i sembla que alguna cosa falla. - Els errors no expliquen el problema: un
401genèric sense dir si el token no és vàlid, ha caducat o li falta l'àmbit.
El punt 4 és especialment traïdor. L'entorn de proves ha d'estar sembrat amb dades fictícies riques: el nostre npm run sembrar amb caf_001, caf_002, cli_842 i com_5001 compleix exactament aquella funció, i per això les primeres crides del portal retornen alguna cosa interessant en lloc de {"dades": [], "total": 0}.
- Cicle de vida i inventari d'APIs
A 04-02, el novè punt de l'OWASP API Top 10 era la gestió inadequada de l'inventari: APIs oblidades, versions antigues encara vives, endpoints de proves exposats. Aquí és on es resol.
El gateway és la font de veritat de l'inventari, perquè tot el que s'exposa hi passa. Si alguna cosa rep trànsit extern i no és a la configuració del gateway, és una API a l'ombra i hi ha un problema.
Cada API o versió té un cicle de vida explícit:
| Estat | Significat | Qui la pot fer servir | Suport |
|---|---|---|---|
| Disseny | Contracte en revisió; mock disponible (05-04) | Ningú, o proves internes | — |
| Beta | Funciona, pot canviar sense cicle de deprecació | Consumidors que accepten el risc | Sense garanties |
| Estable | En producció, amb garanties de compatibilitat | Tothom | Complet |
| Obsoleta | Funciona però es retirarà; Deprecation i Sunset (02-07) |
Els existents; sense altes noves | Correccions de seguretat |
| Retirada | Retorna 410 versio_api_retirada |
Ningú | Cap |
| Zombi | Ningú no sap que existeix i continua viva | Qualsevol | Cap |
L'última fila és el problema real. Les APIs zombis apareixen perquè ningú no té la llista completa, i sobreviuen perquè ningú no s'atreveix a apagar res per si de cas.
Les pràctiques que ho eviten, totes recolzades en peces que ja tenim:
- Inventari automàtic des de la configuració del gateway, versionada a Git.
- Mètriques d'ús per ruta, versió i consumidor (04-07 i el plugin
prometheusambper_consumer). Abans de retirar/v1la pregunta «qui la fa servir encara?» té una resposta amb noms i volums, no una suposició. - Un amo per API, amb nom i equip. Sense amo, no hi ha qui decideixi.
- Data de revisió. Cada API es revisa almenys una vegada l'any: continua fent falta?, està documentada?, té consumidors?
- Els entorns de no producció, tancats. Preproducció sense autenticació, accessible des d'internet, és un clàssic i apareix als informes de bretxes amb una freqüència depriment.
- La retirada, en dues fases. Primer l'apagada de prova: retornar
410durant unes hores en una data anunciada. Els consumidors que quedaven apareixen immediatament. Després, la retirada definitiva. És molt més eficaç que qualsevol correu d'avís.
- Monetització, com a nota
Quan l'API és en si mateixa un producte, el gateway i el portal aporten la infraestructura de cobrament. Els models habituals:
| Model | Com funciona | Exemple |
|---|---|---|
| Gratuït amb límit | Quota generosa, gratis | 1.000 crides al mes |
| Per nivells | Plans amb quotes i funcions creixents | Gratis / Pro / Empresa |
| Per consum | Es paga per crida o per unitat processada | 0,001 € per crida |
| Per funcions | Certs endpoints només en plans alts | Webhooks només a Empresa |
| Repartiment d'ingressos | El soci cobra una comissió per venda generada | CataBox per comanda referida |
Les peces tècniques són totes en aquesta lliçó: quotes al gateway per consumidor, mesura amb les mètriques per consumidor, plans al portal, i la facturació integrada amb la passarel·la.
Un avís sobre la mesura, que és on es cometen els errors cars: cal decidir explícitament si es cobren les crides fallides. Cobrar un 500 propi és indefensable; no cobrar un 429 pot incentivar l'abús. I la mesura ha de ser auditable: un client té dret a veure el seu consum desglossat i a quadrar-lo amb la seva factura.
Per a la Botiga Aroma, l'API no és el producte: és el canal. Un repartiment d'ingressos amb CataBox per comandes referides tindria més sentit que cobrar per crida.
Errors Comuns i Consells
- Posar un gateway tenint una sola API. Hi afegeixes un punt de fallada, latència i una eina més per operar, a canvi de gairebé res. El gateway es justifica amb diversos serveis o amb tercers per gestionar.
- Treure l'autenticació de l'aplicació perquè el gateway la fa. Si algú arriba al servei esquivant el gateway, la teva API està completament oberta. Zero trust, sempre.
- Delegar l'autorització a nivell de recurs. El gateway no pot saber que
com_5001és decli_842. És la fallada número u de l'OWASP API Top 10 i no té solució fora de l'aplicació. - Ficar lògica de negoci al gateway. Regles de domini en un script Lua sense proves, sense tipus i sense revisió. És l'error més car de revertir.
- Configurar CORS al gateway i a l'aplicació amb llistes diferents. Un origen funciona en un entorn i falla en un altre, i depurar-ho porta hores perquè cada capa sembla correcta. Una política, un amo.
- Oblidar
exposed_headersal gateway. El navegador repETagiLinkperò JavaScript no els pot llegir. És l'error de CORS més frustrant de 04-05. trust proxymal configurat. Amb menys salts dels que hi ha, la IP real es perd; amb més, un atacant la pot falsificar. El número ha de coincidir exactament.- Configurar el gateway per interfície gràfica. Ningú no recorda què s'ha canviat ni per què, i es perd a la reconstrucció següent. Configuració declarativa versionada i aplicada des de la canalització.
- No provar contra el gateway. El recorregut de Newman de 05-01 ha d'apuntar al gateway. Un
strip_pathequivocat o un CORS mal posat es descobreixen en producció si no. - Un portal que només és Swagger UI. La referència sense guia d'inici, sense pàgina d'errors i sense exemples executables deixa qui s'integra buscant on començar.
- Aprovació manual per a l'entorn de proves. Multiplica el TTFHW per cent i fa que la gent provi l'API de la competència mentre espera.
- Un entorn de proves buit. La primera crida retorna
{"dades": [], "total": 0}i sembla trencada. Sembra'l amb dades fictícies riques. - Consell: mesura el TTFHW amb una persona real i un cronòmetre. Una hora d'observació produeix millor informació que qualsevol enquesta.
- Consell: abans de retirar una versió, fes una apagada de prova. Unes hores de
410en una data anunciada descobreixen tots els consumidors que quedaven, cosa que cap correu d'avís no aconsegueix. - Consell: documenta en un ADR (04-01) quina política viu al gateway i quina a l'aplicació. És la informació que falta el dia de l'incident.
Exercicis
Exercici 1: repartir responsabilitats
La Botiga Aroma posarà Kong davant de l'API. Per a cada requisit, decideix si s'implementa al gateway, a l'aplicació o a tots dos, i justifica-ho:
- Rebutjar peticions sense token vàlid.
- Comprovar que un client només veu les seves pròpies comandes.
- Limitar CataBox a 10.000 crides al mes.
- Rebutjar
preuMinmés gran quepreuMax. - Bloquejar un rang d'IP que està atacant l'inici de sessió.
- Retornar
409 comanda_ja_pagadasi la comanda ja s'ha pagat. - Permetre peticions des de
https://panel.botigaaroma.examplei de cap altre origen. - Exigir la capçalera
Idempotency-KeyaPOST /v1/comandes. - Rebutjar cossos més grans de 100 KB.
- Retornar
304quan l'ETagcoincideix ambIf-None-Match.
Exercici 2: la ruta de /v2 amb convivència
La Botiga Aroma publica /v2, que viu en un servei separat (api-v2.intern:3000), mentre /v1 continua al servei actual amb sis mesos de convivència. Escriu la configuració declarativa de Kong necessària: els serveis i rutes, com s'apliquen els mateixos plugins de rate limiting i CORS a totes dues versions sense duplicar configuració, i com s'afegeixen les capçaleres Deprecation, Sunset i Link amb la successora només a les respostes de /v1. Indica també què passa el dia del Sunset.
Exercici 3: auditar un portal de desenvolupador
Un portal de la competència té: pàgina d'inici amb la proposta de valor, referència completa generada d'OpenAPI amb consola «prova-ho ara», formulari de contacte per sol·licitar accés —resposta en 2-3 dies laborables—, PDF amb els plans i preus, i una pàgina d'estat del servei.
Identifica almenys cinc mancances greus, estima el TTFHW resultant justificant l'estimació, i proposa un pla prioritzat de millora amb les tres primeres accions, indicant quina mètrica esperaries moure amb cadascuna.
Solucions
Solució 1
| # | Requisit | On | Justificació |
|---|---|---|---|
| 1 | Rebutjar peticions sense token vàlid | Tots dos | El gateway rebutja aviat i estalvia trànsit a l'aplicació. L'aplicació ho repeteix perquè si algú hi arriba esquivant el gateway —regla de xarxa mal posada, desplegament nou, atacant intern— seria l'única defensa. És l'exemple canònic de defensa en profunditat. |
| 2 | Un client només veu les seves comandes | Només l'aplicació | El gateway no pot consultar la base de dades per saber que com_5001 és de cli_842. Delegar-ho és impossible, i intentar-ho amb regles al gateway produiria una autorització incompleta i perillosa (BOLA, 04-02). |
| 3 | 10.000 crides al mes per a CataBox | Només el gateway | És gestió de consumidors: quota per pla i per finestra de temps. L'aplicació no té ni ha de tenir el concepte de «pla de CataBox». A més, canviar la quota no hauria d'exigir un desplegament. |
| 4 | preuMin > preuMax |
Només l'aplicació | És validació semàntica: la relació entre dos camps. El gateway pot comprovar que tots dos són números positius amb el JSON Schema; la relació entre ells és lògica de domini i el seu error (parametre_invalid amb detalls) pertany al teu catàleg. |
| 5 | Bloquejar un rang d'IP atacant | Gateway (i CDN/WAF, encara millor) | Com abans es talli el trànsit maliciós, menys recursos consumeix. L'ideal és el WAF de la CDN, fins i tot abans del gateway. L'aplicació no hauria de veure mai aquell trànsit. |
| 6 | 409 comanda_ja_pagada |
Només l'aplicació | Regla de negoci pura, dependent de l'estat persistit. Ni tan sols és temptador delegar-la. |
| 7 | CORS només des del panell | Gateway, i a l'aplicació amb matisos | El gateway és el lloc natural i respon el preflight. Convé mantenir cors() a l'aplicació amb la mateixa llista per al desenvolupament local sense gateway, però documentant en un ADR que la font de veritat és el gateway per evitar la divergència de l'apartat 11. |
| 8 | Idempotency-Key obligatòria |
Tots dos, amb repartiment | El gateway pot rebutjar la petició si la capçalera falta (428), cosa que és barata i primerenca. Però la lògica real —desar la clau, detectar el reintent, retornar la resposta original, detectar la reutilització amb un cos diferent (409)— requereix estat de negoci i es queda a l'aplicació. |
| 9 | Cossos més grans de 100 KB | Tots dos | El gateway ho talla abans que el cos arribi al teu procés, que és l'eficient. L'aplicació manté express.json({limit: '100kb'}) com a xarxa de seguretat, amb el seu error cos_massa_gran. |
| 10 | 304 amb If-None-Match |
Només l'aplicació | L'ETag es calcula a partir del contingut del recurs i de la seva versió: només l'aplicació el pot generar. Un gateway amb memòria cau pot servir respostes ja desades, però la validació condicional que tanca el cercle amb If-Match i el 412 de la concurrència optimista (04-06) és de l'aplicació. |
Patró que emergeix: tot el que depèn del contingut o de l'estat persistit es queda a l'aplicació; tot el que depèn de qui crida i quant crida va al gateway; i el que és barat de comprovar dues vegades es fa a tots dos llocs.
Solució 2
# gateway/kong.yaml — convivència de /v1 i /v2
_format_version: "3.0"
services:
# ---- Servei v1: l'actual, en fase de deprecació -------------------------
- name: api-v1
url: http://api-botiga-aroma.intern:3000
tags: ["api-publica", "obsoleta"]
routes:
- name: ruta-v1
paths: ["/v1"]
strip_path: false
protocols: ["https"]
plugins:
# Capçaleres de deprecació NOMÉS a v1 (02-07).
# Deprecation: data en què va quedar obsoleta, com a marca Unix (RFC 9745).
# Sunset: data de retirada, en format HTTP (RFC 8594).
# Link amb rel="successor-version": l'alternativa, descobrible.
- name: response-transformer
config:
add:
headers:
- "Deprecation: @1767225600"
- "Sunset: Wed, 30 Jun 2027 23:59:59 GMT"
- 'Link: <https://api.botigaaroma.example/v2>; rel="successor-version"'
- 'Warning: 299 - "La versió v1 es retirarà el 2027-06-30. Migra a /v2: https://developers.botigaaroma.example/migracio/v2"'
# ---- Servei v2: el nou, en un altre desplegament ------------------------
- name: api-v2
url: http://api-v2.intern:3000
tags: ["api-publica", "estable"]
routes:
- name: ruta-v2
paths: ["/v2"]
strip_path: false
protocols: ["https"]
# ---------------------------------------------------------------------------
# PLUGINS GLOBALS: s'apliquen a TOTES DUES versions sense duplicar configuració.
# Aquesta és la resposta a "com no duplicar": els plugins sense `service` ni
# `route` són globals; només es declaren per ruta els que difereixen.
# ---------------------------------------------------------------------------
plugins:
- name: rate-limiting
config:
minute: 600
hour: 20000
policy: redis
redis: { host: redis.intern, port: 6379, database: 1 }
limit_by: consumer
fault_tolerant: true
# La quota és COMPARTIDA entre v1 i v2 a propòsit: un client que migra
# progressivament no ha de rebre el doble de quota per fer servir totes dues.
- name: cors
config:
origins:
- https://botigaaroma.example
- https://panel.botigaaroma.example
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
headers: [Authorization, Content-Type, Idempotency-Key, If-Match, If-None-Match]
exposed_headers:
- ETag
- Link
- Location
- Retry-After
- Deprecation # imprescindible: sense això la SPA no veu l'avís
- Sunset
- Aroma-Traca-Id
- Aroma-RateLimit-Restants
credentials: true
max_age: 3600
- name: jwt-signer
config:
access_token_issuer: https://auth.botigaaroma.example
access_token_jwks_uri: https://auth.botigaaroma.example/.well-known/jwks.json
verify_access_token_signature: true
verify_access_token_expiry: true
- name: correlation-id
config:
header_name: Aroma-Traca-Id
generator: uuid
echo_downstream: true
- name: prometheus
config:
per_consumer: true
status_code_metrics: true
latency_metrics: trueCom s'evita la duplicació: els plugins declarats al nivell superior (sense service ni route) són globals i s'apliquen a totes les rutes. Només es declara per servei o per ruta el que difereix, que aquí és únicament el response-transformer amb les capçaleres de deprecació. Canviar el límit global és una línia que afecta totes dues versions.
Detall que sempre s'oblida: Deprecation i Sunset han de ser a exposed_headers del plugin de CORS. Si no, el navegador les rep però la SPA no les pot llegir, i la instrumentació que volies —que el front avisi a la consola que fa servir una versió obsoleta— no funciona.
Què passa el dia del Sunset (30 de juny de 2027):
# Fase 1 — Apagada de prova: unes hores, en una data anunciada amb antelació.
# És el que descobreix els consumidors que quedaven, cosa que els correus no aconsegueixen.
- name: request-termination
route: ruta-v1
config:
status_code: 410
content_type: "application/json"
body: '{"error":{"codi":"versio_api_retirada","missatge":"La versió v1 es va retirar el 2027-06-30. Fes servir /v2: https://developers.botigaaroma.example/migracio/v2","detalls":[]}}'Seqüència recomanada:
- Mesos abans: capçaleres
DeprecationiSunsetactives (ja ho són), avisos al changelog del portal, i correus als consumidors identificats per les mètriques del gateway, que diuen exactament qui continua cridant/v1i amb quin volum. - Un mes abans: apagada de prova de dues hores, anunciada. Apareixen els resseguidors.
- El dia del
Sunset: s'activa elrequest-terminationamb410.410 Gonei no404, perquè410significa «va existir i es va retirar deliberadament», que és informació útil per a qui depura. - Setmanes després: s'elimina la ruta de la configuració i s'apaga el servei
api-v1. El410deixa pas al404genèric.
Es manté el 410 explicatiu durant setmanes en lloc d'esborrar la ruta immediatament perquè un 404 sec deixa el consumidor sense saber què ha passat, mentre que el 410 amb enllaç a la guia de migració és autoexplicatiu.
Solució 3
Cinc mancances greus:
- Accés amb aprovació manual de 2-3 dies. És la mancança més greu amb diferència. Converteix una avaluació de cinc minuts en un projecte d'una setmana, i qui avalua alternatives provarà la de la competència mentre espera. Molts no tornen mai.
- No hi ha entorn de proves amb credencials immediates. Conseqüència de l'anterior: no es pot tocar res sense permís, així que la consola «prova-ho ara» és decorativa. Ningú no pot avaluar l'API sense comprometre's abans.
- Només hi ha referència, no guies. La referència diu quins camps accepta
POST /comandes; no explica la idempotència, la paginació, com reintentar davant d'un429o com verificar la signatura d'un webhook. Sense això, totes les integracions es fan malament de la mateixa manera i el cost es trasllada a suport. - Preus en un PDF. Un PDF no es pot enllaçar a una secció concreta, es desactualitza sense que ningú se n'adoni, no és accessible i transmet que la informació és estàtica. A més, si els plans són en PDF, les quotes tècniques probablement no estiguin documentades enlloc.
- No hi ha changelog ni política de deprecació. És la mancança que més por fa a qui construirà un negoci a sobre: no hi ha manera de saber si l'API canviarà ni amb quant d'avís. Sense un compromís públic de compatibilitat, integrar-s'hi és assumir un risc indefinit.
Mancances addicionals: no hi ha pàgina d'errors amb el catàleg de codis; no es veu una col·lecció de Postman ni exemples executables; res no indica els límits d'ús tècnics; i no hi ha clients generats ni SDK (05-02).
Estimació del TTFHW: 2-3 dies laborables. El desglossament justifica la xifra: descobriment i lectura, 10 minuts; omplir el formulari, 5 minuts; espera d'aprovació, 2-3 dies; configurar l'autenticació amb documentació incompleta, 30-60 minuts; primera crida amb èxit, 10 minuts. El temps actiu són uns 90 minuts; el temps transcorregut, tres dies. I el que compta comercialment és el transcorregut, perquè és el que determina si la persona continua interessada.
Pla prioritzat — tres primeres accions:
Acció 1 (impacte altíssim, cost mitjà): registre automàtic per a l'entorn de proves.
Qualsevol es registra amb un correu, crea una aplicació i obté client_id i client_secret de proves a l'instant. L'aprovació manual es conserva només per a producció, que és on de debò cal. El sandbox ha d'estar sembrat amb dades fictícies riques perquè la primera crida retorni alguna cosa interessant.
Mètrica esperada: TTFHW de 2-3 dies a menys de 15 minuts. És un canvi d'ordre de magnitud i, per si sol, justifica el projecte.
Acció 2 (impacte alt, cost baix): guia d'inici de quatre passos i pàgina d'errors.
Una pàgina amb registre → token → primera crida → passos següents, amb ordres curl copiables i provades a CI perquè no es desactualitzin mai (05-04 ja ens va ensenyar a provar que els exemples del contracte funcionen). I una pàgina amb el catàleg complet d'errors: què significa cada codi, si convé reintentar-ho i què fer.
Mètrica esperada: reducció del temps actiu de 90 a 20 minuts, i caiguda dels tiquets de suport de primer nivell, que solen ser el 60-70 % del total i gairebé sempre són «no entenc aquest error».
Acció 3 (impacte mitjà-alt, cost baix): changelog públic i compromís de compatibilitat. Una pàgina amb l'historial de canvis, un compromís explícit —«no eliminem camps sense sis mesos d'avís; els canvis trencadors van en una versió major de la ruta»— i subscripció per correu o RSS. És la peça que converteix una API que es prova en una API sobre la qual algú s'atreveix a construir un producte. Mètrica esperada: conversió de comptes de prova a integracions en producció. I, a mitjà termini, menys incidències a cada desplegament, perquè els consumidors s'assabenten dels canvis abans de patir-los.
Després: publicar els preus en HTML, oferir la col·lecció de Postman amb un botó d'importar, generar SDK amb OpenAPI Generator (05-02) i documentar els límits tècnics al costat dels plans.
Conclusió
Has vist la capa que envolta l'API per fora. Un API gateway centralitza les preocupacions transversals del trànsit —rate limiting, terminació TLS, validació de JWT i OAuth per JWKS, CORS, compressió, encaminament per versió, quotes per consumidor, mètriques, mTLS amb socis, llistes negres— i les converteix en configuració declarativa versionable en lloc de codi repetit a cada servei. Tens la seva configuració real per a la Botiga Aroma a gateway/kong.yaml, amb els mateixos límits de 04-04 i el seu fault_tolerant decidit ruta a ruta, la llista blanca de 04-05 amb els exposed_headers que gairebé tothom oblida, el correlation-id que propaga l'Aroma-Traca-Id de 04-07 i el plugin de Prometheus segmentat per consumidor; i l'alternativa equivalent a gateway/nginx.conf, amb l'X-Forwarded-For que dona sentit al trust proxy 1 de la posició 1 de src/app.js.
I tens el criteri, que val més que la configuració: el gateway sap qui crida i on; només l'aplicació sap què hi ha a dins i què significa. Per això el rate limiting, el TLS, la validació criptogràfica del token i el CORS es deleguen bé, mentre que l'autorització a nivell de recurs, la lògica de negoci i la validació semàntica no es deleguen mai —i per això, amb el gateway al davant, dels setze middlewares de src/app.js només se'n retiren un o dos—. El valor no és aprimar la teva aplicació, sinó la consistència entre diversos serveis, la gestió de consumidors que abans no existia i poder canviar polítiques sense desplegar. Tot això assumint-ne els riscos amb els ulls oberts: punt únic de fallada, latència afegida, configuració divergent i la temptació sempre present de ficar regles de negoci on no hi ha proves ni revisió.
La segona meitat de la lliçó era l'altra cara: un portal de desenvolupador amb guia d'inici de quatre passos, referència generada de l'openapi.yaml de 05-02, consola interactiva, registre d'aplicacions amb credencials OAuth immediates per a l'entorn de proves, pàgina d'errors amb el catàleg de 02-04, changelog amb els avisos de deprecació de 02-07, límits, estat del servei i suport. Amb una mètrica que ho resumeix tot, el temps fins a la primera crida amb èxit, que es mesura amb una persona real i un cronòmetre i que decideix si la teva API s'adopta o s'abandona la primera hora. I amb la gestió del cicle de vida i de l'inventari, on el gateway es converteix en la font de veritat de què està exposat i les mètriques per consumidor responen amb noms i volums a la pregunta «qui continua fent servir /v1?» abans de retirar-la —amb una apagada de prova d'unes hores, que descobreix resseguidors com no ho aconsegueix cap correu—.
Amb això es tanca el mòdul 5. L'API de la Botiga Aroma ja no està sola: té una col·lecció de Postman executable a CI, un contracte OpenAPI complet que serveix de documentació, de mock, de validador i de generador de clients, la perspectiva per saber què hauria canviat amb un altre framework i què no, proves de contracte que impedeixen que el codi i el contracte se separin juntament amb una porta que detecta canvis trencadors, una canalització que construeix, prova i desplega sense talls amb migracions retrocompatibles i retorn enrere, i una capa de gateway i portal que la protegeix i l'explica. És una API operable i consumible, no només correcta.
El que falta ja no és cap peça solta: és veure tot això junt, aplicat de principi a fi i sostingut en el temps. Al mòdul 6, Casos d'Estudi i Projectes, deixem les eines i tornem al disseny amb tot el que hem après al damunt: un cas d'estudi complet de l'API d'una botiga en línia, recorrent les decisions des dels recursos fins al desplegament (06-01); un segon cas, el d'una xarxa social, on els problemes són diferents —grafs de relacions, línies de temps, paginació per cursor a gran escala, contingut generat per usuaris i moderació— i obliguen a replantejar diverses de les decisions que aquí vam donar per bones (06-02); l'evolució i el manteniment d'una API en producció, és a dir, què passa els tres anys següents al llançament: deute de contracte, migracions de versió, incidents reals i retirada de funcionalitats (06-03); i el projecte final, en què dissenyaràs i desenvoluparàs la teva pròpia API RESTful aplicant els sis mòduls complets (06-04).
Curs de REST API: Principis de Disseny i Desenvolupament d'APIs RESTful
Mòdul 1: Introducció a les APIs RESTful
- Què és una API?
- Història i evolució de les APIs
- Fonaments d'HTTP per a APIs
- Principis bàsics de REST
- Model de maduresa de Richardson i HATEOAS
- REST vs. SOAP
- REST davant de GraphQL, gRPC i webhooks
Mòdul 2: Disseny d'APIs RESTful
- Principis de disseny d'APIs RESTful
- Recursos i URIs
- Mètodes HTTP
- Codis d'estat HTTP
- Representacions, capçaleres i negociació de contingut
- Filtratge, ordenació, paginació i cerca
- Versionat d'APIs
- Documentació d'APIs
Mòdul 3: Desenvolupament d'APIs RESTful
- Configuració de l'entorn de desenvolupament
- Creació d'un servidor bàsic
- Gestió de peticions i respostes
- Validació de dades d'entrada
- Persistència i capa d'accés a dades
- Autenticació i autorització
- Gestió d'errors
- Proves i validació
Mòdul 4: Bones Pràctiques i Seguretat
- Bones pràctiques en el disseny d'APIs
- Seguretat en APIs RESTful
- OAuth 2.0 i OpenID Connect a la pràctica
- Rate limiting i throttling
- CORS i polítiques de seguretat
- Memòria cau HTTP i rendiment
- Observabilitat: logs, mètriques i traces
Mòdul 5: Eines i Frameworks
- Postman per a proves d'APIs
- Swagger i OpenAPI per a documentació
- Frameworks populars per a APIs RESTful
- Contractes, mocks i proves automatitzades d'API
- Integració contínua i desplegament
- API gateways i portals de desenvolupador
