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

  1. Per a què serveix el model de maduresa
  2. Nivell 0: el pantà de POX
  3. Nivell 1: recursos
  4. Nivell 2: verbs HTTP i codis d'estat
  5. Nivell 3: controls hipermèdia (HATEOAS)
  6. Els quatre nivells d'un cop d'ull
  7. Què és HATEOAS i quin problema resol
  8. Formats hipermèdia: HAL, JSON:API i Siren
  9. El debat honest: per què gairebé ningú no arriba al nivell 3?
  10. Criteris per decidir el teu nivell

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

  1. No és de Fielding ni és normatiu. És un model descriptiu, útil per diagnosticar, no un examen que s'hagi d'aprovar.
  2. Pujar de nivell no és automàticament millor. Cada nivell té un cost. L'objectiu és triar amb criteri, no maximitzar la puntuació.

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

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

POST /v1/comandes/com_5001 HTTP/1.1
Content-Type: application/json

{ "accio": "obtenir" }

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 POST per 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í.

  1. 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 Created en comptes de 200 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 Content

I 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, PUT i DELETE: 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.

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

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

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

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

Link: </v1/cafes?pagina=3>; rel="next", </v1/cafes?pagina=1>; rel="first"

És hipermèdia de baix cost i perfectament legítima. Hi tornarem en parlar de paginació a 02-06.

  1. 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ç pagar hi 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.

  1. 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ç self a 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 GET i POST. Si retornes 200 OK als errors o poses verbs a les URI, no hi ets.
  • Implementar _links sense 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 _links d'HAL juntament amb l'estructura data/attributes de JSON:API confon les eines i les persones.
  • Confondre HATEOAS amb "retornar URL absolutes". Un camp urlImatge no é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 self i 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:

  1. POST /servei amb cos {"metode":"llistarCafes"}, resposta sempre 200 OK.
  2. GET /v1/cafes/caf_001 retorna 200 OK; POST /v1/cafes retorna 201 Created amb Location; GET /v1/cafes/caf_999 retorna 404 Not Found.
  3. POST /v1/cafes/cercar, POST /v1/cafes/crear, POST /v1/cafes/esborrar, totes amb 200 OK.
  4. Com la 2, però cada resposta inclou _links amb self, ressenyes i, 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

  1. Nivell 0. Un únic endpoint (/servei), l'operació va al cos, tot per POST i sempre 200.
  2. 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 (201 amb Location, 404 quan no existeix). No hi ha enllaços, així que no és nivell 3.
  3. Nivell 1. Hi ha URI sota /v1/cafes, però el verb continua sent a la ruta i tot va per POST amb 200: no s'aprofiten ni els mètodes ni els codis.
  4. 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 TOKEN
HTTP/1.1 204 No Content

Decisions:

  • El verb passa al mètode HTTP: eliminar és DELETE, no un camp accio al 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 amb PATCH.
  • DELETE és idempotent: si la petició es reintenta després d'una fallada de xarxa, el resultat és el mateix. Amb el POST anterior no hi havia aquesta garantia.
  • 204 No Content indica èxit sense cos, en comptes d'un {"exit": true} redundant. Si volguéssim retornar la cistella actualitzada per estalviar una petició al client, 200 OK amb 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 editar i esborrar en comptes d'aprovar i rebutjar. Una resposta hipermèdia reflecteix permisos, no només estat.
  • Les accions es modelen com a subrecursos (/aprovacio, /rebuig, /respostes) als quals es fa POST, i s'eviten així els verbs a les URI.
  • L'enllaç self hi és sempre, i motiuRebuig nomé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

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