Ja tenim el mapa de recursos i les seves URIs. Ara cal dir què es pot fer amb cadascun, i en REST això ho diu el mètode HTTP. Aquesta lliçó recorre en profunditat GET, POST, PUT, PATCH, DELETE, HEAD i OPTIONS aplicats a la Botiga Aroma, amb petició i resposta completes de cadascun. Després entra en els dos conceptes que separen una API que aguanta la realitat d'una que es trenca en producció: la seguretat (safety) i la idempotència. No són subtileses acadèmiques: són la diferència entre que RàpidEnviaments reintenti una petició sense conseqüències o que un client acabi pagant dues vegades la mateixa comanda. Tancarem amb PUT davant de PATCH a fons, l'esborrat lògic, les claus d'idempotència i les operacions en lot.
Contingut
- Els mètodes d'una ullada
- GET: llegir sense efectes
- POST: crear i executar accions
- PUT: substitució total
- PATCH: modificació parcial
- PUT davant de PATCH, i què tria la Botiga Aroma
- DELETE: esborrat físic i lògic
- HEAD i OPTIONS
- Seguretat i idempotència
- Claus d'idempotència per al pagament
- Operacions en lot
- Els mètodes d'una ullada
| Mètode | Què significa | Cos a la petició? | Cos a la resposta? | Segur | Idempotent |
|---|---|---|---|---|---|
GET |
Obtenir la representació | No | Sí | Sí | Sí |
HEAD |
Com GET, només capçaleres | No | No | Sí | Sí |
OPTIONS |
Què es pot fer aquí | No | Opcional | Sí | Sí |
POST |
Crear subordinat o executar acció | Sí | Sí | No | No |
PUT |
Substituir del tot | Sí | Sí (o 204) | No | Sí |
PATCH |
Modificar parcialment | Sí | Sí (o 204) | No | Depèn |
DELETE |
Eliminar | No (normalment) | Opcional (o 204) | No | Sí |
Existeixen dos mètodes més i aquí només els esmentem: TRACE (eco de la petició, es deshabilita per seguretat) i CONNECT (túnels de proxy). Cap API REST no els fa servir.
Si un client fa servir un mètode que el recurs no admet, la resposta correcta és 405 Method Not Allowed amb la capçalera Allow; si el servidor no coneix el mètode en absolut, és 501 Not Implemented. El detall dels codis és la lliçó següent.
- GET: llegir sense efectes
GET obté la representació d'un recurs. És el mètode més utilitzat de qualsevol API i l'únic que es pot posar a la memòria cau amb garanties (04-06).
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Language: ca
Cache-Control: public, max-age=300
{
"id": "caf_001",
"nom": "Etiòpia Yirgacheffe",
"origen": "Etiòpia",
"torrefaccio": "clar",
"preuEuros": 14.50,
"estoc": 120,
"notesTast": ["cítric", "floral", "te negre"],
"dataCreacio": "2026-01-15T08:30:00Z",
"_links": {
"self": { "href": "/v1/cafes/caf_001" },
"ressenyes": { "href": "/v1/cafes/caf_001/ressenyes" }
}
}Regles de disseny per a GET a la Botiga Aroma:
- Mai no modifica res. Ni tan sols un comptador "discret" de visites: els prefetchers dels navegadors, els rastrejadors i els proxys fan
GETpel seu compte. Si necessites registrar la visita, fes-ho fora de la semàntica del recurs o amb unPOSTexplícit. - No porta cos. Tècnicament HTTP no ho prohibeix, però molts intermediaris el descarten i alguns clients ni tan sols l'envien. Consultes complexes per cos →
POST(02-06). - Un
GETsobre una col·lecció buida és200amb{"dades": [], "total": 0}, no404. La col·lecció existeix encara que no tingui elements. - Un
GETsobre un element inexistent és404amb l'error de negoci corresponent:
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": {
"codi": "cafe_no_trobat",
"missatge": "No existeix cap cafè amb l'identificador 'caf_999'.",
"detalls": []
}
}
- POST: crear i executar accions
POST és el mètode de propòsit general: crea un recurs subordinat a la col·lecció o executa l'acció que representa un subrecurs (02-02).
3.1. Crear un element en una col·lecció
curl -i -X POST "https://api.botigaaroma.example/v1/cafes" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"nom": "Colòmbia Huila",
"origen": "Colòmbia",
"torrefaccio": "mitja",
"preuEuros": 12.90,
"estoc": 80,
"notesTast": ["xocolata", "caramel", "taronja"]
}'HTTP/1.1 201 Created
Content-Type: application/json
Location: https://api.botigaaroma.example/v1/cafes/caf_002
{
"id": "caf_002",
"nom": "Colòmbia Huila",
"origen": "Colòmbia",
"torrefaccio": "mitja",
"preuEuros": 12.90,
"estoc": 80,
"notesTast": ["xocolata", "caramel", "taronja"],
"dataCreacio": "2026-03-14T09:12:00Z",
"_links": { "self": { "href": "/v1/cafes/caf_002" } }
}Tres punts del contracte:
- L'identificador l'assigna el servidor. El client no envia
id; si l'envia, es rebutja amb400. Locationés obligatòria en tota creació. Conté la URI del recurs creat. És el que permet a un client encadenar operacions sense endevinar URLs.- Es retorna el recurs complet al cos, no només l'id: estalvia un
GETimmediat i mostra els camps calculats pel servidor (dataCreacio).
3.2. Executar una acció
curl -i -X POST "https://api.botigaaroma.example/v1/comandes/com_5001/pagament" \
-H "Authorization: Bearer <token>" \
-H "Idempotency-Key: 5f3b9c2a-1d7e-4a44-9f30-8b1c2d3e4f50" \
-H "Content-Type: application/json" \
-d '{ "metode": "targeta", "tokenTargeta": "tok_visa_4242" }'HTTP/1.1 201 Created
Content-Type: application/json
Location: https://api.botigaaroma.example/v1/comandes/com_5001/pagament
{
"estat": "pagat",
"importEuros": 29.00,
"metode": "targeta",
"referencia": "pay_7712",
"dataPagament": "2026-03-14T10:32:00Z",
"_links": {
"self": { "href": "/v1/comandes/com_5001/pagament" },
"comanda": { "href": "/v1/comandes/com_5001" },
"factura": { "href": "/v1/comandes/com_5001/factura" }
}
}3.3. POST no és idempotent
És la característica que defineix POST i la font de la majoria dels incidents reals:
# Executat dues vegades, crea DOS cafès diferents
curl -X POST .../v1/cafes -d '{"nom": "Colòmbia Huila", ...}' # → caf_002
curl -X POST .../v1/cafes -d '{"nom": "Colòmbia Huila", ...}' # → caf_003Quan això sigui inacceptable (pagaments, comandes), hi ha dos remeis: la capçalera Idempotency-Key (secció 10) o retornar 409 Conflict si detectes un duplicat de negoci. La Botiga Aroma fa servir totes dues coses.
- PUT: substitució total
PUT diu: "en aquesta URI hi ha de quedar exactament aquesta representació". És una substitució completa, no una fusió.
curl -i -X PUT "https://api.botigaaroma.example/v1/cafes/caf_001" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"nom": "Etiòpia Yirgacheffe",
"origen": "Etiòpia",
"torrefaccio": "clar",
"preuEuros": 15.20,
"estoc": 120,
"notesTast": ["cítric", "floral", "te negre"]
}'HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "caf_001",
"nom": "Etiòpia Yirgacheffe",
"origen": "Etiòpia",
"torrefaccio": "clar",
"preuEuros": 15.20,
"estoc": 120,
"notesTast": ["cítric", "floral", "te negre"],
"dataCreacio": "2026-01-15T08:30:00Z"
}El perill de PUT: si el client vol apujar el preu i envia només {"preuEuros": 15.20}, un PUT correcte deixa el cafè sense nom, sense origen, sense torrefacció, sense estoc i sense notes de tast. És l'error de principiant més car d'aquesta lliçó. La Botiga Aroma ho mitiga rebutjant amb 400 els PUT als quals els falten camps obligatoris, però la semàntica continua sent "substitueix-ho tot".
PUT sí que és idempotent: enviar-lo deu vegades deixa el recurs exactament igual que enviar-lo una vegada. Per això és el mètode que fa servir RàpidEnviaments per actualitzar l'enviament, els reintents del qual són freqüents:
PUT /v1/comandes/com_5001/enviament HTTP/1.1
Content-Type: application/json
Authorization: Bearer <token de RàpidEnviaments>
{
"estat": "en_repartiment",
"codiSeguiment": "RE-9981234",
"dataEstimada": "2026-03-16T12:00:00Z"
}Un detall poc conegut: PUT també pot crear un recurs si el client coneix la URI per endavant. La Botiga Aroma ho fa exactament en un lloc, les línies de cistella:
Si la línia no existia es crea (201 Created); si existia se substitueix la quantitat (200 OK). I és idempotent: prémer tres vegades "posar 3 unitats" deixa 3 unitats, no 9. Compara-ho amb POST /linies amb {"cafeId": "caf_002", "quantitat": 1}, que suma una unitat cada vegada que s'executa: totes dues operacions tenen sentit, però signifiquen coses diferents i convé tenir-ho escrit a la documentació.
- PATCH: modificació parcial
PATCH envia només allò que canvia. La pregunta interessant és en quin format, perquè PATCH no en defineix cap: el defineix el Content-Type.
5.1. JSON Merge Patch (RFC 7386)
El document enviat es fusiona amb el recurs. Un valor null significa "esborra aquest camp".
PATCH /v1/cafes/caf_001 HTTP/1.1
Content-Type: application/merge-patch+json
{
"preuEuros": 15.20,
"estoc": 95
}Resultat: es canvien preuEuros i estoc; tota la resta es queda com estava.
Resultat: s'elimina el camp notesTast.
La limitació de Merge Patch són els arrays: se substitueixen sencers, mai no s'editen per posició. Per afegir una nota de tast cal enviar la llista completa:
I el corol·lari incòmode: amb Merge Patch no es pot posar un camp a null de debò, perquè null està reservat per a "esborrar".
5.2. JSON Patch (RFC 6902)
És una llista d'operacions sobre el document, expressades amb JSON Pointer.
PATCH /v1/cafes/caf_001 HTTP/1.1
Content-Type: application/json-patch+json
[
{ "op": "replace", "path": "/preuEuros", "value": 15.20 },
{ "op": "add", "path": "/notesTast/-", "value": "bergamota" },
{ "op": "remove", "path": "/notesTast/0" },
{ "op": "test", "path": "/estoc", "value": 120 }
]Les operacions són add, remove, replace, move, copy i test. path fa servir JSON Pointer (/notesTast/- significa "al final de l'array"). Si qualsevol operació falla, no se n'aplica cap: és atòmic.
L'operació test és la joia amagada: "aplica això només si estoc continua valent 120". És control de concurrència optimista dins del mateix cos, complementari al que es fa amb If-Match i ETags (04-06).
5.3. Comparació
| Criteri | JSON Merge Patch (7386) | JSON Patch (6902) |
|---|---|---|
Content-Type |
application/merge-patch+json |
application/json-patch+json |
| Llegibilitat | Molt alta: sembla el recurs | Baixa: cal llegir operacions |
| Editar un element d'array | No (se substitueix l'array) | Sí, per índex |
Posar un camp a null |
Impossible (null = esborrar) |
Sí (replace amb value: null) |
| Condicions prèvies | No | Sí, amb test |
| Reordenar, moure, copiar | No | Sí |
| Idempotència | Sí, sempre | No sempre (add a /array/- acumula) |
| Facilitat per al client | Molt alta | Mitjana |
| Adopció | Majoritària | Nínxols (documents complexos, Kubernetes) |
5.4. La decisió de la Botiga Aroma
JSON Merge Patch com a format oficial, amb Content-Type: application/merge-patch+json. Raons: els recursos són plans i petits, els clients són majoritàriament frontends que ja tenen l'objecte a la memòria, la llegibilitat de les peticions facilita el suport, i el 100 % dels casos d'ús reals són "canviar dos o tres camps". Els arrays de la Botiga Aroma (notesTast, linies) són curts i substituir-los sencers no és cap problema.
A més, s'accepta application/json a seques i es tracta com a Merge Patch, perquè és el que envien moltes eines per defecte; un Content-Type diferent d'aquests dos es rebutja amb 415 Unsupported Media Type. Que l'API no admet JSON Patch queda escrit a la documentació, perquè ningú no ho intenti.
- PUT davant de PATCH, i què tria la Botiga Aroma
| Criteri | PUT |
PATCH |
|---|---|---|
| Semàntica | Substitueix el recurs complet | Aplica canvis parcials |
| Camps absents | S'esborren o es posen per defecte | Es mantenen |
| Mida del cos | Tot el recurs | Només allò que canvia |
| Idempotent | Sempre | Amb Merge Patch, sí |
| Risc de trepitjar canvis d'un altre | Alt: envies camps que no volies tocar | Baix: només toques allò teu |
| Pot crear el recurs | Sí | No (404 si no existeix) |
| Ús típic | Formularis complets, sincronització | Edicions puntuals |
Assignació al mapa de la Botiga Aroma:
| Recurs | Mètode d'actualització | Motiu |
|---|---|---|
/cafes/{id} |
PUT i PATCH |
El panell edita fitxes completes; els ajustos de preu i estoc són parcials |
/clients/{id} |
Només PATCH |
Mai no se substitueix un client sencer: hi ha camps que el client no veu |
/clients/{id}/preferencies |
Només PUT |
Són quatre camps i el formulari els envia tots |
/cistelles/{id}/linies/{cafeId} |
Només PUT |
Fixar la quantitat ha de ser idempotent |
/comandes/{id} |
Només PATCH |
Només hi ha camps molt concrets editables (adreça abans de l'enviament) |
/comandes/{id}/enviament |
Només PUT |
RàpidEnviaments envia l'estat complet i reintenta |
/ressenyes/{id} |
Només PATCH |
Es corregeix el comentari; l'estat es canvia amb /aprovacio o /rebuig |
- DELETE: esborrat físic i lògic
curl -i -X DELETE "https://api.botigaaroma.example/v1/cistelles/cis_77/linies/caf_002" \
-H "Authorization: Bearer <token>"7.1. Físic davant de lògic
| Esborrat físic | Esborrat lògic (soft delete) | |
|---|---|---|
| Què fa | Elimina la fila | Marca eliminat = true i l'amaga |
| Reversible | No | Sí |
| Auditoria i històric | Es perden | Es conserven |
| Integritat referencial | Pot trencar comandes antigues | Intacta |
| Cost | Cap | Filtrar a totes les consultes, creixement de taules |
La Botiga Aroma fa servir esborrat lògic per als cafès (una comanda del 2025 ha de poder continuar mostrant el cafè que es va vendre) i per als clients (obligacions fiscals), i esborrat físic per a les línies de cistella (dades efímeres sense valor històric). Les comandes no s'esborren mai: s'anul·len amb POST /comandes/{id}/anullacio.
Punt clau: l'esborrat lògic és invisible al contracte. Des de fora, després d'un DELETE /v1/cafes/caf_001, el cafè ja no apareix a /v1/cafes i GET /v1/cafes/caf_001 respon 404 (o 410, veure més avall). Que per dins la fila continuï allà és cosa de la capa de persistència (03-05).
7.2. I si esborro dues vegades?
Aquí hi ha un debat clàssic. Segon DELETE sobre un recurs ja esborrat:
404 Not Found: literal. El recurs no existeix ara, i és la resposta més comuna.204 No Content: pragmàtica. L'objectiu del client ("que no existeixi") ja es compleix.
Cap de les dues no trenca la idempotència, i convé entendre bé per què: idempotència significa que l'efecte sobre el servidor és el mateix després d'una o N peticions, no que el codi de resposta sigui idèntic. Després d'un o cinc DELETE, el recurs està esborrat: això és idempotent.
Decisió de la Botiga Aroma: 404, perquè distingeix "l'he esborrat jo ara" de "això ja no hi era", informació útil per depurar clients amb reintents. I per als cafès retirats del catàleg definitivament es fa servir 410 Gone, que diu "va existir i no tornarà" (02-04).
DELETE no ha de portar cos de petició. Si necessites paràmetres per esborrar (un motiu, una data efectiva), és senyal que estàs davant d'una acció, i les accions es modelen com a subrecurs amb POST (02-02).
- HEAD i OPTIONS
8.1. HEAD
Idèntic a GET, però el servidor retorna només les capçaleres. Serveix per comprovar existència, mida o frescor sense descarregar el cos.
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Length: 48213
Last-Modified: Sat, 14 Mar 2026 10:33:00 GMTAroma Mòbil el fa servir per saber si val la pena descarregar una factura de 48 KB amb la connexió actual. La regla d'or: HEAD ha de retornar exactament les mateixes capçaleres que retornaria GET; si el teu HEAD retorna Content-Length: 0, està mal implementat.
8.2. OPTIONS
Pregunta què es pot fer amb un recurs. La resposta obligatòria és la capçalera Allow.
HTTP/1.1 204 No Content
Allow: GET, HEAD, PUT, PATCH, DELETE, OPTIONS
Accept-Patch: application/merge-patch+jsonAccept-Patch és la manera estàndard d'anunciar quins formats de PATCH accepta el recurs: aquí queda publicada, al mateix protocol, la decisió de la secció 5.4.
L'ús massiu d'OPTIONS a la pràctica no el fan les persones, sinó els navegadors: és la petició de comprovació prèvia (preflight) de CORS, que la SPA de la Botiga Aroma dispara abans de cada PATCH o DELETE amb capçaleres personalitzades. Aquest mecanisme complet s'estudia a 04-05.
- Seguretat i idempotència
Les dues propietats més importants d'aquesta lliçó.
- Segur (safe): el mètode no modifica l'estat del servidor. És una lectura. Qualsevol intermediari el pot repetir, precarregar o posar a la memòria cau sense demanar permís.
- Idempotent: executar-lo N vegades deixa el servidor en el mateix estat que executar-lo una vegada. Compte: el mateix estat, no la mateixa resposta.
graph TD
A["El client envia POST /comandes/com_5001/pagament"] --> B["El servidor la rep<br/>i cobra 29,00 €"]
B --> C["La resposta es perd:<br/>timeout de xarxa"]
C --> D{"El client reintenta?"}
D -->|"Sense Idempotency-Key"| E["Segon cobrament de 29,00 €<br/><b>client cobrat dues vegades</b>"]
D -->|"Amb Idempotency-Key"| F["El servidor reconeix la clau<br/>i retorna la resposta original<br/><b>un sol cobrament</b>"]
Tot mètode segur és idempotent; el contrari no és cert (DELETE és idempotent però no segur).
Per què importa de debò, amb els tres escenaris de la Botiga Aroma:
- Reintents de RàpidEnviaments. El seu client HTTP reintenta automàticament davant d'un
503o d'un timeout. Com que actualitza l'enviament ambPUT(idempotent), tres reintents deixen el mateix enviament. Si ho haguéssim modelat com aPOST /comandes/{id}/esdeveniments-enviament, tindríem tres esdeveniments duplicats. - Timeouts de xarxa al mòbil. Aroma Mòbil perd cobertura just després d'enviar la petició. El client no sap si el servidor la va processar. Només pot reintentar sense por si el mètode és idempotent.
- Doble clic a "pagar". El cas més humà de tots.
POSTno és idempotent, així que el remei no pot venir del mètode: ve de la secció següent.
Conseqüència de disseny: com més idempotent sigui la teva API, més barat és operar-la, perquè els reintents automàtics (del client, del balancejador, de la passarel·la) deixen de ser perillosos.
- Claus d'idempotència per al pagament
Una clau d'idempotència és un identificador únic que el client genera abans d'enviar la petició i repeteix a cada reintent d'aquesta mateixa operació lògica.
Flux complet de /comandes/com_5001/pagament:
# El client genera un UUID i el desa ABANS d'enviar res
CLAU="5f3b9c2a-1d7e-4a44-9f30-8b1c2d3e4f50"
curl -i -X POST "https://api.botigaaroma.example/v1/comandes/com_5001/pagament" \
-H "Authorization: Bearer <token>" \
-H "Idempotency-Key: $CLAU" \
-H "Content-Type: application/json" \
-d '{ "metode": "targeta", "tokenTargeta": "tok_visa_4242" }'Què fa el servidor:
- Busca la clau. Si no existeix, la registra juntament amb una empremta del cos, processa el pagament i desa la resposta durant 24 hores.
- Si existeix i el cos coincideix: no torna a cobrar; retorna la resposta desada, amb
Idempotent-Replay: trueperquè el client sàpiga que és una repetició. - Si existeix i el cos és diferent: respon
422 Unprocessable Contentamb codiclau_idempotencia_reutilitzada. És una protecció contra errors del client: la mateixa clau no pot significar dues operacions diferents. - Si la petició original encara s'està processant: respon
409 Conflictamboperacio_en_cursiRetry-After: 2.
Resposta del reintent:
HTTP/1.1 201 Created
Content-Type: application/json
Location: https://api.botigaaroma.example/v1/comandes/com_5001/pagament
Idempotent-Replay: true
{
"estat": "pagat",
"importEuros": 29.00,
"referencia": "pay_7712",
"dataPagament": "2026-03-14T10:32:00Z"
}Contracte de la Botiga Aroma sobre Idempotency-Key:
| Endpoint | Clau? | Comportament sense clau |
|---|---|---|
POST /comandes |
Obligatòria | 400 amb clau_idempotencia_requerida |
POST /comandes/{id}/pagament |
Obligatòria | 400 amb clau_idempotencia_requerida |
POST /comandes/{id}/anullacio |
Recomanada | Es processa; si ja està anul·lada, 409 comanda_ja_anullada |
POST /cafes |
Opcional | Es processa (podria crear duplicats) |
POST /cafes/{id}/ressenyes |
Opcional | Es processa |
I una segona línia de defensa que no depèn del client: el mateix recurs protegeix la seva transició. Un segon pagament de la mateixa comanda, encara que vingui amb una altra clau, troba la comanda en estat pagat i respon 409 Conflict amb comanda_ja_pagada. La idempotència protegeix dels reintents tècnics; la màquina d'estats protegeix dels errors lògics. Calen totes dues.
- Operacions en lot
Tard o d'hora algú demanarà "actualitzar l'estoc de 200 cafès de cop". Les opcions:
a) N peticions individuals. Semànticament perfecte, fàcil de posar a la memòria cau i de reintentar. Amb HTTP/2 i connexions multiplexades, 200 PATCH petits són molt més viables del que la gent suposa.
b) Un endpoint de lot.
POST /v1/cafes/actualitzacions-lot HTTP/1.1
Content-Type: application/json
{
"operacions": [
{ "id": "caf_001", "canvis": { "estoc": 95 } },
{ "id": "caf_002", "canvis": { "estoc": 0 } },
{ "id": "caf_999", "canvis": { "estoc": 10 } }
]
}I aquí apareix el problema fonamental del lot: quin codi retornes si una de les tres falla? No és 200, perquè alguna cosa ha fallat. No és 400, perquè dues han funcionat. La resposta habitual és 207 Multi-Status, un codi que ve de WebDAV, amb el detall per element:
HTTP/1.1 207 Multi-Status
Content-Type: application/json
{
"dades": [
{ "id": "caf_001", "estat": 200 },
{ "id": "caf_002", "estat": 200 },
{ "id": "caf_999", "estat": 404,
"error": { "codi": "cafe_no_trobat", "missatge": "No existeix 'caf_999'.", "detalls": [] } }
],
"total": 3
}Riscos que cal tenir presents abans d'acceptar un endpoint de lot:
- Atomicitat ambigua: és tot o res, o parcial? Cal decidir-ho i documentar-ho; totes dues opcions són defensables i la confusió és la que fa mal.
- Es perd la memòria cau i el
Location: és unPOSTopac a un endpoint artificial. - Idempotència complicada: reintentar el lot després d'una fallada parcial requereix clau d'idempotència i saber què s'ha aplicat.
- Timeouts i límits: un lot de 10.000 elements tomba la petició; cal fixar un màxim (Botiga Aroma: 100 operacions) i respondre
413si se supera. - Verb encobert:
actualitzacions-lotés un recurs inventat que no existeix al domini. És una concessió conscient, no un patró per estendre.
Decisió de la Botiga Aroma: no hi ha endpoints de lot a la v1. El panell intern actualitza en paral·lel amb peticions individuals. Si el volum ho exigeix, s'afegirà un sol endpoint de lot per a l'estoc, amb les regles anteriors escrites a la guia d'estil.
Errors Comuns i Consells
- Fer servir
GETper a operacions que modifiquen.GET /esborrar?id=res_101és un desastre esperant un rastrejador. Mai. - Fer servir
POSTper a tot. Funciona, però renuncies a la memòria cau, als reintents segurs i a la meitat de la semàntica d'HTTP: és el nivell 1 de Richardson que ja vam rebutjar a 01-05. - Enviar un
PUTparcial. L'error clàssic que esborra mig recurs. Si has d'enviar tres camps, fes servirPATCH. - Retornar
200en crear. La creació és201ambLocation. Un200obliga el client a rebuscar l'id al cos. - Implementar
PATCHsense decidir el format. SenseContent-Typeexplícit, cada client suposarà una cosa diferent. Documentaapplication/merge-patch+jsoni rebutja la resta amb415. - Creure que idempotència és "retorna el mateix". És "deixa el servidor igual". Un segon
DELETEpot respondre404i continuar sent idempotent. - Posar cos a
GETo aDELETE. Alguns intermediaris el descarten silenciosament i depurar-ho és un malson. - Consell: pregunta't sempre "què passa si això s'envia dues vegades?". Aplica-ho a cada endpoint nou abans de donar-lo per tancat. És la pregunta que més incidents evita.
- Consell: la clau d'idempotència la genera el client abans d'enviar, no després. Si es genera al reintent, ja no serveix de res.
Exercicis
Exercici 1: triar el mètode
Indica mètode, URI i per què, per a cada necessitat de la Botiga Aroma:
- Aroma Mòbil vol saber si la factura de
com_5001ja està disponible, sense descarregar-la. - El panell corregeix una errada al nom de
caf_002. - La SPA fixa a 3 unitats la quantitat de
caf_002a la cistellacis_77. - Un moderador rebutja la ressenya
res_102indicant-ne el motiu. - RàpidEnviaments comunica que
com_5001ha sortit a repartiment. - El panell retira
caf_001del catàleg conservant-ne l'històric. - Un client confirma la seva cistella i crea una comanda.
Exercici 2: PUT davant de PATCH
Aquest és l'estat actual de caf_001:
{
"id": "caf_001",
"nom": "Etiòpia Yirgacheffe",
"origen": "Etiòpia",
"torrefaccio": "clar",
"preuEuros": 14.50,
"estoc": 120,
"notesTast": ["cítric", "floral", "te negre"]
}a) Què queda després d'un PUT /v1/cafes/caf_001 amb cos {"preuEuros": 15.20}, si el servidor no valida camps obligatoris?
b) Escriu el PATCH amb Merge Patch que apugi el preu a 15,20 € i abaixi l'estoc a 95.
c) Escriu el PATCH amb Merge Patch que elimini el camp notesTast.
d) Escriu el JSON Patch que afegeixi la nota "bergamota" només si l'estoc continua sent 120, i explica per què això no es pot fer amb Merge Patch.
Exercici 3: dissenyar la idempotència d'una devolució
La Botiga Aroma afegeix POST /v1/comandes/{id}/devolucio, que genera una etiqueta de retorn i reemborsa l'import. Dissenya'n el comportament responent:
- Ha d'exigir
Idempotency-Key? Per què? - Què passa si arriba dues vegades la mateixa clau amb el mateix cos?
- Què passa si arriba una devolució d'una comanda que encara no s'ha enviat?
- Què passa si arriba una segona devolució, amb clau diferent, d'una comanda ja retornada?
- Seria idempotent modelar-ho com a
PUT /v1/comandes/{id}/devolucio? Què s'hi guanyaria i què s'hi perdria?
Solucions
Solució 1
| # | Mètode i URI | Justificació |
|---|---|---|
| 1 | HEAD /v1/comandes/com_5001/factura |
Comprova existència i mida sense gastar dades: exactament per a això existeix HEAD |
| 2 | PATCH /v1/cafes/caf_002 amb {"nom": "..."} |
Canvi parcial; amb PUT caldria reenviar tota la fitxa i arriscar-se a trepitjar altres camps |
| 3 | PUT /v1/cistelles/cis_77/linies/caf_002 amb {"quantitat": 3} |
"Fixar la quantitat" és substitució i idempotent; amb POST se sumaria cada vegada |
| 4 | POST /v1/ressenyes/res_102/rebuig amb {"motiu": "..."} |
Acció amb paràmetres propis, modelada com a subrecurs (02-02) |
| 5 | PUT /v1/comandes/com_5001/enviament amb l'estat complet |
Singleton actualitzat per un soci que reintenta: la idempotència de PUT és imprescindible |
| 6 | DELETE /v1/cafes/caf_001 |
Esborrat lògic per dins; des de fora el cafè desapareix del catàleg |
| 7 | POST /v1/comandes amb Idempotency-Key |
Creació a la col·lecció, no idempotent per naturalesa: la clau evita comandes duplicades |
Solució 2
a) Un PUT literal substitueix el recurs complet, així que quedaria:
Sense nom, sense origen, sense torrefacció, sense estoc i sense notes de tast. L'id sobreviu perquè forma part de la identitat, no del contingut enviat. Per això la Botiga Aroma valida els camps obligatoris i retorna 400 dades_invalides en lloc de destruir el recurs.
b)
PATCH /v1/cafes/caf_001 HTTP/1.1
Content-Type: application/merge-patch+json
{ "preuEuros": 15.20, "estoc": 95 }c)
d)
PATCH /v1/cafes/caf_001 HTTP/1.1
Content-Type: application/json-patch+json
[
{ "op": "test", "path": "/estoc", "value": 120 },
{ "op": "add", "path": "/notesTast/-", "value": "bergamota" }
]Amb Merge Patch és impossible per dues raons acumulades: no existeix cap operació condicional (test), i els arrays se substitueixen sencers, així que "afegir al final" obliga a enviar la llista completa —cosa que, a més, trepitjaria qualsevol nota afegida per un altre usuari entre la lectura i l'escriptura. L'alternativa de la Botiga Aroma per al cas condicional no és JSON Patch, sinó If-Match amb ETag (04-06).
Solució 3
- Sí, obligatòria. Mou diners: un reintent per timeout no pot provocar dos reemborsaments. Mateix criteri que
/pagament. - No es reemborsa dues vegades. El servidor retorna la resposta desada de la primera execució amb
Idempotent-Replay: truei el mateix201iLocation. 409 Conflictamb un codi de negoci nou,comanda_no_enviada: la comanda existeix i la petició està ben formada, però el seu estat actual no permet la transició. No és400(les dades són vàlides) ni404(la comanda existeix).409 Conflictambcomanda_ja_retornada. La clau nova no ajuda: és la màquina d'estats del recurs la que rebutja la segona transició. És l'exemple de per què calen les dues defenses de la secció 10.- Sí que seria idempotent, i aquest és el seu atractiu:
PUT /devoluciosignificaria "vull que existeixi aquesta devolució amb aquestes dades", i repetir-ho deixaria el mateix estat. S'hi guanyaria idempotència sense capçalera addicional. S'hi perdria, en canvi, la semàntica d'acció amb efectes (unPUTsuggereix que s'escriu una dada, no que s'executa un reemborsament), la possibilitat que el servidor assigni dades pròpies de l'operació (referència del reemborsament, data) i la coherència amb/pagament,/anullacioi/aprovacio, que ja fan servirPOST. La Botiga Aroma prioritza la coherència:POSTamb clau d'idempotència.
Conclusió
Els mètodes HTTP són el vocabulari de verbs de la teva API i fer-los servir bé és el que separa el nivell 1 del nivell 2 de Richardson. GET llegeix sense efectes i es pot posar a la memòria cau; POST crea i executa accions, i no és idempotent; PUT substitueix del tot i sí que ho és; PATCH modifica just allò necessari —a la Botiga Aroma amb JSON Merge Patch i Content-Type: application/merge-patch+json—; DELETE elimina, encara que per dins sigui un esborrat lògic; i HEAD i OPTIONS donen informació sense transferir el recurs. Per damunt de tot queden la seguretat i la idempotència, que deixen de ser teoria així que hi ha reintents, timeouts o un doble clic: per això el pagament i la creació de comandes exigeixen Idempotency-Key i, a més, la màquina d'estats del recurs rebutja les transicions impossibles.
Justament aquí hem anat deixant caps solts: 201 amb Location, 204 sense cos, 404 davant de 410, 409 quan la transició no és possible, 415 quan el Content-Type no s'admet, 405 quan el mètode no està permès. Cadascun d'aquests números és una decisió de contracte. A la lliçó següent, 02-04 Codis d'estat HTTP, els recorrerem tots amb criteri, construirem un arbre de decisió per triar el correcte, dissenyarem el cos de l'error de la Botiga Aroma davant de l'estàndard application/problem+json i tancarem el catàleg de codis d'error de negoci.
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
