L'API de la Botiga Aroma funciona. Té contracte, capes, validació, persistència, autenticació, errors unificats i proves que ho protegeixen tot. I tanmateix, si demà l'entregues a un equip extern, passaran coses: preguntaran per què limit accepta 100 però expandir no té sostre; descobriran que la pantalla de "les meves comandes" de l'app mòbil necessita cinc crides; algú enviarà "rol": "administrador" en el registre per veure què passa; i un altre es queixarà que dades_invalides li diu que alguna cosa falla però no què ha d'escriure per arreglar-ho. Cap d'aquests problemes no és un bug. Tots són decisions de disseny, i cap prova automatitzada no els detecta.

Aquesta lliçó és diferent de les anteriors: no afegeix codi nou al projecte, afegeix criteri. A 02-01 vam veure els principis de disseny abans de construir res; ara que ja saps construir, els revisitem des de l'altra banda, amb l'experiència d'haver implementat cada peça. En acabar tindràs una llista de revisió aplicable a qualsevol API, un catàleg d'antipatrons per reconèixer-los en la feina real, i una estratègia per quan descobreixis —perquè passarà— que ja t'has equivocat.

Contingut

  1. Correcta enfront d'excel·lent
  2. La consistència com a valor suprem
  3. Com es garanteix la consistència a la pràctica
  4. Linting de l'especificació amb Spectral
  5. Dissenyar per al consumidor: la pantalla "les meves comandes"
  6. Massa fina, massa gruixuda: la granularitat
  7. Previsibilitat i el principi de mínima sorpresa
  8. Valors per defecte assenyats i segurs
  9. El principi de robustesa i els seus límits
  10. Idempotència i reintents com a contracte explícit
  11. Errors accionables
  12. Compatibilitat cap endavant per disseny
  13. Salut, metadades i arrel descobrible
  14. Paginació obligatòria i límits per defecte
  15. Zones horàries, unitats i localització
  16. Antipatrons que cal evitar
  17. La llista de revisió de disseny de la Botiga Aroma
  18. Deute de disseny: què fer quan ja t'has equivocat

  1. Correcta enfront d'excel·lent

Una API correcta compleix la seva especificació: els codis són els que diu el contracte, les dades que entren surten bé, els errors no filtren res. És el que has construït al mòdul 3 i és una condició necessària.

Una API excel·lent afegeix alguna cosa que no apareix en cap especificació: el cost d'utilitzar-la és baix. Es mesura en una unitat incòmoda de quantificar però fàcil de reconèixer: quant triga un desenvolupador que no la coneix a integrar el seu primer cas d'ús complet, i quantes vegades ha d'obrir la documentació després de la primera setmana.

Dimensió API correcta API excel·lent
Correcció Fa el que diu Fa el que diu
Aprenentatge S'aprèn llegint la documentació sencera S'endevina; la documentació ho confirma
Consistència Cada endpoint és correcte per separat Tots segueixen les mateixes regles
Errors Indiquen que alguna cosa ha fallat Indiquen què cal fer a continuació
Casos d'ús Cada recurs és accessible Els fluxos reals necessiten poques crides
Evolució Canviar trenca clients Canviar és rutina
Defectes Es detecten en producció Es detecten en la revisió de disseny

La diferència pràctica és econòmica. Una API interna que consumeixen tres equips, amb 40 desenvolupadors integrant-s'hi al llarg de dos anys, multiplica cada petita fricció per centenars d'hores. Una decisió de nomenclatura que es pren en cinc minuts es paga durant anys.

  1. La consistència com a valor suprem

Si haguessis de triar una sola propietat de disseny i sacrificar totes les altres, tria la consistència. La raó és cognitiva: un consumidor aprèn una API construint un model mental, i aquest model és una màquina d'extrapolar. Si GET /v1/cafes?limit=20 funciona, assumeix que GET /v1/comandes?limit=20 funciona. Si aquesta extrapolació encerta el 100 % de les vegades, deixa de llegir la documentació i va ràpid. Si encerta el 90 %, no se'n pot refiar de cap: ha de verificar les deu, i va més lent que si l'API fos uniformement mediocre.

Una API consistentment imperfecta és més usable que una API inconsistentment perfecta. És contraintuïtiu i és cert. Si la Botiga Aroma hagués decidit snake_case en el JSON, seria una decisió pitjor que camelCase per a consumidors JavaScript, però aplicada als 24 recursos costaria exactament un paràgraf de documentació. Barrejar els dos convenis costa una consulta a la documentació per cada camp, per sempre.

Les dimensions on la consistència es trenca amb més facilitat, per ordre de freqüència real:

Dimensió Regla de la Botiga Aroma Símptoma de ruptura
Nomenclatura de camps camelCase sempre preuEuros al costat de data_creacio
Noms de recursos Plural, substantiu, minúscula /cafes al costat de /obtenirComanda
Format de col·lecció {"dades": [...], "total": n} Un endpoint que retorna un array pelat
Format d'error {"error": {codi, missatge, detalls}} Un {"missatge": "..."} solt
Codis d'estat Arbre de decisió de 02-04 Un 200 on hi hauria d'haver un 201
Paginació limit/desplacament, cursor a /comandes page/per_page en un recurs nou
Dates ISO-8601 UTC amb Z Un 1734567890 epoch en un camp
Diners Euros amb dos decimals cap enfora Un camp en cèntims que s'escapa
Identificadors string amb prefix caf_, com_ Un enter nu en un recurs nou
Capçaleres pròpies Prefix Aroma- Un X-Total-Count heretat d'un exemple

Fixa't en el patró: gairebé totes les ruptures passen en afegir alguna cosa nova, mesos després, quan qui ho afegeix no va participar en les decisions originals i copia l'estil d'un exemple d'Internet. La consistència no és un acte de disseny, és un procés de manteniment.

  1. Com es garanteix la consistència a la pràctica

Tres mecanismes, de menys a més automàtic. Tots tres són necessaris; cap no substitueix els altres.

La guia d'estil viva

A 02-01 vam escriure la guia d'estil de la Botiga Aroma. L'adjectiu important és viva: un document que s'escriu una vegada i s'arxiva no serveix de res. Una guia viva té tres propietats:

  • Viu al repositori, no en un wiki corporatiu. Es versiona amb el codi, es revisa per pull request i es pot enllaçar a un commit concret.
  • Cada regla és normativa i comprovable. "Fes servir noms clars" no és una regla, és un desig. "Els noms de recurs són substantius en plural, en minúscules, sense guions baixos" sí que ho és: dues persones l'apliquen igual.
  • Registra les decisions amb el seu motiu. Quan d'aquí a un any algú pregunti per què els diners viatgen en euros amb dos decimals i no en cèntims, la resposta ha d'estar escrita. Si no, la decisió es reverteix per desconeixement.

Un format pràctic és l'ADR (Architecture Decision Record): un fitxer curt per decisió, amb context, decisió i conseqüències.

<!-- docs/decisions/0007-diners-en-euros-amb-dos-decimals.md -->
# 0007. Els diners viatgen en euros amb dos decimals

- Estat: acceptat
- Data: 2026-03-14

## Context
Internament emmagatzemem `preu_centims` com a enter per evitar els errors
de coma flotant. Cap enfora hi havia dues opcions: exposar cèntims enters
(`1450`) o euros amb dos decimals (`14.50`).

## Decisió
S'exposa `preuEuros: 14.50`. La conversió viu a `src/serveis/mapejadors.js`
i enlloc més.

## Conseqüències
- (+) La SPA i l'app mòbil mostren el valor sense conversió ni risc de dividir malament.
- (+) La documentació és autoexplicativa: ningú no confon 1450 amb 1450 €.
- (-) Un consumidor descurat pot sumar en coma flotant i acumular error.
  Es mitiga documentant-ho i retornant `totalEuros` ja calculat pel servidor.
- El camp es diu `preuEuros`, amb la unitat al nom, precisament per (-).

Aquest fitxer, de vint línies, estalvia una discussió d'una hora cada vegada que entra algú nou.

La revisió de disseny

És una revisió que passa abans d'escriure codi, sobre l'especificació, no sobre la implementació. El seu objectiu és que cap endpoint públic no neixi sense que almenys una altra persona n'hagi mirat la forma. La conversa és curta si el disseny és bo i llarga si no ho és, que és exactament el que vols: el moment barat de canviar /v1/comandes/{id}/cancellar per /v1/comandes/{id}/anullacio és quan només existeix en un YAML.

Un guió de quinze minuts que funciona:

  1. Quin cas d'ús real habilita aquest endpoint? (Si no hi ha resposta concreta, no es construeix.)
  2. És un recurs o és un verb disfressat?
  3. Els noms segueixen la guia d'estil? S'assemblen als que ja existeixen?
  4. Quins codis d'estat retorna i quins hi falten?
  5. Què passa si es crida dues vegades? És idempotent? Ho ha de ser?
  6. Qui el pot cridar? Què hi veu un client que no n'és el propietari?
  7. Es pot afegir un camp d'aquí a sis mesos sense trencar ningú?
  8. Està paginat si retorna una col·lecció?

El linting automàtic

El que es pot comprovar amb una màquina no ha de consumir temps humà en la revisió. Aquí entra Spectral.

  1. Linting de l'especificació amb Spectral

Spectral és un linter per a fitxers OpenAPI i AsyncAPI. S'executa sobre openapi.yaml —el que vam començar a 02-08— i aplica regles escrites per tu. Converteix la guia d'estil, que és prosa, en comprovacions que fallen a la integració contínua.

# Instal·lació com a dependència de desenvolupament del projecte
npm install --save-dev @stoplight/spectral-cli

# Execució sobre el contracte
npx spectral lint openapi.yaml

El fitxer de regles es diu .spectral.yaml i viu a l'arrel del projecte:

# .spectral.yaml — regles d'estil de l'API de la Botiga Aroma
extends: ["spectral:oas"]          # hereta les regles base d'OpenAPI (estructura vàlida)

rules:
  # --- Regles heretades que ajustem ---
  operation-tag-defined: error     # tota operació ha de tenir una etiqueta declarada
  info-contact: error              # el contracte ha de dir a qui escriure

  # --- Regles pròpies de la Botiga Aroma ---

  aroma-rutes-en-minuscula-i-plural:
    description: Les rutes fan servir substantius en plural i minúscules, sense guions baixos ni camelCase.
    message: "{{property}} no compleix el conveni de rutes de la Botiga Aroma."
    severity: error
    given: $.paths[*]~             # el ~ selecciona la CLAU (la ruta), no el seu valor
    then:
      function: pattern
      functionOptions:
        match: "^(/[a-z0-9-]+|/\\{[a-zA-Z]+\\})+$"

  aroma-sense-verbs-a-la-uri:
    description: Les URIs no contenen verbs; l'acció l'expressa el mètode HTTP.
    message: "La ruta {{property}} conté un verb: fes servir un substantiu o un subrecurs."
    severity: error
    given: $.paths[*]~
    then:
      function: pattern
      functionOptions:
        notMatch: "(crear|obtenir|llistar|esborrar|actualitzar|get|create|delete|update|cercar)"

  aroma-propietats-en-camelcase:
    description: Totes les propietats dels esquemes van en camelCase.
    severity: error
    given: $.components.schemas[*].properties[*]~
    then:
      function: casing
      functionOptions:
        type: camel

  aroma-colleccions-paginades:
    description: Tota operació GET que retorna una col·lecció declara limit i desplacament.
    severity: warn
    given: $.paths[*].get
    then:
      field: parameters
      function: schema
      functionOptions:
        schema:
          type: array
          contains:
            type: object
            properties:
              name: { const: limit }

  aroma-tota-operacio-declara-401:
    description: Les operacions sota /v1 han de documentar la resposta 401.
    severity: warn
    given: $.paths[?(@property.match(/^\/(cafes|comandes|clients|ressenyes|cistelles)/))][get,post,put,patch,delete]
    then:
      field: responses.401
      function: truthy

  aroma-capcaleres-propies-amb-prefix:
    description: Les capçaleres pròpies porten el prefix Aroma-, mai X-.
    severity: error
    given: $.paths[*][*].responses[*].headers[*]~
    then:
      function: pattern
      functionOptions:
        notMatch: "^[Xx]-"

Repassem les peces menys evidents:

  • extends: ["spectral:oas"] carrega el conjunt de regles oficial que verifica que el document és un OpenAPI estructuralment vàlid. Les teves regles se sumen a aquestes.
  • given és una expressió JSONPath que selecciona els nodes que cal comprovar. El sufix ~ és específic de Spectral i significa "aplica la regla a la clau del node, no al seu valor": per això $.paths[*]~ selecciona /cafes/{id} com a text.
  • then.function és la comprovació. pattern accepta match (ha de complir) i notMatch (no ha de complir); casing verifica convenis de noms; truthy exigeix que el camp existeixi i no estigui buit.
  • severity decideix si la fallada trenca la construcció (error) o només avisa (warn). Una regla nova s'introdueix sempre com a warn, es netegen les infraccions existents i només llavors es puja a error; si no, ningú no pot fer merge el dia que l'afegeixes.

A la integració contínua (que veurem a 05-05) això és un pas més:

npx spectral lint openapi.yaml --fail-severity=error

L'efecte cultural és més gran que el tècnic: la discussió sobre l'estil deixa de passar a cada pull request i passa a produir-se una sola vegada, quan es proposa la regla.

  1. Dissenyar per al consumidor: la pantalla "les meves comandes"

Aquí hi ha l'error més comú del disseny d'APIs, i no és un error de nomenclatura: dissenyar des del model de dades en comptes de des del cas d'ús. Els recursos de la Botiga Aroma són un reflex gairebé exacte de les taules de SQLite, cosa còmoda per a nosaltres i de vegades terrible per a qui consumeix.

Vegem-ho amb un cas concret. L'app Aroma Mòbil té una pantalla "Les meves comandes" que mostra, per cadascuna de les últimes deu comandes del client: data, estat, total, i una miniatura amb el nom del primer cafè de la llista.

Amb l'API tal com està en acabar el mòdul 3, el client mòbil fa això:

GET /v1/clients/cli_842/comandes?limit=10&ordenar=-dataCreacio
GET /v1/cafes/caf_001
GET /v1/cafes/caf_002
GET /v1/cafes/caf_007
... (una per cada cafè diferent que aparegui a les línies)

Onze peticions per pintar una pantalla. En una xarxa mòbil amb 150 ms de latència per petició, si el client les encadena són 1,6 segons només d'anada i tornada. I això és el problema N+1, el mateix que a 03-05 vam atacar dins de la base de dades, però ara passa per damunt d'HTTP, on cada salt costa mil vegades més.

La solució no és inventar GET /v1/pantalla-les-meves-comandes. És fer servir el mecanisme que ja vam dissenyar a 02-05:

GET /v1/clients/cli_842/comandes?limit=10&ordenar=-dataCreacio&expandir=linies.cafe&camps=id,dataCreacio,estat,totalEuros,linies

Una petició. La resposta porta imbricat el just:

{
  "dades": [
    {
      "id": "com_5001",
      "dataCreacio": "2026-08-02T09:14:22Z",
      "estat": "enviat",
      "totalEuros": 41.90,
      "linies": [
        {
          "cafeId": "caf_001",
          "quantitat": 2,
          "cafe": { "id": "caf_001", "nom": "Etiòpia Yirgacheffe", "torrefaccio": "clar" }
        }
      ],
      "_links": {
        "self": { "href": "/v1/comandes/com_5001" },
        "retornar": { "href": "/v1/comandes/com_5001/devolucio", "method": "POST" }
      }
    }
  ],
  "total": 7
}

El principi general: el nombre de crides necessàries per a un cas d'ús real és una mètrica de disseny de primer ordre. Quan dissenyis un recurs, escriu al costat els dos o tres fluxos que l'utilitzaran i compta les crides. Si un flux freqüent en necessita més de dues o tres, hi falta un mecanisme.

Els mecanismes disponibles, per ordre de preferència:

Mecanisme Quan Cost
expandir sobre relacions La dada extra és a un salt Baix; ja implementat
camps per aprimar La resposta és gran i el client en fa servir poc Baix
Subrecurs de col·lecció (/clients/{id}/comandes) La relació és la consulta natural Baix
Recurs agregat nou Un flux crític i molt freqüent ho justifica Alt: recurs que cal mantenir per sempre
GraphQL en paral·lel Molts clients amb necessitats molt dispars Molt alt (vegeu 01-07)

  1. Massa fina, massa gruixuda: la granularitat

L'apartat anterior empeny cap a respostes més grosses. Hi ha un límit, i passar-se té el seu propi càstig.

API massa fina API massa gruixuda
Símptoma 11 crides per a una pantalla Una crida que retorna 400 KB
Cost Latència acumulada, bateria, complexitat al client Amplada de banda, memòria, consultes SQL innecessàries
Memòria cau Cada tros es desa bé per separat Tot s'invalida quan canvia qualsevol part
Exemple dolent GET /v1/comandes/{id}/total com a recurs a part GET /v1/comandes/{id}?expandir=client.comandes.linies.cafe.ressenyes
Permisos Fàcils d'acotar per recurs Un sol endpoint barreja dades amb permisos diferents

L'equilibri de la Botiga Aroma és explícit i val la pena enunciar-lo com a regla:

El recurs per defecte és fi; el consumidor l'engreixa a demanda amb expandir, i el servidor limita fins on.

Aquest "el servidor limita" no és opcional: expandir sense sostre és un vector de denegació de servei, perquè el consumidor decideix quanta feina fa la teva base de dades. La regla concreta que aplica la Botiga Aroma és profunditat màxima 2 i una llista blanca de rutes expandibles; el detall de per què això és una defensa de disponibilitat i no només de rendiment el veurem a 04-04.

  1. Previsibilitat i el principi de mínima sorpresa

El principi de mínima sorpresa diu que, davant de dos dissenys vàlids, triïs el que el consumidor hauria endevinat. Aplicat a una API, es tradueix en una prova molt concreta que pots fer sense eines: ensenya la llista d'endpoints a algú que no conegui el sistema i demana-li que prediga la resposta de tres. El que no encerti és una sorpresa, i tota sorpresa és una consulta a la documentació repetida per cada consumidor durant tota la vida de l'API.

Els eixos on es juga la previsibilitat:

Eix Previsible Sorprenent
Nom del recurs /v1/comandes /v1/ordre-compra-v2
Nom del camp preuEuros (unitat al nom) preu (euros? cèntims?)
Booleans disponible noDisponible, senseEstoc (doble negació)
Enumerats pendent_pagament | pagat | enviat 0 | 1 | 2
Col·lecció buida {"dades": [], "total": 0} amb 200 404, o null, o {}
Camp absent S'omet, o null — però sempre igual Unes vegades null, altres absent, altres ""
Esborrat repetit 204 la primera vegada, 404 després 500
Ordre sense ordenar Estable i documentat (per id) El que decideixi SQLite aquell dia

Dues regles pràctiques que resolen la majoria dels casos:

  • Els noms es trien en el domini del consumidor, no en el de la base de dades. La taula es pot dir t_ord_hdr; el recurs es diu comandes.
  • La mateixa pregunta es respon sempre al mateix lloc. Si el total d'una col·lecció és a total, és a total en les onze col·leccions, no en una capçalera en algunes i en el cos en altres.

  1. Valors per defecte assenyats i segurs

Tot paràmetre opcional té un valor per defecte, el declaris o no. Si no el declares, el valor per defecte és el que resulti de la teva implementació, i això és una decisió de disseny presa per accident.

Un bon valor per defecte compleix dues condicions alhora:

  1. Assenyat: és el que vol el 80 % dels consumidors, perquè no l'hagin d'escriure.
  2. Segur: si el consumidor no sap què fa, el dany està acotat. En cas de dubte, el defecte és el conservador.

Els de la Botiga Aroma, ja implementats, amb la seva justificació:

Paràmetre Defecte Assenyat perquè Segur perquè
limit 20 Cap en una pantalla Sense ell, GET /v1/cafes bolcaria la taula sencera
limit màxim 100 Suficient per a un lot Acota la feina per petició
desplacament màxim 10 000 Ningú no pagina de debò més enllà Evita OFFSET gegants que escombren la taula
ordenar id ascendent Ordre estable i reproduïble Sense ordre explícit la paginació duplica i salta files
camps Tots els públics El que s'espera La llista blanca del mapejador impedeix filtrar interns
expandir Cap La resposta base és barata El cost extra és sempre una elecció explícita
Accept-Language ca Idioma principal de la botiga Determinista
Visibilitat d'un recurs nou Privat Es publica en afegir-lo al contracte, no en desplegar-lo

La quarta fila mereix un comentari, perquè és un error clàssic que ja vam evitar a 03-05 gairebé sense adonar-nos-en: la paginació sense ordre explícit no és determinista. Si el motor retorna les files en l'ordre que li convé, el desplacament=20 pot repetir files que ja vas veure al 0 i saltar-se'n d'altres. Per això el desempat per id no és un detall estètic, és correcció.

  1. El principi de robustesa i els seus límits

El principi de robustesa (o llei de Postel) diu: sigues conservador en el que envies, liberal en el que acceptes. Va néixer amb TCP i s'ha aplicat durant dècades al disseny de protocols. Avui s'accepta amb matisos importants.

La primera meitat és incondicionalment bona. Ser conservador en el que envies significa: dates sempre en el mateix format, camps sempre del mateix tipus, col·leccions sempre amb el mateix embolcall, errors sempre amb la mateixa forma. Mai no hi ha raó per relaxar-la.

La segona meitat és perillosa. Acceptar liberalment el que arriba sembla amable, però té un cost diferit brutal:

  • Si acceptes preuEuros: "14.50" (cadena) a més del número, aquest comportament es converteix en contracte de facto tan bon punt un consumidor l'utilitzi. Ja no el pots treure.
  • Si ignores silenciosament els camps que no coneixes, un consumidor que escrigui preuEuro (sense s) es pensarà que ha actualitzat el preu i no ho haurà fet. La fallada es manifesta en un altre lloc, dies després.
  • Cada tolerància és una branca del codi que cal provar i mantenir per sempre.

Per això la Botiga Aroma és estricta a l'entrada: els esquemes de Zod porten .strict(), un camp desconegut produeix 400 dades_invalides en comptes d'ignorar-se, i els tipus no es coaccionen. És menys amable en el primer minut d'integració i molt més amable en els dos anys següents.

On sí que convé ser tolerant, amb criteri:

Situació Tolerar Motiu
Espais al voltant d'un text Sí, amb .trim() Error humà trivial, sense ambigüitat
Majúscules en un correu Sí, normalitzant a minúscules El correu no distingeix caixa al domini
?torrefaccio=Clar enfront de clar No Ensenya un conveni i després el contradiu
Camp desconegut en el cos No, mai Silencia errors i habilita l'assignació massiva (04-02)
Data en un altre format No L'ambigüitat 03/04 és irresoluble
Camp desconegut en la resposta que rep un client Sí, sempre És el tolerant reader: vegeu l'apartat 12

L'asimetria de l'última fila és la clau i sol confondre's: estricte en rebre peticions, tolerant en llegir respostes d'altres. Són papers diferents.

  1. Idempotència i reintents com a contracte explícit

A 02-03 vam estudiar la idempotència com a propietat dels mètodes HTTP i a 03-03 la vam implementar amb Idempotency-Key. El que hi falta és la part de disseny: la idempotència no és una característica tècnica que s'activa, és una promesa documentada sense la qual el consumidor no pot reintentar amb seguretat.

El raonament del consumidor davant d'un timeout és sempre el mateix, i és un dilema real:

graph TD
  A[POST /v1/comandes] --> B{Arriba resposta?}
  B -->|Si, 201| C[Comanda creada. Fi]
  B -->|Timeout / xarxa caiguda| D{S'ha creat la comanda?}
  D -->|No ho se| E{L'API promet idempotencia?}
  E -->|Si, documentada| F[Reintent amb la mateixa Idempotency-Key]
  F --> G[Mateixa resposta 201, una sola comanda]
  E -->|No ho diu| H[No reintentar i arriscar perdre la comanda]
  E -->|No ho diu| I[Reintentar i arriscar cobrar dues vegades]

Sense la promesa escrita, el consumidor tria entre dues males opcions. Amb ella, el cas deixa de ser un problema.

El que cal documentar, endpoint per endpoint, és una taula com aquesta —que a més és exactament el que un consumidor busca quan alguna cosa falla en producció:

Operació Idempotent? Mecanisme Què fer davant d'un timeout
GET (qualsevol) Sí, per definició Reintentar lliurement
PUT /v1/cafes/{id} Sí, per definició Reintentar; l'estat final és el mateix
DELETE /v1/ressenyes/{id} Segona crida → 404 Reintentar; 404 significa "ja no hi és"
POST /v1/comandes Sí, amb clau Idempotency-Key obligatòria, 24 h Reintentar amb la mateixa clau
POST /v1/comandes/{id}/pagament Sí, amb clau Idempotency-Key obligatòria, 24 h Reintentar amb la mateixa clau
POST /v1/cafes/{id}/ressenyes No Consultar abans de reintentar
PATCH amb merge-patch+json Depèn del cos Reintentar només si el pedaç és absolut

La fila del PATCH és la més subtil: {"estoc": 100} és idempotent perquè fixa un valor absolut; un hipotètic {"estocIncrement": 10} no ho seria. És una raó més per preferir pedaços absoluts.

I hi ha un detall de disseny que sempre s'oblida: què passa si es reutilitza la clau amb un cos diferent. La Botiga Aroma respon 409 clau_idempotencia_reutilitzada, i això és el correcte, perquè gairebé sempre indica un bug del client (una clau generada una vegada per sessió en comptes d'una per operació) i silenciar-ho el faria indetectable.

  1. Errors accionables

La pregunta que cal fer-se davant de cada missatge d'error és una de sola: què fa el desenvolupador que el llegeix, immediatament després de llegir-lo? Si la resposta és "obrir la documentació", "preguntar al xat de suport" o "provar coses", l'error no és accionable.

{
  "error": {
    "codi": "dades_invalides",
    "missatge": "La petició conté dades no vàlides.",
    "detalls": [
      { "camp": "torrefaccio", "missatge": "Ha de ser un de: clar, mitja, fosc. S'ha rebut: 'torrat'." },
      { "camp": "preuEuros", "missatge": "Ha de ser un número més gran que 0 amb dos decimals com a màxim. S'ha rebut: -3." },
      { "camp": "notesTasts", "missatge": "Camp no reconegut. Volies dir 'notesTast'?" }
    ]
  }
}

Les quatre propietats d'un detall accionable:

Propietat A l'exemple Sense ella
Assenyala on "camp": "torrefaccio" El desenvolupador busca a ull entre 12 camps
Diu què s'esperava "un de: clar, mitja, fosc" Ha d'obrir la documentació
Diu què s'ha rebut "S'ha rebut: 'torrat'" No sap si el problema és el seu codi o la seva dada
Suggereix la correcció "Volies dir 'notesTast'?" Perd deu minuts amb una errada

I les tres regles complementàries, totes ja implementades:

  • Totes les fallades alhora, no la primera. Un consumidor que corregeix d'una en una fa sis viatges per arreglar sis errades.
  • Codi estable i llegible per màquina (estoc_insuficient), separat del missatge llegible per humans. El codi és contracte; el missatge es pot reescriure o traduir.
  • No filtrar mai l'interior en construir el missatge: ni SQL, ni rutes de fitxer, ni el nom de la columna. Això ho vam tancar a 03-07 i és igual de cert aquí.

Un últim matís de disseny: els missatges d'error d'una API s'escriuen per a desenvolupadors, no per a usuaris finals. "Es requereix el camp clientId" és correcte a l'API; "Si us plau, indica a qui enviem la comanda" és feina de la SPA. Confondre les dues audiències produeix missatges inútils per a totes dues.

  1. Compatibilitat cap endavant per disseny

A 02-07 vam veure el versionat com a estratègia. Aquí va l'altra meitat: com millor dissenyis, menys vegades necessitaràs una versió nova. Una /v2 és un fracàs car; l'objectiu és que /v1 visqui anys.

Tres tècniques que s'apliquen en el moment del disseny, no després.

Camps opcionals des del principi. Afegir un camp opcional a una resposta és compatible; afegir-lo obligatori a una petició no ho és. Per això, quan dubtis entre exigir un camp o donar-li un valor per defecte, el defecte és més barat de mantenir.

Enumerats extensibles. estat avui val pendent_pagament | pagat | enviat. Demà hi haurà retornat i anullat. Si el consumidor va escriure un switch sense branca per defecte, el teu afegit trenca la seva aplicació. Per això el contracte ha de dir explícitament, amb aquestes paraules: «el conjunt de valors d'aquest enumerat pot créixer; els clients han de tractar els valors desconeguts sense fallar». I la documentació ha de mostrar com:

// Client TOLERANT: els valors nous no trenquen la pantalla.
const ETIQUETES = {
  pendent_pagament: 'Pendent de pagament',
  pagat: 'Pagat',
  enviat: 'Enviat',
};

function etiquetaEstat(estat) {
  // Si el servidor afegeix 'retornat', mostrem alguna cosa raonable en comptes de trencar.
  return ETIQUETES[estat] ?? 'Estat desconegut';
}

Tolerant reader. És el patró que converteix el consumidor en resistent al canvi. Un lector tolerant:

  • llegeix només els camps que necessita i ignora els que no coneix (mai no falla perquè arribi un camp nou);
  • no depèn de l'ordre de les claus d'un objecte ni dels elements d'un array llevat que el contracte ho garanteixi;
  • no valida la resposta contra un esquema tancat que rebutgi propietats addicionals;
  • no reconstrueix les URL: segueix els _links que li dona el servidor (aquí HATEOAS deixa de ser teoria, com vam veure a 01-05).
// Lector TOLERANT d'una resposta de la Botiga Aroma.
function llegirCafe(json) {
  return {
    id: json.id,
    nom: json.nom,
    preu: json.preuEuros,
    // Si demà arriben 'altitudMetres' o 'varietat', simplement no es llegeixen.
  };
}

// Lector FRÀGIL: es trenca el dia que l'API afegeix un camp. No ho facis.
function llegirCafeFragil(json) {
  const claus = Object.keys(json);
  if (claus.length !== 8) throw new Error('resposta inesperada'); // ← bomba de rellotgeria
  return json;
}

La conseqüència per a tu com a dissenyador de l'API és doble: documenta que el client ha de ser tolerant i, sobretot, no publiquis un esquema amb additionalProperties: false a les respostes, perquè estaries prometent que mai no afegiràs un camp. A les peticions, al contrari, aquesta restricció és exactament el que vols.

  1. Salut, metadades i arrel descobrible

Dos endpoints que no formen part del domini i que gairebé sempre s'obliden fins que fan falta.

GET /salut ja existeix a src/app.js, deliberadament fora de /v1: no és part del contracte de negoci, és infraestructura, i no s'ha de versionar amb ell. El seu paper complet (liveness enfront de readiness, i què ha de comprovar i què no) el desenvolupem a 04-07, perquè pertany a l'observabilitat.

GET /v1, l'arrel descobrible, és el punt d'entrada que permet a un client començar sense més coneixement que una URL:

{
  "nom": "API de la Botiga Aroma",
  "versio": "1.0.0",
  "documentacio": "https://api.botigaaroma.example/docs",
  "_links": {
    "self":      { "href": "/v1" },
    "cafes":     { "href": "/v1/cafes" },
    "comandes":  { "href": "/v1/comandes" },
    "clients":   { "href": "/v1/clients" },
    "ressenyes": { "href": "/v1/ressenyes" },
    "cistelles": { "href": "/v1/cistelles" },
    "sessions":  { "href": "/v1/sessions", "method": "POST" }
  }
}

És coherent amb el nivell 3 de Richardson (01-05) i amb els _links que ja retornen tots els recursos. Costa vint línies i dona tres coses: un lloc on apuntar a la documentació, un punt de comprovació trivial per a un consumidor nou, i un lloc natural on anunciar la versió i l'enllaç a la documentació.

  1. Paginació obligatòria i límits per defecte

Val la pena enunciar-ho com a regla absoluta perquè les excepcions envelleixen malament:

Cap col·lecció no es retorna sense paginar. Mai. Ni tan sols les que avui tenen quatre elements.

L'argument és de creixement: quan /v1/cafes tenia 12 registres, retornar-los tots semblava raonable. Amb 4.000 referències i trenta consumidors mòbils, aquesta decisió és una caiguda del servei. I no pots afegir la paginació després sense trencar els clients que assumien rebre-ho tot: el canvi de [...] a {"dades": [...], "total": n} és incompatible, i limitar a 20 el que abans venia complet és pitjor, perquè no es trenca visiblement sinó que fa que els consumidors comencin a perdre dades en silenci.

D'aquí que l'embolcall {"dades": [...], "total": n} hi sigui des del primer dia a les onze col·leccions de la Botiga Aroma, fins i tot a les que retornen tres elements. El cost de tenir-lo és zero; el cost d'afegir-lo tard és una versió nova.

  1. Zones horàries, unitats i localització

Tres fonts de bugs subtils que es decideixen una vegada i s'apliquen a tota l'API.

Dates. ISO-8601, sempre en UTC, sempre amb la Z explícita: "2026-08-02T09:14:22Z".

Format Problema
1754126062 (epoch) Illegible; ambigüitat segons/mil·lisegons
02/08/2026 2 d'agost o 8 de febrer?
2026-08-02T09:14:22 Sense zona: s'interpreta diferent a cada client
2026-08-02T11:14:22+02:00 Vàlid, però barreja dues coses i complica comparar
2026-08-02T09:14:22Z Sense ambigüitat, ordenable com a text

La conversió a la zona de l'usuari és responsabilitat del client, que és l'únic que sap on és. Compte amb una excepció real: una data civil sense hora (un aniversari, la data de caducitat d'un lot) és "2026-08-02" a seques, no un instant; convertir-la a UTC la desplaça un dia a mitja Europa.

Unitats al nom. És la pràctica més barata i rendible d'aquesta lliçó: preuEuros, pesGrams, duracioSegons, altitudMetres. Un camp pes obliga a mirar la documentació cada vegada; pesGrams no. I el nom viatja amb la dada: apareix als logs, als bolcats i al codi del client.

Diners. La regla de la Botiga Aroma: cèntims enters per dins (preu_centims), euros amb dos decimals per fora (preuEuros). Mai coma flotant a la base de dades ni als càlculs, perquè 0.1 + 0.2 !== 0.3. I si algun dia la botiga ven fora de la zona euro, caldrà un moneda: "EUR" al costat de l'import i, millor encara, un objecte {"quantitat": 14.50, "moneda": "EUR"}; dissenyar-ho ara costa poc i evita una migració incompatible.

Localització. L'idioma del contingut es negocia amb Accept-Language (02-05) i es declara amb Vary: Accept-Language perquè les memòries cau no barregin idiomes —cosa que es torna crítica a 04-06—. El que no es tradueix mai és el contracte: els noms de camp, els valors dels enumerats i els codis d'error són identificadors, no text per a humans. estat: "enviat" és un símbol estable; la paraula "Enviat" que veu l'usuari la posa la SPA.

  1. Antipatrons que cal evitar

Antipatró Exemple Per què és dolent Alternativa
Verbs a la URI POST /v1/crearComanda, GET /v1/comandes/obtenirTotes Duplica el que ja diu el mètode; multiplica endpoints; trenca la memòria cau i els proxys POST /v1/comandes, GET /v1/comandes
200 amb exit: false 200 OK + {"exit": false, "error": "sense estoc"} Els clients HTTP, proxys, memòries cau i monitoratges creuen que tot va bé; obliga a inspeccionar el cos sempre 409 + {"error": {"codi": "estoc_insuficient"}}
Exposar l'esquema de la BD {"t_ord_id": 5001, "fk_cli": 842, "flg_del": 0} Lliga el contracte a la taula: no pots refactoritzar; filtra informació interna Mapejador explícit amb llista blanca (03-03)
Endpoint "tot en un" POST /v1/api amb {"accio": "crear_comanda", ...} És RPC sobre HTTP: un sol codi d'estat, sense memòria cau, sense permisos per recurs Recursos i mètodes HTTP
Paràmetres màgics ?mode=2, ?tipus=A, ?flags=15 Ningú no recorda què significa 2; impossible de llegir en un log ?estat=pagat, ?incloureAnullats=true
Resposta que canvia de forma dades és un objecte si n'hi ha un i un array si n'hi ha diversos El client necessita un if a cada consum; trenca el tipatge Sempre array a les col·leccions, encara que en tingui un
Filtrar identificadors interns Retornar id autoincremental, hash_contrasenya, actiu, versio Permet enumerar recursos aliens i filtra dades sensibles Ids opacs amb prefix; llista blanca al mapejador
Imbricació profunda /v1/clients/842/comandes/5001/linies/3/cafe/ressenyes/101 URL impredictibles; el mateix recurs accessible per N rutes Màxim un nivell; la resta per id: /v1/ressenyes/res_101
GET que modifica GET /v1/comandes/com_5001/anullar Un rastrejador o un prefetch del navegador anul·la comandes POST /v1/comandes/com_5001/anullacio
Col·lecció sense paginar GET /v1/comandes retorna les 400.000 Caiguda garantida; impossible d'arreglar sense trencar Paginació des del dia u
Números com a enumerats "estat": 2 Illegible; el 2 acaba significant una altra cosa "estat": "pagat"
Nuls amb significat preuEuros: -1 per a "no disponible" Un client descurat suma −1 a la cistella disponible: false

La fila del GET que modifica no és teòrica: és un dels incidents més repetits de la història del web. Un rastrejador que segueix enllaços, o el prefetch d'un navegador, executa accions destructives perquè algú va decidir que un enllaç era més còmode que un formulari. La safety del GET que vam veure a 02-03 és una promesa que fan els intermediaris de tota la xarxa, no una recomanació.

  1. La llista de revisió de disseny de la Botiga Aroma

Aquesta llista s'aplica a cada endpoint nou abans d'escriure'n la implementació. És accionable: cada línia es respon sí o no.

Recurs i URI

  • [ ] El nom és un substantiu en plural, minúscules, sense verbs.
  • [ ] La ruta té com a màxim un nivell d'imbricació.
  • [ ] L'identificador és opac i amb prefix (caf_, com_, cli_).
  • [ ] La URI és estable: no conté res que hagi de canviar (estat, categoria, any).

Mètodes i semàntica

  • [ ] El mètode coincideix amb la semàntica: GET és segur, PUT/DELETE idempotents.
  • [ ] Si és POST i no és idempotent per naturalesa, s'ha decidit si exigeix Idempotency-Key.
  • [ ] Hi ha 405 amb capçalera Allow per als mètodes no admesos d'aquella ruta.

Peticions

  • [ ] El cos té esquema Zod amb .strict(); els camps desconeguts donen 400.
  • [ ] Cada paràmetre de query és a la llista blanca; un de desconegut dona 400 parametre_invalid.
  • [ ] Els opcionals tenen defecte documentat, assenyat i segur.
  • [ ] La mida del cos està acotada (100 kB globals).

Respostes

  • [ ] El codi d'estat surt de l'arbre de decisió de 02-04.
  • [ ] Si és una col·lecció: embolcall {"dades", "total"}, paginada, amb Link.
  • [ ] Els camps són camelCase, amb unitat al nom quan calgui.
  • [ ] Les dates són ISO-8601 UTC amb Z.
  • [ ] Passa pel mapejador: cap columna interna no arriba al JSON.
  • [ ] _links.self sempre; enllaços d'acció només si l'acció és possible ara.
  • [ ] Si crea un recurs: 201 amb Location.

Errors

  • [ ] Tots els codis utilitzats existeixen al catàleg, o s'ha decidit ampliar-lo i documentar-lo.
  • [ ] dades_invalides retorna totes les fallades, amb camp, esperat i rebut.
  • [ ] Cap missatge no filtra SQL, rutes de fitxer, versions ni l'existència de recursos aliens.

Seguretat i permisos

  • [ ] Està decidit quins rols el poden cridar (client, empleat, administrador, soci).
  • [ ] Un client no pot accedir a dades d'un altre ni distingir "no existeix" de "no és teu".
  • [ ] Cap camp sensible no entra per assignació massiva (rol, actiu, saldo).

Evolució

  • [ ] Es pot afegir un camp a la resposta sense trencar ningú.
  • [ ] Els enumerats estan documentats com a extensibles.
  • [ ] És a openapi.yaml i npx spectral lint passa sense errors.

Proves

  • [ ] Hi ha prova d'integració del camí feliç i d'almenys dos errors.
  • [ ] Hi ha prova de permisos: l'accés aliè es rebutja.

  1. Deute de disseny: què fer quan ja t'has equivocat

T'equivocaràs. La pregunta útil no és com evitar-ho, sinó què fer després. El primer pas és classificar l'error, perquè el tractament depèn del tipus:

Tipus d'error Exemple a la Botiga Aroma Cost d'arreglar-ho Tractament
Cosmètic, sense consumidors Un camp mal anomenat en un endpoint que encara no fa servir ningú Nul Arregla'l avui
Additiu Falta expandir en un recurs Baix Afegeix-lo; és compatible
Ampliació de tolerància limit màxim de 100 a 200 Baix Amplia; ningú no es trenca
Canvi de forma total passa de capçalera a cos Alt Convivència temporal i Deprecation
Canvi de semàntica estat: "pagat" passa a significar una altra cosa Molt alt Camp nou; el vell es congela
Error estructural El recurs equivocat, RPC disfressat Màxim /v2 per a aquell recurs, o redisseny amb doble escriptura

Les cinc regles que fan manejable el deute de disseny:

  1. Reconeix-lo per escrit. Un fitxer docs/deute-de-disseny.md amb "sabem que POST /v1/cistelles/{id}/linies/{cafeId} hauria de ser PUT i per què no ho canviem" evita que cada persona nova reobri la discussió i, sobretot, evita que l'error es copiï al recurs següent.
  2. Deixa de sagnar. El primer no és arreglar el vell, és que el nou no repeteixi l'error. Una regla de Spectral impedeix que el patró es propagui encara que no puguis netejar el passat.
  3. Conviu abans de trencar. El camp nou i el vell es retornen alhora; el vell es marca deprecated a OpenAPI i amb les capçaleres Deprecation i Sunset de 02-07.
  4. Mesura abans de retirar. Si no saps quants consumidors fan servir el camp vell, no el pots retirar. Instrumentar-ho és una necessitat de disseny, no només d'operació; a 04-07 veuràs com es compta.
  5. Agrupa els canvis incompatibles. Si has de trencar, trenca una vegada: acumula els canvis incompatibles i treu-los junts a /v2. Tres versions en un any destrueixen la confiança més que un error de disseny.

Errors Comuns i Consells

Confondre consistència amb rigidesa. La consistència és sobre la forma, no sobre les capacitats. Un recurs pot tenir paràmetres propis que cap altre no té; el que no pot és anomenar-los amb un altre conveni.

Dissenyar per al consumidor que tens avui. La pantalla "les meves comandes" d'Aroma Mòbil és un cas d'ús, no el cas d'ús. Optimitzar l'API fins a convertir-la en el backend d'una pantalla concreta la torna inútil per al client següent. La prova: si el nom d'un endpoint conté el d'una pantalla, has creuat la línia.

Afegir un endpoint agregat a la primera queixa. Abans de crear /v1/resum-client, comprova si expandir i camps resolen el cas. Cada recurs agregat s'ha de mantenir, versionar, documentar i provar per sempre.

Creure que la guia d'estil es compleix sola. Sense Spectral a la integració contínua, la guia s'erosiona en tres mesos. Automatitza allò automatitzable el mateix dia que escrius la regla.

Posar totes les regles de Spectral en error de cop. Bloqueges tot l'equip. Entra en warn, neteja i puja.

Tractar openapi.yaml com a documentació. És el contracte. Si el codi i el YAML difereixen, hi ha un bug en algun lloc; quin dels dos ho veurem a 05-04, amb les proves de contracte.

Consell: escriu la petició i la resposta d'exemple abans que el codi. Cinc minuts escrivint el JSON que vols rebre detecten més problemes de disseny que dues hores implementant.

Consell: llegeix la teva pròpia API com si fos aliena. Tanca l'editor, obre només la documentació i intenta resoldre un cas d'ús complet. Tot el que t'obligui a mirar el codi és una fallada de disseny.

Exercicis

Exercici 1: auditoria d'antipatrons

Un equip proposa aquests cinc endpoints per al mòdul de fidelització de la Botiga Aroma. Identifica els antipatrons de cadascun i proposa l'alternativa correcta.

1. POST /v1/clients/cli_842/calcularPunts
2. GET  /v1/punts?client=842&mode=3
3. GET  /v1/clients/cli_842/punts   → 200 {"exit": true, "dades": {...}}
                                      200 {"exit": false, "error": "sense programa"}
4. GET  /v1/clients/cli_842/comandes/com_5001/linies/1/cafe/punts
5. GET  /v1/promocions   → retorna les 1.200 promocions històriques

Exercici 2: reduir les crides d'una pantalla

La pantalla "Detall de comanda" d'Aroma Mòbil mostra: dades de la comanda, nom i foto de cada cafè de les línies, adreça d'enviament del client i estat de l'enviament. Avui necessita: 1 crida a la comanda + 1 per cafè (fins a 5) + 1 al client + 1 a l'enviament = fins a 8 crides.

Dissenya la petició única que resol la pantalla fent servir només els mecanismes que ja existeixen a l'API, i justifica quin límit posaries a expandir perquè aquest flux no es converteixi en un problema.

Exercici 3: escriure una regla de Spectral

Escriu una regla de Spectral anomenada aroma-dates-amb-sufix-iso que avisi quan una propietat d'un esquema tingui format date-time i el seu nom no comenci per data. Justifica per què la severitat ha de ser warn i no error en el moment d'introduir-la.

Solucions

Solució 1

Núm. Antipatrons Alternativa
1 Verb a la URI (calcularPunts); a més un POST que només llegeix GET /v1/clients/cli_842/punts
2 Identificador sense prefix (842); paràmetre màgic (mode=3); filtre per client en una col·lecció global quan existeix el subrecurs GET /v1/clients/cli_842/punts?incloureCaducats=true
3 200 amb exit:false; embolcall dades/exit diferent de la resta de l'API 200 amb el recurs, o 404 {"error":{"codi":"programa_no_trobat"}}
4 Imbricació profunda (sis nivells); la mateixa dada accessible per diverses rutes GET /v1/cafes/caf_001/punts, o un camp punts a la representació del cafè
5 Col·lecció sense paginar GET /v1/promocions?limit=20&desplacament=0 amb total i Link

I un antipatró transversal: la col·lecció /v1/punts del cas 2 suggereix que "punt" és un recurs de primer nivell quan en realitat és un atribut de la relació client-programa. Si no existeix un GET /v1/punts/pnt_1 que retorni un punt individual, probablement no hauria d'existir la col·lecció.

Solució 2

GET /v1/comandes/com_5001?expandir=linies.cafe,client,enviament HTTP/1.1
Host: api.botigaaroma.example
Authorization: Bearer <token>
Accept: application/json

I per no portar de més, es combina amb camps:

GET /v1/comandes/com_5001?expandir=linies.cafe,client,enviament&camps=id,estat,totalEuros,linies,client,enviament

De vuit crides a una. Sobre el límit d'expandir, tres restriccions que cal imposar alhora:

  1. Profunditat màxima 2. linies.cafe és vàlid; linies.cafe.ressenyes.autor no. Cada nivell multiplica les consultes.
  2. Llista blanca de rutes expandibles per recurs, declarada al contracte. No val qualsevol combinació: només les que tenen una consulta eficient al darrere.
  3. Prohibit expandir col·leccions no acotades. expandir=client porta un objecte; un hipotètic expandir=client.comandes portaria una col·lecció sencera dins d'una altra. Si es permet, es pagina o es limita als N primers.

Sense aquestes tres regles, el consumidor decideix quanta feina fa la teva base de dades, que és justament el que cal evitar (04-04).

Solució 3

  aroma-dates-amb-sufix-iso:
    description: Les propietats date-time s'han d'anomenar començant per 'data'.
    message: "La propietat {{property}} és date-time però no comença per 'data'."
    severity: warn
    given: $.components.schemas[*].properties[?(@.format == 'date-time')]~
    then:
      function: pattern
      functionOptions:
        match: "^data[A-Z]?"

El given combina dues coses: el filtre JSONPath [?(@.format == 'date-time')] selecciona només les propietats amb aquest format, i el ~ final fa que la comprovació s'apliqui al nom de la propietat en comptes de a la seva definició.

Per què warn i no error en introduir-la: l'especificació actual ja té propietats que la incompleixen (per exemple un creatEl heretat). Si la regla entra com a error, la integració contínua es posa en vermell i bloqueja tot l'equip per un assumpte d'estil, amb la qual cosa la reacció probable serà desactivar-la. El procediment correcte és entrar com a warn, corregir les infraccions en un pull request específic —canviant-ne el nom amb període de convivència si el camp ja és públic— i només llavors pujar-la a error perquè ningú no pugui reintroduir el problema.

Conclusió

El que separa una API correcta d'una d'excel·lent no és una tècnica, és un conjunt de decisions preses amb criteri i sostingudes en el temps. La consistència per damunt de tot, perquè és el que permet al consumidor extrapolar i deixar de llegir la documentació; el disseny des del cas d'ús i no des del model de dades, que converteix onze crides en una sense inventar recursos artificials; la previsibilitat, els defectes assenyats i segurs, l'estrictesa a l'entrada i la tolerància en la lectura; la idempotència com a promesa documentada i no com a detall d'implementació; els errors que diuen què cal fer a continuació; i la compatibilitat cap endavant dissenyada des del principi, perquè /v1 visqui anys. Tens a més el catàleg d'antipatrons per reconèixer-los en qualsevol API, la llista de revisió que s'aplica a cada endpoint nou, Spectral perquè la guia d'estil es compleixi sola, i una estratègia per al deute de disseny que ja existeix.

Tot això millora l'API per a qui la fa servir bé. La lliçó següent s'ocupa de qui la fa servir malament: a 04-02, Seguretat en APIs RESTful, recorrerem l'OWASP API Security Top 10 sobre la Botiga Aroma —amb el GET /v1/comandes/com_5001 d'un altre client com a exemple de BOLA, el "rol": "administrador" en el registre com a assignació massiva i els endpoints de prova oblidats com a inventari descontrolat—, veurem per què el transport es xifra sempre, quines injeccions continuen sent possibles després de les sentències preparades de 03-05, afegirem helmet a src/app.js amb la seva posició exacta a la cadena de middlewares, i acabarem amb un model d'amenaces lleuger que diu, actiu per actiu, on està implementada cada defensa.

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