A la lliçó anterior vam veure que REST es compleix en graus, i que gairebé cap API real no és plenament RESTful. Això deixa oberta una pregunta molt pràctica: com es mesura aquest grau? Leonard Richardson va proposar el 2008 un model de quatre nivells que s'ha convertit en el vocabulari estàndard del sector. En aquesta lliçó recorrerem els quatre nivells reescrivint la mateixa operació de la Botiga Aroma —crear una comanda— en cadascun, de manera que l'evolució es vegi amb els ulls. Després entrarem en HATEOAS, el nivell més alt i més discutit, veurem formats hipermèdia reals com HAL i JSON:API, i tancarem amb un debat honest sobre quan compensa arribar fins a dalt i quan no.
Contingut
- Per a què serveix el model de maduresa
- Nivell 0: el pantà de POX
- Nivell 1: recursos
- Nivell 2: verbs HTTP i codis d'estat
- Nivell 3: controls hipermèdia (HATEOAS)
- Els quatre nivells d'un cop d'ull
- Què és HATEOAS i quin problema resol
- Formats hipermèdia: HAL, JSON:API i Siren
- El debat honest: per què gairebé ningú no arriba al nivell 3?
- Criteris per decidir el teu nivell
- Per a què serveix el model de maduresa
Leonard Richardson va presentar aquest model a la conferència QCon del 2008, i Martin Fowler el va popularitzar en un article del 2010. La seva virtut és doble:
- Ofereix un vocabulari compartit: dir "estem al nivell 2 i no pensem pujar" comunica en cinc paraules una decisió d'arquitectura completa.
- Converteix una discussió binària i estèril ("això és REST o no?") en una escala progressiva sobre la qual es pot raonar.
Dos advertiments abans de començar:
- No és de Fielding ni és normatiu. És un model descriptiu, útil per diagnosticar, no un examen que s'hagi d'aprovar.
- Pujar de nivell no és automàticament millor. Cada nivell té un cost. L'objectiu és triar amb criteri, no maximitzar la puntuació.
- Nivell 0: el pantà de POX
POX vol dir Plain Old XML (XML del muntó), encara que avui el pantà és més aviat de JSON. Els seus senyals d'identitat:
- Un únic endpoint per a tot.
- Un únic mètode, gairebé sempre
POST. - L'operació que es vol executar va dins del cos.
- HTTP es fa servir com a simple túnel de transport: els seus mètodes, els seus codis i la seva memòria cau s'ignoren del tot.
Així crearia la Botiga Aroma una comanda en nivell 0:
POST /api HTTP/1.1
Host: api.botigaaroma.example
Content-Type: application/json
{
"accio": "crearComanda",
"parametres": {
"clientId": "cli_842",
"linies": [{ "cafeId": "caf_001", "quantitat": 2 }]
}
}I la resposta:
HTTP/1.1 200 OK
Content-Type: application/json
{
"exit": true,
"resultat": { "comandaId": "com_5001", "totalEuros": 29.00 }
}I així consultaria aquesta mateixa comanda:
POST /api HTTP/1.1
Content-Type: application/json
{ "accio": "obtenirComanda", "parametres": { "comandaId": "com_5001" } }Què hi ha malament, en termes concrets i mesurables:
| Problema | Conseqüència pràctica |
|---|---|
Tot va per POST |
Cap resposta no es pot posar a la memòria cau, ni pel navegador ni per un CDN |
| No hi ha URI de recursos | No es pot compartir ni desar com a adreça d'interès un enllaç a una comanda |
L'error viatja al cos amb 200 OK |
El monitoratge no detecta fallades; els reintents automàtics no funcionen |
La semàntica és a accio |
Cap intermediari no entén res sense conèixer el teu domini |
| No hi ha idempotència | Un reintent després d'una fallada de xarxa crea una segona comanda |
Això és, essencialment, RPC sobre HTTP. És exactament el que feia SOAP (lliçó 01-06) i el que fa avui... GraphQL, que també fa servir un únic endpoint i POST (lliçó 01-07). Amb una diferència important: GraphQL ho fa de manera deliberada, amb un contracte tipat i eines pròpies que compensen el que hi perd. El nivell 0 sol ser accidental.
- Nivell 1: recursos
El primer salt: deixar de tenir un únic endpoint i donar identitat pròpia a cada cosa del domini. Ja no es parla amb "l'API", es parla amb recursos concrets.
POST /v1/comandes HTTP/1.1
Content-Type: application/json
{
"accio": "crear",
"clientId": "cli_842",
"linies": [{ "cafeId": "caf_001", "quantitat": 2 }]
}HTTP/1.1 200 OK
Content-Type: application/json
{ "exit": true, "comandaId": "com_5001", "totalEuros": 29.00 }Consultar aquesta comanda, en nivell 1:
Què s'hi ha guanyat:
- Cada comanda té URI pròpia:
/v1/comandes/com_5001. Es pot enllaçar, registrar en logs i encaminar de manera diferenciada. - Es pot repartir la càrrega per recurs: les comandes a uns servidors, el catàleg a uns altres.
- L'API és molt més comprensible en llegir un log o una traça.
Què hi falta encara:
- Es continua fent servir
POSTper a tot, fins i tot per llegir. Sense memòria cau, i sense distingir lectura d'escriptura. - El verb continua al cos (
accio). - Els codis d'estat continuen sense fer-se servir.
El nivell 1 és un estat de transició: molta gent hi arriba en reorganitzar una API antiga i es queda a mig camí. Reconèixer-lo té valor diagnòstic: si les teves URL són bones però tot és POST, ets aquí.
- Nivell 2: verbs HTTP i codis d'estat
Aquí hi ha el salt gran, i on viu la immensa majoria de les APIs professionals, inclosa la que construirem al mòdul 3. Consisteix a fer servir el protocol tal com va ser dissenyat: el verb indica la intenció i el codi d'estat indica el resultat.
Crear una comanda:
POST /v1/comandes HTTP/1.1
Host: api.botigaaroma.example
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
{
"clientId": "cli_842",
"linies": [{ "cafeId": "caf_001", "quantitat": 2 }]
}HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/comandes/com_5001
Cache-Control: no-store
{
"id": "com_5001",
"clientId": "cli_842",
"estat": "pendent_pagament",
"totalEuros": 29.00,
"linies": [
{ "cafeId": "caf_001", "nom": "Etiòpia Yirgacheffe", "quantitat": 2, "preuEuros": 14.50 }
],
"dataCreacio": "2026-08-14T09:12:44Z"
}Fixa't en tres detalls que no existien als nivells anteriors:
201 Createden comptes de200 OK: comunica amb precisió que s'ha creat alguna cosa nova.Location: diu on ha quedat el recurs, sense que el client hagi de compondre la URL.Cache-Control: no-store: instrucció explícita perquè ningú no desi dades d'una comanda.
La resta d'operacions sobre el mateix recurs, ara sense cap camp accio:
# Consultar una comanda
curl -H "Authorization: Bearer TOKEN" \
https://api.botigaaroma.example/v1/comandes/com_5001 # -> 200 OK
# Llistar les comandes d'un client
curl -H "Authorization: Bearer TOKEN" \
"https://api.botigaaroma.example/v1/comandes?clientId=cli_842" # -> 200 OK
# Cancel·lar una comanda
curl -X DELETE -H "Authorization: Bearer TOKEN" \
https://api.botigaaroma.example/v1/comandes/com_5001 # -> 204 No ContentI els errors s'expressen en el mateix protocol:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json
{
"error": {
"codi": "estoc_insuficient",
"missatge": "No hi ha estoc suficient per completar la comanda",
"detalls": [
{ "cafeId": "caf_001", "sollicitat": 2, "disponible": 0 }
]
}
}El codi 422 diu "he entès la teva petició però no la puc processar pel seu contingut"; el cos en dona el detall llegible per al desenvolupador. Màquines i humans, cadascú amb la seva informació.
Beneficis acumulats al nivell 2:
- Memòria cau real als
GET, amb impacte directe en cost i latència. - Idempotència a
GET,PUTiDELETE: els reintents són segurs. - Monitoratge automàtic: qualsevol eina compta els 5xx sense saber res de cafès.
- Corba d'aprenentatge mínima per a qui la consumeix: si coneix HTTP, ja coneix la teva API.
- Nivell 3: controls hipermèdia (HATEOAS)
L'últim nivell afegeix enllaços a les respostes: el servidor no només retorna dades, també indica quines transicions són possibles des de l'estat actual.
HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/comandes/com_5001
{
"id": "com_5001",
"estat": "pendent_pagament",
"totalEuros": 29.00,
"linies": [
{ "cafeId": "caf_001", "nom": "Etiòpia Yirgacheffe", "quantitat": 2, "preuEuros": 14.50 }
],
"_links": {
"self": { "href": "/v1/comandes/com_5001" },
"pagar": { "href": "/v1/comandes/com_5001/pagament", "method": "POST" },
"cancellar": { "href": "/v1/comandes/com_5001", "method": "DELETE" },
"client": { "href": "/v1/clients/cli_842" },
"linies": { "href": "/v1/comandes/com_5001/linies" }
}
}L'interessant passa quan canvia l'estat. Un cop pagada i enviada, la mateixa petició GET /v1/comandes/com_5001 retorna altres enllaços:
{
"id": "com_5001",
"estat": "enviat",
"totalEuros": 29.00,
"_links": {
"self": { "href": "/v1/comandes/com_5001" },
"seguiment": { "href": "/v1/comandes/com_5001/enviament" },
"factura": { "href": "/v1/comandes/com_5001/factura" },
"retornar": { "href": "/v1/comandes/com_5001/devolucio", "method": "POST" }
}
}Han desaparegut pagar i cancellar —ja no són possibles— i han aparegut seguiment, factura i retornar. El client no necessita conèixer la màquina d'estats de la comanda: n'hi ha prou que pinti els botons corresponents als enllaços que rep. Si demà la Botiga Aroma decideix que les comandes enviades també es poden reenviar com a regal, apareix un enllaç nou i els clients que el sàpiguen interpretar el mostraran sense desplegar cap versió nova.
graph TD
N0["<b>Nivell 0</b><br/>El pantà de POX<br/><i>un endpoint, tot POST</i>"] --> N1
N1["<b>Nivell 1</b><br/>Recursos<br/><i>cada cosa amb la seva URI</i>"] --> N2
N2["<b>Nivell 2</b><br/>Verbs i codis HTTP<br/><i>GET/POST/PUT/DELETE + 2xx/4xx/5xx</i>"] --> N3
N3["<b>Nivell 3</b><br/>Controls hipermèdia<br/><i>HATEOAS: enllaços que guien</i>"]
N2 -.- M["Aquí hi ha la majoria<br/>de les APIs professionals"]
- Els quatre nivells d'un cop d'ull
| Nivell 0 | Nivell 1 | Nivell 2 | Nivell 3 | |
|---|---|---|---|---|
| URI | Una de sola | Una per recurs | Una per recurs | Una per recurs |
| Mètodes | Només POST |
Només POST |
Tots, amb la seva semàntica | Tots, amb la seva semàntica |
| Codis d'estat | Sempre 200 |
Sempre 200 |
Els correctes | Els correctes |
| Memòria cau | Impossible | Impossible | Sí, en lectures | Sí, en lectures |
| Descobriment | Documentació | Documentació | Documentació | Documentació + enllaços |
| Acoblament del client | Molt alt | Alt | Mitjà | Baix |
| Cost d'implementació | Baix | Baix | Mitjà | Alt |
| Exemple típic | SOAP, APIs antigues | Reorganitzacions a mitges | La majoria d'APIs actuals | APIs de pagament, algunes públiques madures |
- Què és HATEOAS i quin problema resol
HATEOAS són les sigles d'Hypermedia As The Engine Of Application State: la hipermèdia com a motor de l'estat de l'aplicació. Traduït: el client avança per l'aplicació seguint els enllaços que el servidor li dona, igual que tu navegues per un web sense saber-te'n les URL de memòria.
L'analogia amb el navegador és la més aclaridora. Quan entres en una botiga en línia:
- No escrius a mà
botiga.example/cistella/afegir?producte=123. - Cliques a "Afegeix a la cistella", un enllaç o un formulari que la pàgina mateixa t'ha donat.
- Si el producte està exhaurit, aquest botó senzillament no hi apareix.
El navegador no sap res de cafès ni de cistelles: sap seguir enllaços i enviar formularis. HATEOAS pretén portar aquesta mateixa capacitat als clients programàtics.
El problema que resol és l'acoblament a les URL i a les regles de negoci:
| Sense HATEOAS | Amb HATEOAS |
|---|---|
| El client construeix les URL concatenant cadenes | El client segueix els enllaços rebuts |
| Canviar una URL trenca tots els clients | Les URL poden canviar sense trencar res |
| El client replica la màquina d'estats ("si estat == 'pendent_pagament', mostra pagar") | El servidor decideix i ho comunica amb enllaços |
| Afegir una operació exigeix desplegar el client | L'operació apareix com a enllaç nou |
- Formats hipermèdia: HAL, JSON:API i Siren
Si cada API s'inventa la seva manera d'expressar enllaços, se'n perd mig avantatge. Per això hi ha formats estandarditzats. Vegem la mateixa comanda de la Botiga Aroma en els dos més usats.
HAL (Hypertext Application Language)
És el més lleuger i el més adoptat. Hi afegeix dues convencions: _links per als enllaços i _embedded per als recursos incrustats. El seu tipus de contingut és application/hal+json.
GET /v1/comandes/com_5001 HTTP/1.1
Accept: application/hal+json
HTTP/1.1 200 OK
Content-Type: application/hal+json{
"id": "com_5001",
"estat": "pendent_pagament",
"totalEuros": 29.00,
"dataCreacio": "2026-08-14T09:12:44Z",
"_links": {
"self": { "href": "/v1/comandes/com_5001" },
"client": { "href": "/v1/clients/cli_842" },
"pagar": { "href": "/v1/comandes/com_5001/pagament" },
"cancellar": { "href": "/v1/comandes/com_5001" }
},
"_embedded": {
"linies": [
{
"cafeId": "caf_001",
"nom": "Etiòpia Yirgacheffe",
"quantitat": 2,
"preuEuros": 14.50,
"_links": { "cafe": { "href": "/v1/cafes/caf_001" } }
}
]
}
}Avantatge d'HAL: és una capa molt fina sobre el JSON que ja tenies. Limitació: els enllaços no diuen quin mètode cal fer servir ni quins camps cal enviar; això s'ha de documentar a part o estendre per convenció.
JSON:API
És un format molt més estricte i complet, amb especificació pròpia i tipus de contingut application/vnd.api+json. Estandarditza no només els enllaços, sinó l'estructura de dades, les relacions, la inclusió de recursos relacionats, la paginació, el filtratge i els errors.
{
"data": {
"type": "comandes",
"id": "com_5001",
"attributes": {
"estat": "pendent_pagament",
"totalEuros": 29.00,
"dataCreacio": "2026-08-14T09:12:44Z"
},
"relationships": {
"client": {
"data": { "type": "clients", "id": "cli_842" },
"links": { "related": "/v1/clients/cli_842" }
},
"linies": {
"links": { "related": "/v1/comandes/com_5001/linies" }
}
},
"links": {
"self": "/v1/comandes/com_5001",
"pagar": "/v1/comandes/com_5001/pagament"
}
},
"included": [
{
"type": "clients",
"id": "cli_842",
"attributes": { "nom": "Marta Garcia", "email": "[email protected]" }
}
]
}Diferències apreciables respecte a HAL: les dades van sota data amb type i id explícits, els atributs se separen de les relacions, i included permet enviar recursos relacionats complets a la mateixa resposta (cosa que ataca el mateix problema de "moltes peticions" que motiva GraphQL). El preu és la verbositat i una corba d'aprenentatge real.
Siren
Va un pas més enllà i modela accions amb tots els seus detalls: mètode, tipus de contingut i camps esperats.
{
"class": ["comanda"],
"properties": { "id": "com_5001", "estat": "pendent_pagament", "totalEuros": 29.00 },
"actions": [
{
"name": "pagar",
"title": "Pagar la comanda",
"method": "POST",
"href": "/v1/comandes/com_5001/pagament",
"type": "application/json",
"fields": [
{ "name": "metodePagament", "type": "text" },
{ "name": "targetaId", "type": "text" }
]
}
],
"links": [{ "rel": ["self"], "href": "/v1/comandes/com_5001" }]
}Amb Siren, un client genèric podria generar un formulari a partir de la resposta, igual que un navegador amb HTML. És el més fidel a l'esperit d'HATEOAS i el menys utilitzat a la pràctica.
Comparació
| Format | Verbositat | Estandarditza accions | Corba | Adopció |
|---|---|---|---|---|
| HAL | Baixa | No (només enllaços) | Suau | Alta |
| JSON:API | Alta | Parcialment | Mitjana-alta | Mitjana, amb bon ecosistema |
| Siren | Mitjana | Sí, amb camps | Mitjana | Baixa |
Hi ha, a més, una alternativa mínima sense format específic: la capçalera HTTP Link, estandarditzada a l'RFC 8288, molt usada per a la paginació:
És hipermèdia de baix cost i perfectament legítima. Hi tornarem en parlar de paginació a 02-06.
- El debat honest: per què gairebé ningú no arriba al nivell 3?
Fielding va ser taxatiu el 2008: una API sense controls hipermèdia no és REST. I tanmateix, gairebé cap API comercial que facis servir cada dia no implementa HATEOAS de manera plena. Val la pena entendre'n el motiu, sense caure ni en el dogmatisme ni en el menyspreu.
Arguments a favor d'HATEOAS:
- Desacobla el client de les URL, i permet reorganitzar-les sense trencar res.
- Centralitza la lògica de negoci al servidor: la màquina d'estats no es replica a cada client.
- Descobribilitat: un desenvolupador nou pot explorar l'API navegant des de l'arrel.
- És especialment valuós amb molts clients que no controles i fluxos amb estats complexos.
Arguments en contra, o si més no matisadors:
- Els clients reals no són genèrics. Aroma Mòbil té una pantalla dissenyada específicament per pagar una comanda; que l'enllaç
pagarhi sigui o no, no evita que l'app hagi de conèixer aquell flux, els seus camps i el seu disseny. - No existeix cap client universal. El navegador funciona perquè HTML defineix enllaços i formularis de manera estàndard i hi ha un humà interpretant-los. Amb JSON no hi ha equivalent: el client necessita saber que
rel: "pagar"vol dir pagar, cosa que reintrodueix acoblament semàntic. - Cost d'implementació i de manteniment a totes dues bandes: generar enllaços condicionats per estat i permisos no és trivial.
- Respostes més pesants, amb impacte en clients mòbils.
- Els equips consumidors solen ignorar els enllaços i continuen construint URL a mà, amb la qual cosa se'n paga el cost sense obtenir-ne el benefici.
- Eines escasses: comparat amb l'ecosistema d'OpenAPI, el suport per a hipermèdia és reduït.
La postura del sector, que és també la que adopta aquest curs:
- El nivell 2 és l'estàndard professional de facto. Una API al nivell 2 ben feta —URI netes, verbs correctes, codis d'estat significatius, memòria cau i errors ben modelats— és una API excel·lent.
- Afegir hipermèdia parcial és barat i rendible: un enllaç
self, enllaços de paginació i enllaços als recursos relacionats aporten valor real amb un cost mínim. És el que fa gairebé tothom, i és el que farem amb la Botiga Aroma. - HATEOAS complet es reserva per a casos amb fluxos d'estat rics i consumidors diversos: passarel·les de pagament, banca oberta, APIs governamentals de llarga vida.
- Criteris per decidir el teu nivell
Preguntes concretes per situar-te:
| Pregunta | Si la resposta és sí... |
|---|---|
| Controles tots els clients i els seus desplegaments? | El nivell 2 amb enllaços self és suficient |
| Els teus recursos tenen màquines d'estat riques (comandes, pagaments, expedients)? | Els enllaços per estat hi aporten molt |
| Tens desenes de consumidors externs que no controles? | Val la pena invertir en hipermèdia |
| Preveus reorganitzar les URL en el futur? | La hipermèdia protegeix aquesta evolució |
| Els teus clients són mòbils amb amplada de banda ajustada? | Compte amb el pes extra dels enllaços |
| Ja tens documentació OpenAPI ben mantinguda? | Cobreix bona part de la descobribilitat |
La decisió de la Botiga Aroma: nivell 2 sòlid amb hipermèdia selectiva. Concretament:
- Enllaç
selfa tots els recursos. - Enllaços de paginació a les col·leccions (capçalera
Link). - Enllaços als recursos relacionats (
client,cafe) per no obligar a compondre URL. - Enllaços d'acció condicionats a l'estat només a les comandes, que és on la màquina d'estats és real i on el panell intern i l'app en surten beneficiats.
És una decisió conscient, amb els motius escrits. Això és exactament el que s'espera d'un disseny professional.
Errors Comuns i Consells
- Tractar el model com un examen. Ningú no premia arribar al nivell 3. Es premia una API que funcioni bé i es pugui mantenir.
- Creure que ets al nivell 2 perquè fas servir
GETiPOST. Si retornes200 OKals errors o poses verbs a les URI, no hi ets. - Implementar
_linkssense criteri. Retornar sempre els mateixos enllaços, independentment de l'estat i dels permisos, és decoratiu: no aporta res i hi afegeix pes. - Inventar-se un format hipermèdia propi. Si ho has de fer, fes servir HAL o JSON:API: tindràs biblioteques, documentació i desenvolupadors que ja els coneixen.
- Barrejar formats. Fer servir
_linksd'HAL juntament amb l'estructuradata/attributesde JSON:API confon les eines i les persones. - Confondre HATEOAS amb "retornar URL absolutes". Un camp
urlImatgeno és un control hipermèdia; un enllaç amb una relació (rel) que expressa una transició possible, sí. - Consell: si comences avui, apunta a un nivell 2 impecable i afegeix-hi
selfi enllaços de paginació des del primer dia. Pujar després és fàcil; netejar una API mal dissenyada, no.
Exercicis
Exercici 1: diagnosticar el nivell
Per a cada API, indica en quin nivell de Richardson és i justifica-ho amb dues raons:
POST /serveiamb cos{"metode":"llistarCafes"}, resposta sempre200 OK.GET /v1/cafes/caf_001retorna200 OK;POST /v1/cafesretorna201 CreatedambLocation;GET /v1/cafes/caf_999retorna404 Not Found.POST /v1/cafes/cercar,POST /v1/cafes/crear,POST /v1/cafes/esborrar, totes amb200 OK.- Com la 2, però cada resposta inclou
_linksambself,ressenyesi, si hi ha estoc,afegirALaCistella.
Exercici 2: pujar un nivell
Aquesta operació de la Botiga Aroma és al nivell 1. Reescriu-la al nivell 2 complet (petició, codi d'estat i capçaleres rellevants), i explica cada decisió.
POST /v1/cistelles/cis_77 HTTP/1.1
Content-Type: application/json
{ "accio": "eliminarLinia", "cafeId": "caf_002" }Resposta actual: 200 OK amb {"exit": true}.
Exercici 3: dissenyar controls hipermèdia per estat
El recurs ressenya de la Botiga Aroma té tres estats: pendent_moderacio, publicada i rebutjada. Les regles són:
- Una ressenya pendent es pot aprovar o rebutjar (només un moderador), i el seu autor la pot editar.
- Una ressenya publicada pot ser resposta per l'equip de la botiga i el seu autor la pot esborrar.
- Una ressenya rebutjada no admet cap acció, però se'n pot veure el motiu.
Dissenya la resposta en format HAL per als tres estats, vista per un moderador.
Solucions
Solució 1
- Nivell 0. Un únic endpoint (
/servei), l'operació va al cos, tot perPOSTi sempre200. - Nivell 2. Hi ha URI per recurs, es fan servir els mètodes segons la seva semàntica i els codis d'estat són correctes (
201ambLocation,404quan no existeix). No hi ha enllaços, així que no és nivell 3. - Nivell 1. Hi ha URI sota
/v1/cafes, però el verb continua sent a la ruta i tot va perPOSTamb200: no s'aprofiten ni els mètodes ni els codis. - Nivell 3. Compleix tot el del nivell 2 i a més incorpora controls hipermèdia condicionats per l'estat (l'enllaç d'afegir a la cistella només apareix si hi ha estoc).
Solució 2
DELETE /v1/cistelles/cis_77/linies/caf_002 HTTP/1.1
Host: api.botigaaroma.example
Authorization: Bearer TOKENDecisions:
- El verb passa al mètode HTTP: eliminar és
DELETE, no un campaccioal cos. - La línia de la cistella es converteix en un recurs propi amb URI identificable (
/v1/cistelles/cis_77/linies/caf_002), cosa que també permet consultar-la o modificar-ne la quantitat ambPATCH. DELETEés idempotent: si la petició es reintenta després d'una fallada de xarxa, el resultat és el mateix. Amb elPOSTanterior no hi havia aquesta garantia.204 No Contentindica èxit sense cos, en comptes d'un{"exit": true}redundant. Si volguéssim retornar la cistella actualitzada per estalviar una petició al client,200 OKamb la cistella completa també seria correcte: és una decisió de disseny legítima.- Si la línia no existeix, es respon
404 Not Found; si la cistella és d'un altre client,403 Forbidden.
Solució 3
Estat pendent_moderacio (vista de moderador):
{
"id": "res_101",
"cafeId": "caf_001",
"autor": "Marta G.",
"puntuacio": 5,
"comentari": "Equilibrat i dolç.",
"estat": "pendent_moderacio",
"_links": {
"self": { "href": "/v1/ressenyes/res_101" },
"cafe": { "href": "/v1/cafes/caf_001" },
"aprovar": { "href": "/v1/ressenyes/res_101/aprovacio", "method": "POST" },
"rebutjar": { "href": "/v1/ressenyes/res_101/rebuig", "method": "POST" }
}
}Estat publicada:
{
"id": "res_101",
"estat": "publicada",
"dataPublicacio": "2026-08-14T10:02:00Z",
"_links": {
"self": { "href": "/v1/ressenyes/res_101" },
"cafe": { "href": "/v1/cafes/caf_001" },
"respondre":{ "href": "/v1/ressenyes/res_101/respostes", "method": "POST" }
}
}Estat rebutjada:
{
"id": "res_101",
"estat": "rebutjada",
"motiuRebuig": "Conté llenguatge ofensiu",
"_links": {
"self": { "href": "/v1/ressenyes/res_101" },
"cafe": { "href": "/v1/cafes/caf_001" }
}
}Punts clau de la solució:
- Els enllaços canvien amb l'estat: aquí hi ha el valor d'HATEOAS. El panell de moderació pot pintar els seus botons a partir dels enllaços sense conèixer la màquina d'estats.
- Els enllaços també depenen de qui pregunta: l'autor hi veuria
editariesborraren comptes d'aprovarirebutjar. Una resposta hipermèdia reflecteix permisos, no només estat. - Les accions es modelen com a subrecursos (
/aprovacio,/rebuig,/respostes) als quals es faPOST, i s'eviten així els verbs a les URI. - L'enllaç
selfhi és sempre, imotiuRebuignomés apareix quan té sentit.
Conclusió
El model de maduresa de Richardson ofereix un vocabulari precís per parlar de com de RESTful és una API: del nivell 0 —un endpoint, tot POST, HTTP com a simple túnel— al nivell 3, on les respostes inclouen els controls hipermèdia que guien el client. Hem vist la mateixa operació de la Botiga Aroma reescrita en els quatre nivells i hem comprovat que el salt decisiu és el nivell 2: recursos amb URI pròpia, verbs amb la seva semàntica i codis d'estat correctes, que és on s'obtenen memòria cau, idempotència i monitoratge de franc. HATEOAS aporta desacoblament real i centralitza la màquina d'estats, però té un cost que molts equips no rendibilitzen; per això la Botiga Aroma adoptarà un nivell 2 sòlid amb hipermèdia selectiva, i amb els motius escrits.
Amb el model REST ja comprès a fons, toca situar-lo davant de les alternatives. A la lliçó següent, REST vs. SOAP, veurem de prop l'estil que va dominar els serveis web empresarials: el seu sobre XML, el seu contracte WSDL, la seva pila WS-*, i compararem tots dos enfocaments costat a costat sobre el mateix cas de la Botiga Aroma per entendre quan cadascun continua tenint sentit avui.
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
