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

  1. Què és un API gateway i quin problema resol
  2. Les funcions que assumeix, comparades amb el mòdul 4
  3. Què delegar i què no delegar mai
  4. L'arquitectura completa: CDN, gateway, serveis
  5. El patró backend for frontend
  6. El service mesh és una altra cosa
  7. Els productes i com es comparen
  8. Configuració de Kong per a la Botiga Aroma
  9. L'alternativa amb NGINX
  10. Quins middlewares podríem retirar i quins no
  11. Els riscos del gateway
  12. Què és un portal de desenvolupador
  13. Què conté un bon portal
  14. Registre d'aplicacions i credencials OAuth
  15. El temps fins a la primera crida amb èxit
  16. Cicle de vida i inventari d'APIs
  17. Monetització, com a nota
  18. Balanç del mòdul 5

  1. 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.

  1. 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
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
CORS cors(opcionsCors) (04-05) Llista blanca declarativa, preflight
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
Encaminament i versionat app.use('/v1', rutesV1) Ruta a servei; /v1 i /v2 a destins diferents
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
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
Llistes negres No les fem Bloqueig per IP, país, patró o reputació
Reintents i circuit breaker Parcial (04-04) Reintents, timeouts i curtcircuit per destí
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.

  1. 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ó

  1. 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.

  1. 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.

  1. 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.

  1. 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 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.

  1. 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: STRICT

Quatre punts d'aquella configuració que mereixen comentari:

  • fault_tolerant diferent segons la ruta. Al límit global és true: si Redis cau, preferim deixar passar trànsit a tirar l'API sencera. A l'inici de sessió és false: 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: consumer davant de limit_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 rep ETag però JavaScript no el pot llegir si no és a Access-Control-Expose-Headers. Aquí queda declarat d'una vegada per a tota l'API.
  • preflight_continue: false. El gateway respon els OPTIONS i 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.yaml

Això é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.

  1. 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.

  1. 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 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:

  1. Consistència entre diversos serveis. Amb cinc APIs, les polítiques s'escriuen una vegada en lloc de cinc.
  2. Gestió de consumidors. Quotes per pla, credencials, mTLS amb socis, llistes negres. Això no existia al nostre projecte i construir-ho seria costós.
  3. 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.

  1. 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.

  1. 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.

  1. 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/comandes accepta Idempotency-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 429 i Retry-After, amb 409 d'idempotència, amb 412 d'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. curl copiable, 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.

  1. 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_secret es 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.llegir i ressenyes.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.moderar o enviaments.escriure no 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.

  1. 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:

  1. El registre demana dades innecessàries al primer pas (raó social, telèfon, adreça fiscal).
  2. Les credencials requereixen aprovació humana fins i tot per a l'entorn de proves.
  3. L'exemple de la documentació no funciona copiat i enganxat (falta una capçalera, la URL està desactualitzada).
  4. L'entorn de proves no té dades: el primer GET retorna una llista buida i sembla que alguna cosa falla.
  5. Els errors no expliquen el problema: un 401 genè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}.

  1. 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 prometheus amb per_consumer). Abans de retirar /v1 la 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 410 durant 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.

  1. 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 de cli_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_headers al gateway. El navegador rep ETag i Link però JavaScript no els pot llegir. És l'error de CORS més frustrant de 04-05.
  • trust proxy mal 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_path equivocat 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 410 en 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:

  1. Rebutjar peticions sense token vàlid.
  2. Comprovar que un client només veu les seves pròpies comandes.
  3. Limitar CataBox a 10.000 crides al mes.
  4. Rebutjar preuMin més gran que preuMax.
  5. Bloquejar un rang d'IP que està atacant l'inici de sessió.
  6. Retornar 409 comanda_ja_pagada si la comanda ja s'ha pagat.
  7. Permetre peticions des de https://panel.botigaaroma.example i de cap altre origen.
  8. Exigir la capçalera Idempotency-Key a POST /v1/comandes.
  9. Rebutjar cossos més grans de 100 KB.
  10. Retornar 304 quan l'ETag coincideix amb If-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: true

Com 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:

  1. Mesos abans: capçaleres Deprecation i Sunset actives (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 /v1 i amb quin volum.
  2. Un mes abans: apagada de prova de dues hores, anunciada. Apareixen els resseguidors.
  3. El dia del Sunset: s'activa el request-termination amb 410. 410 Gone i no 404, perquè 410 significa «va existir i es va retirar deliberadament», que és informació útil per a qui depura.
  4. Setmanes després: s'elimina la ruta de la configuració i s'apaga el servei api-v1. El 410 deixa pas al 404 genè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:

  1. 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.
  2. 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.
  3. 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'un 429 o 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.
  4. 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.
  5. 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

Mòdul 2: Disseny d'APIs RESTful

Mòdul 3: Desenvolupament d'APIs RESTful

Mòdul 4: Bones Pràctiques i Seguretat

Mòdul 5: Eines i Frameworks

Mòdul 6: Casos d'Estudi i Projectes

© Copyright 2026. Tots els drets reservats