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
- Correcta enfront d'excel·lent
- La consistència com a valor suprem
- Com es garanteix la consistència a la pràctica
- Linting de l'especificació amb Spectral
- Dissenyar per al consumidor: la pantalla "les meves comandes"
- Massa fina, massa gruixuda: la granularitat
- Previsibilitat i el principi de mínima sorpresa
- Valors per defecte assenyats i segurs
- El principi de robustesa i els seus límits
- Idempotència i reintents com a contracte explícit
- Errors accionables
- Compatibilitat cap endavant per disseny
- Salut, metadades i arrel descobrible
- Paginació obligatòria i límits per defecte
- Zones horàries, unitats i localització
- Antipatrons que cal evitar
- La llista de revisió de disseny de la Botiga Aroma
- Deute de disseny: què fer quan ja t'has equivocat
- 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.
- 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.
- 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:
- Quin cas d'ús real habilita aquest endpoint? (Si no hi ha resposta concreta, no es construeix.)
- És un recurs o és un verb disfressat?
- Els noms segueixen la guia d'estil? S'assemblen als que ja existeixen?
- Quins codis d'estat retorna i quins hi falten?
- Què passa si es crida dues vegades? És idempotent? Ho ha de ser?
- Qui el pot cridar? Què hi veu un client que no n'és el propietari?
- Es pot afegir un camp d'aquí a sis mesos sense trencar ningú?
- 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.
- 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.yamlEl 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ó.patternacceptamatch(ha de complir) inotMatch(no ha de complir);casingverifica convenis de noms;truthyexigeix que el camp existeixi i no estigui buit.severitydecideix si la fallada trenca la construcció (error) o només avisa (warn). Una regla nova s'introdueix sempre com awarn, es netegen les infraccions existents i només llavors es puja aerror; 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:
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.
- 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,liniesUna 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) |
- 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.
- 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 diucomandes. - La mateixa pregunta es respon sempre al mateix lloc. Si el total d'una col·lecció és a
total, és atotalen les onze col·leccions, no en una capçalera en algunes i en el cos en altres.
- 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:
- Assenyat: és el que vol el 80 % dels consumidors, perquè no l'hagin d'escriure.
- 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ó.
- 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.
- 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} |
Sí | 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.
- 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.
- 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
_linksque 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.
- 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ó.
- 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.
- 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.
- 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ó.
- 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/DELETEidempotents. - [ ] Si és
POSTi no és idempotent per naturalesa, s'ha decidit si exigeixIdempotency-Key. - [ ] Hi ha
405amb capçaleraAllowper als mètodes no admesos d'aquella ruta.
Peticions
- [ ] El cos té esquema Zod amb
.strict(); els camps desconeguts donen400. - [ ] 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, ambLink. - [ ] 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.selfsempre; enllaços d'acció només si l'acció és possible ara. - [ ] Si crea un recurs:
201ambLocation.
Errors
- [ ] Tots els codis utilitzats existeixen al catàleg, o s'ha decidit ampliar-lo i documentar-lo.
- [ ]
dades_invalidesretorna 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.yamlinpx spectral lintpassa 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.
- 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:
- Reconeix-lo per escrit. Un fitxer
docs/deute-de-disseny.mdamb "sabem quePOST /v1/cistelles/{id}/linies/{cafeId}hauria de serPUTi 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. - 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.
- Conviu abans de trencar. El camp nou i el vell es retornen alhora; el vell es marca
deprecateda OpenAPI i amb les capçaleresDeprecationiSunsetde 02-07. - 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.
- 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òriquesExercici 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/jsonI 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,enviamentDe vuit crides a una. Sobre el límit d'expandir, tres restriccions que cal imposar alhora:
- Profunditat màxima 2.
linies.cafeés vàlid;linies.cafe.ressenyes.autorno. Cada nivell multiplica les consultes. - Llista blanca de rutes expandibles per recurs, declarada al contracte. No val qualsevol combinació: només les que tenen una consulta eficient al darrere.
- Prohibit expandir col·leccions no acotades.
expandir=clientporta un objecte; un hipotèticexpandir=client.comandesportaria 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
- Què és una API?
- Història i evolució de les APIs
- Fonaments d'HTTP per a APIs
- Principis bàsics de REST
- Model de maduresa de Richardson i HATEOAS
- REST vs. SOAP
- REST davant de GraphQL, gRPC i webhooks
Mòdul 2: Disseny d'APIs RESTful
- Principis de disseny d'APIs RESTful
- Recursos i URIs
- Mètodes HTTP
- Codis d'estat HTTP
- Representacions, capçaleres i negociació de contingut
- Filtratge, ordenació, paginació i cerca
- Versionat d'APIs
- Documentació d'APIs
Mòdul 3: Desenvolupament d'APIs RESTful
- Configuració de l'entorn de desenvolupament
- Creació d'un servidor bàsic
- Gestió de peticions i respostes
- Validació de dades d'entrada
- Persistència i capa d'accés a dades
- Autenticació i autorització
- Gestió d'errors
- Proves i validació
Mòdul 4: Bones Pràctiques i Seguretat
- Bones pràctiques en el disseny d'APIs
- Seguretat en APIs RESTful
- OAuth 2.0 i OpenID Connect a la pràctica
- Rate limiting i throttling
- CORS i polítiques de seguretat
- Memòria cau HTTP i rendiment
- Observabilitat: logs, mètriques i traces
Mòdul 5: Eines i Frameworks
- Postman per a proves d'APIs
- Swagger i OpenAPI per a documentació
- Frameworks populars per a APIs RESTful
- Contractes, mocks i proves automatitzades d'API
- Integració contínua i desplegament
- API gateways i portals de desenvolupador
