El contracte de la v1 de la Botiga Aroma està complet: recursos, URIs, mètodes, codis, representacions, col·leccions i una política de versionat. Però un contracte que només existeix al cap de l'equip que el va dissenyar no és un contracte: és un acord tàcit esperant a ser incomplert. Aquesta lliçó tracta la peça que converteix el disseny en una cosa utilitzable per altres. Veurem per què la documentació és part del producte i no un extra, quins tipus de document serveixen a quin lector, què ha d'incloure la referència de cada endpoint, i què canvia radicalment quan el contracte s'escriu en un format llegible per màquines com OpenAPI. Tanquem el mòdul 2 fent balanç de tot el contracte dissenyat i preparant el salt al mòdul 3.
Contingut
- La documentació és part del producte
- Tipus de documentació i a qui serveix cadascun
- Anatomia de la referència d'un endpoint
- Documentació dirigida per contracte: OpenAPI
- Un fragment real:
GET /cafesen OpenAPI - Què desbloqueja una especificació llegible per màquines
- Documentació com a codi
- Bones pràctiques de redacció
- Mantenir la documentació viva
- Balanç del contracte de la Botiga Aroma
- La documentació és part del producte
Una API no té interfície visual. No hi ha botons per explorar ni menús que insinuïn què es pot fer. Per a un desenvolupador que la consumeix, la documentació és literalment el producte: si no està documentat, no existeix.
Pensa-hi des de l'altre costat. Quan l'equip de RàpidEnviaments s'asseu a integrar la Botiga Aroma, la seva experiència consisteix a: llegir, provar un curl, tornar a llegir, escriure codi, trobar un error no documentat, escriure un correu, esperar dos dies. Cadascun d'aquests passos és cost. El que la bona documentació redueix no són "molèsties": és el temps fins a la primera crida correcta, la mètrica que decideix si la teva API s'adopta o s'abandona.
Conseqüències pràctiques de prendre-s'ho seriosament:
- Es planifica i s'estima com qualsevol altra funcionalitat. Un endpoint sense documentar no està acabat.
- Es prova: els exemples s'executen, no es copien de memòria.
- Té responsable i es revisa a les pull requests.
- Redueix el suport: cada pregunta que arriba per correu és una pregunta que la documentació no va respondre, i la resposta correcta no és contestar el correu, sinó arreglar la documentació.
- Tipus de documentació i a qui serveix cadascun
L'error més comú és escriure un únic document gegant. Hi ha lectors diferents, en moments diferents, amb necessitats incompatibles.
| Tipus | Lector | Quan el llegeix | Pregunta que respon |
|---|---|---|---|
| Inici ràpid | Desenvolupador nou | Primers 15 minuts | Com faig la meva primera crida? |
| Guia de conceptes | Integrador | En començar el disseny | Com funciona aquest domini? |
| Tutorials | Integrador | En implementar un cas d'ús | Com faig un flux complet? |
| Referència | Tothom | Constantment, mentre programen | Quins paràmetres accepta aquest endpoint? |
| Changelog | Integrador ja actiu | En actualitzar, o quan alguna cosa falla | Què ha canviat? |
| Exemples executables | Tothom | En provar | Puc veure això funcionant ja? |
Inici ràpid
L'objectiu és una sola cosa: una crida correcta en menys de cinc minuts. Res d'arquitectura, res de teoria.
## La teva primera crida a l'API de la Botiga Aroma 1. Aconsegueix la teva clau al panell de desenvolupador. 2. Executa:
curl -H "Authorization: Bearer EL_TEU_TOKEN"
"https://api.botigaaroma.example/v1/cafes?limit=3"
{ "dades": [ { "id": "caf_001", "nom": "Etiòpia Yirgacheffe", "preuEuros": 14.50 } ], "total": 137 }
Guia de conceptes
Explica el model mental, que cap referència no transmet. Per a la Botiga Aroma: què és una cistella i en què es diferencia d'una comanda, la màquina d'estats de les comandes, el cicle de moderació de ressenyes, com funcionen els webhooks signats, què significa que els identificadors siguin opacs. Sense això, l'integrador dedueix el model per prova i error, i el dedueix malament.
Tutorials
Recorren un cas d'ús complet de principi a fi: "De la cistella a la comanda pagada", amb les set crides encadenades, les seves respostes reals i els errors probables a cada pas. És el que més s'agraeix i el que menys s'escriu.
Changelog
Una entrada per canvi, amb data, tipus (afegit / canviat / deprecat / eliminat / corregit) i enllaç a la guia de migració quan toqui:
## 2026-04-02
### Afegit
- `GET /v1/cafes` accepta el filtre `disponible` (booleà).
- Els cafès inclouen `puntuacioMitjana` i `nombreRessenyes`.
### Deprecat
- Camp `preu` a la representació de cafè. Fes servir `preuEuros`.
Retirada prevista: 2027-04-02. Vegeu la [guia de migració](./migracio-preu).És el document més barat de mantenir i el que més confiança genera: demostra que l'API és viva i que els canvis s'anuncien.
- Anatomia de la referència d'un endpoint
La referència és el que es consulta cada dia. Cada endpoint necessita tots aquests elements; si en falta un, algú acabarà preguntant-lo per correu.
| Element | Detall |
|---|---|
| Mètode i ruta | GET /v1/cafes/{cafeId} |
| Descripció | Una frase que digui què fa i per a què serveix |
| Permisos | Quin token o rol cal |
| Paràmetres de ruta | Nom, tipus, format, exemple |
| Paràmetres de query | Nom, tipus, obligatorietat, valor per defecte, valors admesos, màxims |
| Capçaleres | Les que accepta o exigeix (Idempotency-Key, Accept-Language…) |
| Cos de la petició | Esquema complet, camps obligatoris, validacions |
| Resposta d'èxit | Codi, capçaleres rellevants i exemple complet |
| Respostes d'error | Tots els codis possibles amb el seu codi d'error |
| Límits | Rate limiting, mides màximes, cost |
| Idempotència | Si ho és, i com es garanteix |
| Exemples | curl complet, copiable i funcional |
Exemple abreujat de com queda per a la Botiga Aroma:
### POST /v1/comandes/{comandaId}/pagament
Paga una comanda pendent. Crea el recurs de pagament associat a la comanda i,
si es completa, en canvia l'estat a `pagat` i emet l'esdeveniment `comanda.pagada`
cap a RàpidEnviaments.
**Permisos:** el client propietari de la comanda, o un token del panell intern.
**Idempotència:** obligatòria. Has d'enviar `Idempotency-Key` amb un UUID
únic per intent de pagament. Els reintents amb la mateixa clau i el mateix
cos retornen la resposta original amb `Idempotent-Replay: true`.
**Paràmetres de ruta**
| Nom | Tipus | Descripció |
|---|---|---|
| `comandaId` | string | Identificador de la comanda. Ex.: `com_5001` |
**Cos**
| Camp | Tipus | Obligatori | Descripció |
|---|---|---|---|
| `metode` | string | Sí | `targeta` o `transferencia` |
| `tokenTargeta` | string | Si `metode` és `targeta` | Token de la passarel·la |
**Respostes**
| Codi | Quan | `codi` d'error |
|---|---|---|
| `201` | Pagament fet (capçalera `Location`) | — |
| `400` | Dades invàlides o falta `Idempotency-Key` | `dades_invalides`, `clau_idempotencia_requerida` |
| `401` | Sense autenticació vàlida | `no_autenticat` |
| `403` | La comanda no és teva | `permisos_insuficients` |
| `404` | La comanda no existeix | `comanda_no_trobada` |
| `409` | La comanda ja està pagada o hi ha un pagament en curs | `comanda_ja_pagada`, `operacio_en_curs` |
| `422` | `Idempotency-Key` reutilitzada amb un altre cos | `clau_idempotencia_reutilitzada` |
| `502` | La passarel·la de pagament no respon correctament | `servei_no_disponible` |
**Exemple**
curl -i -X POST "https://api.botigaaroma.example/v1/comandes/com_5001/pagament"
-H "Authorization: Bearer $AROMA_TOKEN"
-H "Idempotency-Key: 5f3b9c2a-1d7e-4a44-9f30-8b1c2d3e4f50"
-H "Content-Type: application/json"
-d '{ "metode": "targeta", "tokenTargeta": "tok_visa_4242" }'
La columna d'errors és la que més s'omet i la que més incidències evita: sense ella, l'integrador descobreix el 409 el dia que un usuari prem dues vegades.
- Documentació dirigida per contracte: OpenAPI
Tot l'anterior es pot escriure a mà en Markdown. Funciona, i per a una API petita pot ser suficient. Però hi ha una alternativa que canvia les regles del joc: escriure el contracte en un format que les màquines entenguin.
OpenAPI (abans Swagger) és una especificació —avui a la versió 3.1— per descriure una API HTTP en YAML o JSON: les seves rutes, mètodes, paràmetres, esquemes de dades, respostes, errors i seguretat. No és documentació sobre l'API: és l'API descrita formalment, i la documentació llegible n'és només un dels productes.
graph TD
O["<b>openapi.yaml</b><br/>el contracte"] --> D["Documentació<br/>de referència navegable"]
O --> C["Clients generats<br/>(JS, Java, Python…)"]
O --> M["Servidors simulats<br/>(mocks)"]
O --> T["Proves de contracte<br/>automàtiques"]
O --> V["Validació de<br/>peticions i respostes"]
O --> G["Configuració de la<br/>passarel·la i del portal"]
La diferència amb la documentació escrita a mà és de naturalesa, no de grau: un document en Markdown descriu el contracte i pot mentir; una especificació OpenAPI és el contracte i es pot verificar contra la implementació de manera automàtica.
Aquesta lliçó es queda en el perquè i en un fragment il·lustratiu. Swagger i OpenAPI a fons —editors, generadors, interfície interactiva, bones pràctiques d'escriptura— són la lliçó 05-02, i l'ús del contracte per a mocks i proves automatitzades és 05-04.
- Un fragment real:
GET /cafes en OpenAPI
GET /cafes en OpenAPIAquest és l'aspecte que té el contracte de la col·lecció de cafès que vam dissenyar a 02-06, escrit en OpenAPI 3.1:
openapi: 3.1.0
info:
title: API de la Botiga Aroma
version: 1.7.0
description: |
API REST de la botiga de cafè d'especialitat Botiga Aroma.
Tots els imports són en euros amb dos decimals i totes les
dates en ISO-8601 UTC.
servers:
- url: https://api.botigaaroma.example/v1
description: Producció
paths:
/cafes:
get:
summary: Llista el catàleg de cafès
description: |
Retorna els cafès del catàleg, filtrats, ordenats i paginats.
La paginació és obligatòria: si no s'indica `limit`, s'apliquen 20.
operationId: obtenirCafes
tags: [Cafès]
parameters:
- name: origen
in: query
description: Filtra per país d'origen. Admet diversos valors separats per comes.
schema: { type: string }
example: Colòmbia
- name: torrefaccio
in: query
description: Filtra per nivell de torrefacció. Admet diversos valors separats per comes.
schema:
type: string
example: clar,mitja
- name: preuMin
in: query
description: Preu mínim en euros, inclusivament.
schema: { type: number, minimum: 0 }
- name: preuMax
in: query
description: Preu màxim en euros, inclusivament.
schema: { type: number, minimum: 0 }
- name: q
in: query
description: Cerca de text a nom, origen i notes de tast.
schema: { type: string, minLength: 2, maxLength: 100 }
- name: ordenar
in: query
description: |
Camp d'ordenació. Prefixa'l amb `-` per a ordre descendent.
L'ordre es desempata sempre per `id` ascendent.
schema:
type: string
enum: [nom, -nom, preuEuros, -preuEuros, estoc, -estoc, dataCreacio, -dataCreacio]
default: nom
- $ref: '#/components/parameters/limit'
- $ref: '#/components/parameters/desplacament'
responses:
'200':
description: Llista de cafès.
headers:
Link:
description: Enllaços de paginació (RFC 8288) amb rel next, prev, first i last.
schema: { type: string }
content:
application/json:
schema:
type: object
required: [dades, total]
properties:
dades:
type: array
items: { $ref: '#/components/schemas/Cafe' }
total:
type: integer
description: Nombre total d'elements que compleixen el filtre.
example:
dades:
- 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'
total: 137
'400':
description: Paràmetre de consulta invàlid.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example:
error:
codi: parametre_invalid
missatge: "El paràmetre 'limit' no pot superar 100."
detalls: []
components:
parameters:
limit:
name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
desplacament:
name: desplacament
in: query
schema: { type: integer, minimum: 0, maximum: 10000, default: 0 }
schemas:
Cafe:
type: object
required: [id, nom, origen, torrefaccio, preuEuros, estoc]
properties:
id:
type: string
pattern: '^caf_[a-zA-Z0-9]+$'
description: Identificador opac. No el parsegis.
nom: { type: string, maxLength: 120 }
origen: { type: string }
torrefaccio:
type: string
enum: [clar, mitja, fosc]
description: S'hi poden afegir valors nous sense previ avís.
preuEuros: { type: number, minimum: 0, multipleOf: 0.01 }
estoc: { type: integer, minimum: 0 }
notesTast:
type: array
items: { type: string }
dataCreacio: { type: string, format: date-time }
Error:
type: object
required: [error]
properties:
error:
type: object
required: [codi, missatge, detalls]
properties:
codi: { type: string, example: cafe_no_trobat }
missatge: { type: string }
detalls: { type: array, items: { type: object } }Fixa't en quant contracte d'aquest mòdul hi ha codificat aquí, i de manera verificable:
- El
limitper defecte 20 i màxim 100, i el topall dedesplacament(02-06). - El desempat per
ida l'ordenació, documentat a la descripció. - L'embolcall
dades/total(02-05) i el format d'error ambcodi/missatge/detalls(02-04). - El patró de l'identificador i l'avís d'opacitat (02-02).
- Els imports amb
multipleOf: 0.01, és a dir, dos decimals (02-05). - L'avís que l'enumerat
torrefacciopot créixer (02-07). - La capçalera
Linkcom a part declarada de la resposta.
Els $ref a components eviten repetir limit, desplacament, Cafe i Error a cada endpoint: s'escriuen una vegada i es referencien, que és exactament la consistència que demanava 02-01, ara garantida per construcció.
- Què desbloqueja una especificació llegible per màquines
| Producte | Què és | Benefici | Es veu a |
|---|---|---|---|
| Referència navegable | Documentació HTML generada | Mai no es desincronitza del contracte | 05-02 |
| Interfície interactiva | "Prova-ho" des del navegador | Primera crida sense escriure codi | 05-02 |
| Clients generats | SDK en diversos llenguatges | El consumidor no escriu codi HTTP | 05-02 |
| Servidors simulats | Mock que respon segons el contracte | La SPA avança sense esperar el servidor | 05-04 |
| Proves de contracte | Verifiquen la implementació contra l'especificació | Detecten la deriva automàticament | 05-04 |
| Validació en execució | Middleware que valida peticions i respostes | Errors coherents sense escriure'ls a mà | 03-04 |
| Linters d'estil | Comproven les convencions de la guia | La guia d'estil deixa de ser voluntària | 05-05 |
| Configuració de passarel·la | Rutes, límits i seguretat importats | Menys configuració duplicada | 05-06 |
El més important per a un projecte API-first com el nostre és el mock: amb l'openapi.yaml acabat, la SPA i Aroma Mòbil poden començar a integrar el primer dia contra un servidor simulat, mentre l'equip de servidor implementa. Això és el que fa que "dissenyar abans de programar" no sigui temps perdut, sinó paral·lelisme guanyat.
- Documentació com a codi
La documentació es tracta exactament igual que el codi:
- Viu al repositori, al costat de la implementació.
openapi.yamla l'arrel, guies adocs/. - Es versiona amb Git, així que l'històric respon a "des de quan diu això?".
- Es revisa en pull request: un canvi d'API que no toca el contracte ni el changelog no s'aprova.
- Es valida en integració contínua: l'especificació es comprova sintàcticament, s'hi passa un linter d'estil i s'executen les proves de contracte.
- Es publica automàticament en fusionar a la branca principal.
Estructura típica del repositori de la Botiga Aroma:
botiga-aroma-api/ ├── openapi.yaml # el contracte ├── CHANGELOG.md # canvis per data ├── docs/ │ ├── guia-estil.md # les convencions de 02-01 │ ├── inici-rapid.md │ ├── conceptes/ │ │ ├── comandes-estats.md │ │ ├── idempotencia.md │ │ └── webhooks.md │ ├── tutorials/ │ │ └── de-la-cistella-a-la-comanda-pagada.md │ └── migracions/ │ └── migracio-preu.md └── src/ # la implementació (mòdul 3)
El detall que fa que això funcioni de debò: la porta de qualitat a CI. Si el pipeline falla quan la implementació no compleix el contracte, la documentació deixa de dependre de la bona voluntat. Es munta a 05-05.
- Bones pràctiques de redacció
Exemples reals i copiables. Res de <EL_TEU_ID_AQUÍ> barrejat amb dades falses. Fes servir els identificadors del domini (caf_001, com_5001, cli_842) de manera consistent a tota la documentació: qui llegeix un tutorial reconeix les mateixes dades a la referència.
# ✗ Inútil: no es pot executar i no diu què retorna
GET /cafes?params
# ✓ Copiable, executable, amb resposta esperada
curl -H "Authorization: Bearer $AROMA_TOKEN" \
"https://api.botigaaroma.example/v1/cafes?torrefaccio=mitja&limit=2"curl complet, sempre. Amb la capçalera d'autenticació, la URL entre cometes (per l'&, com vam veure a 01-03) i el cos sencer. És el mínim comú denominador: funciona en qualsevol sistema i no pressuposa cap llenguatge.
Documenta els errors tant com els èxits. És la diferència més visible entre una documentació professional i una d'amateur.
Explica el perquè, no només el què. "Idempotency-Key és obligatòria perquè un reintent després d'un timeout podria cobrar dues vegades" ensenya; "Idempotency-Key: cadena, obligatòria" només informa.
Res de "TBD", "per documentar" o "pendent". Un buit declarat és pitjor que l'absència: el lector perd el temps confiant que hi apareixerà.
Coherència amb la guia d'estil. Si la guia diu camelCase, cap exemple no porta snake_case. La documentació és on les incoherències es veuen, i on destrueixen la confiança més ràpid.
Escriu per a qui no coneix el teu domini. El primer ús de "cistella", "línia" o "moderació" mereix una definició. L'equip de RàpidEnviaments sap de logística, no de cafè d'especialitat.
Compte amb les captures de pantalla. Envelleixen malament i no es poden cercar ni copiar. Prefereix els blocs de codi.
- Mantenir la documentació viva
L'enemic té nom: la deriva (drift), la distància que s'obre entre el que la documentació diu i el que l'API fa. Una API mal documentada és un problema; una API mal documentada que sembla ben documentada és pitjor, perquè l'integrador s'hi confia i falla.
Generada davant d'escrita a mà
| Enfocament | Com funciona | A favor | En contra |
|---|---|---|---|
| Contracte primer | S'escriu l'openapi.yaml i d'aquí surten documentació, mocks i validació |
Coherent amb API-first; permet mocks abans d'implementar | Requereix disciplina perquè el codi segueixi el contracte |
| Codi primer | S'anoten els controladors i es genera l'especificació | Difícil que es desincronitzi de la implementació | Documenta el que hi ha, no el que es va acordar; arriba tard |
| Mixt | Contracte escrit a mà + proves que verifiquen la implementació | El millor de tots dos | Cal muntar les proves de contracte |
La Botiga Aroma fa servir l'enfocament mixt: openapi.yaml escrit a mà —és la font de veritat, coherent amb l'API-first de 02-01— i proves de contracte a CI que fallen si la implementació se'n desvia. Les guies i els tutorials s'escriuen sempre a mà: cap generador no explica per què una cistella no és una comanda.
Detectar la deriva
| Tècnica | Què detecta | On es tracta |
|---|---|---|
| Proves de contracte a CI | Respostes que no compleixen l'esquema | 05-04 |
| Validació de respostes en preproducció | Camps nous no documentats | 03-04 |
| Executar els exemples de la documentació com a proves | Exemples obsolets | 05-04 |
| Linter d'OpenAPI | Convencions incomplertes, descripcions absents | 05-05 |
| Revisió obligatòria a cada PR que toqui l'API | Canvis sense documentar | Procés |
| Mètriques d'ús davant d'endpoints documentats | Endpoints "fantasma" no documentats | 04-07 |
Senyals d'alarma
- El changelog fa mesos que no té entrades, però l'API ha canviat.
- Els exemples fan servir camps que ja no existeixen.
- Hi ha endpoints en producció que no apareixen a l'especificació.
- Les respostes d'error reals no coincideixen amb les documentades.
- L'equip respon per xat preguntes que la documentació hauria de respondre.
- Balanç del contracte de la Botiga Aroma
Això és el que hem dissenyat al llarg del mòdul 2, i és exactament el que el mòdul 3 implementarà.
Mètode i principis (02-01). Enfocament API-first, consumidors identificats (SPA, Aroma Mòbil, panell intern, RàpidEnviaments), recursos extrets del domini i una guia d'estil escrita.
Recursos i URIs (02-02). Mapa complet de 24 URIs sobre https://api.botigaaroma.example/v1, plural en minúscules, kebab-case, imbricació màxima de dos nivells, singleton en singular, identificadors opacs amb prefix i accions no CRUD modelades com a subrecursos amb POST (/pagament, /anullacio, /aprovacio, /rebuig).
Mètodes (02-03). GET, POST, PUT, PATCH, DELETE, HEAD i OPTIONS assignats recurs a recurs; PATCH amb JSON Merge Patch; esborrat lògic invisible des de fora; Idempotency-Key obligatòria a POST /comandes i POST /comandes/{id}/pagament.
Codis (02-04). Els codis que es fan servir de debò, amb 201 + Location, 409 per als conflictes d'estat, la distinció 401/403 i 404/410; format d'error propi {"error": {"codi", "missatge", "detalls"}} davant de problem+json; catàleg de 28 codis d'error de negoci.
Representacions (02-05). camelCase, ISO-8601 UTC, euros amb dos decimals, enumerats snake_case ampliables, null davant d'absent, embolcall dades/total, criteris d'incrustar davant d'enllaçar, _links d'hipermèdia selectiva, expandir i camps, i negociació amb Accept, Accept-Language, Accept-Encoding i Vary.
Col·leccions (02-06). Filtres explícits, rangs Min/Max i Des/Fins, multivalor amb comes, ordenar amb desempat per id, offset per a /cafes i cursor per a /comandes, total al cos i navegació a la capçalera Link, ?q= per a la cerca, i límits per defecte i màxims.
Evolució (02-07). Versió a la ruta, dues versions vives com a màxim, 12 mesos de depreciació, capçaleres Deprecation i Sunset i apagada amb 410 Gone.
Documentació (02-08). openapi.yaml com a font de veritat, guies i tutorials a mà, changelog per data, tot al repositori i validat a CI.
Errors Comuns i Consells
- Deixar la documentació per al final. Aquest final no arriba mai. Documenta l'endpoint a la mateixa pull request que el crea.
- Documentar només el camí feliç. Els errors són la meitat de la feina de l'integrador:
409i422sense documentar generen incidències que ningú no sap explicar. - Exemples que no es poden executar. Prova cada
curlde la documentació. Si un exemple falla, has perdut la confiança del lector per a tota la resta. - Confondre referència amb guia. La referència diu què accepta un endpoint; mai no explica com encadenar sis crides per pagar una comanda. Calen totes dues.
- Generar la documentació del codi i donar-la per bona. Documenta el que hi ha, inclosos els bugs, i no diu res de conceptes ni d'intenció.
- No documentar els límits. Rate limits, mides màximes,
limitmàxim i profunditat d'expansió són part del contracte: sense ells, l'integrador els descobreix amb un400en producció. - Oblidar el changelog. És el document més barat i el que més agraeix un consumidor extern.
- Consell: mesura el "temps fins a la primera crida correcta". Seu amb algú que no conegui l'API, dona-li la documentació i cronometra en silenci. Descobriràs més en vint minuts que en tres reunions.
- Consell: tracta cada pregunta de suport com una fallada de documentació. La resposta no és contestar el correu, és arreglar el document i després contestar amb l'enllaç.
Exercicis
Exercici 1: escriure la referència d'un endpoint
Escriu la documentació de referència completa de POST /v1/cafes/{cafeId}/ressenyes amb tots els elements de la secció 3. Fes servir el contracte dissenyat en aquest mòdul: el cos porta puntuacio (enter d'1 a 5) i comentari (text de fins a 5.000 caràcters); la ressenya es crea en estat pendent_moderacio; només poden ressenyar els clients que hagin comprat aquell cafè.
Exercici 2: completar el contracte OpenAPI
Amplia el fragment de la secció 5 afegint-hi l'operació GET /cafes/{cafeId}: paràmetre de ruta, resposta 200 amb l'esquema Cafe reutilitzat, resposta 404 amb l'esquema Error i exemple de l'error cafe_no_trobat, i resposta 304 per a la memòria cau condicional. Reutilitza els components existents.
Exercici 3: detectar deriva
Un desenvolupador arriba a la Botiga Aroma i es troba això. Identifica tots els problemes de documentació i proposa el remei concret i el procés que evitaria que tornés a passar.
- La referència diu que
GET /v1/cafesretorna un array; l'API retorna{"dades": [...], "total": n}. - No hi ha cap menció al paràmetre
expandir, que en canvi funciona. - L'exemple de
POST /v1/comandesno inclou la capçaleraIdempotency-Key, que és obligatòria. - La taula d'errors de
POST /v1/comandes/{id}/pagamentnomés llista400i500. - El changelog s'acaba fa vuit mesos.
- Hi ha un endpoint
/v1/promocionsen producció que no apareix enlloc.
Solucions
Solució 1
### POST /v1/cafes/{cafeId}/ressenyes
Crea una ressenya sobre un cafè. La ressenya es crea en estat
`pendent_moderacio` i no apareix a les llistes públiques fins que un
moderador l'aprovi amb `POST /v1/ressenyes/{ressenyaId}/aprovacio`.
**Permisos:** client autenticat que hagi comprat el cafè en una comanda
en estat `enviat`. En cas contrari es retorna `403`.
**Idempotència:** no obligatòria. `Idempotency-Key` és opcional i es
recomana per evitar ressenyes duplicades per doble enviament del formulari.
**Paràmetres de ruta**
| Nom | Tipus | Descripció |
|---|---|---|
| `cafeId` | string | Identificador del cafè. Ex.: `caf_001` |
**Cos**
| Camp | Tipus | Obligatori | Validació |
|---|---|---|---|
| `puntuacio` | integer | Sí | Entre 1 i 5, tots dos inclosos |
| `comentari` | string | Sí | Entre 10 i 5.000 caràcters |
**Respostes**
| Codi | Quan | `codi` d'error |
|---|---|---|
| `201` | Ressenya creada (capçalera `Location`) | — |
| `400` | Validació fallida | `dades_invalides` |
| `401` | Sense autenticació | `no_autenticat` |
| `403` | El client no ha comprat aquest cafè | `permisos_insuficients` |
| `404` | El cafè no existeix | `cafe_no_trobat` |
| `409` | El client ja ha ressenyat aquest cafè | `ressenya_duplicada` |
| `410` | El cafè està descatalogat | `cafe_descatalogat` |
| `429` | Límit de peticions superat | `limit_peticions` |
**Límits:** màxim 5 ressenyes per client i dia.
**Exemple**
curl -i -X POST "https://api.botigaaroma.example/v1/cafes/caf_001/ressenyes"
-H "Authorization: Bearer $AROMA_TOKEN"
-H "Content-Type: application/json"
-d '{ "puntuacio": 5, "comentari": "Cítric i floral, espectacular en V60." }'
HTTP/1.1 201 Created Location: https://api.botigaaroma.example/v1/ressenyes/res_101
{ "id": "res_101", "cafeId": "caf_001", "clientId": "cli_842", "puntuacio": 5, "comentari": "Cítric i floral, espectacular en V60.", "estat": "pendent_moderacio", "dataCreacio": "2026-03-14T10:30:00Z", "_links": { "self": { "href": "/v1/ressenyes/res_101" }, "cafe": { "href": "/v1/cafes/caf_001" } } }
Observa que ha aparegut un codi d'error nou, ressenya_duplicada (409): documentar obliga a tancar decisions que el disseny havia deixat obertes. Aquest és un dels grans beneficis d'escriure la referència abans d'implementar.
Solució 2
/cafes/{cafeId}:
get:
summary: Obté un cafè concret
operationId: obtenirCafePerId
tags: [Cafès]
parameters:
- name: cafeId
in: path
required: true
description: Identificador opac del cafè.
schema:
type: string
pattern: '^caf_[a-zA-Z0-9]+$'
example: caf_001
- name: If-None-Match
in: header
required: false
description: ETag d'una còpia prèvia, per a memòria cau condicional.
schema: { type: string }
example: '"a1b2c3d4"'
responses:
'200':
description: El cafè sol·licitat.
headers:
ETag:
description: Identificador de versió de la representació.
schema: { type: string }
content:
application/json:
schema: { $ref: '#/components/schemas/Cafe' }
example:
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'
'304':
description: |
La representació no ha canviat des de la versió indicada
a `If-None-Match`. Sense cos.
headers:
ETag:
schema: { type: string }
'404':
description: No existeix cap cafè amb aquest identificador.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example:
error:
codi: cafe_no_trobat
missatge: "No existeix cap cafè amb l'identificador 'caf_999'."
detalls: []
'410':
description: El cafè va existir i s'ha descatalogat definitivament.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example:
error:
codi: cafe_descatalogat
missatge: "El cafè 'caf_001' es va retirar del catàleg el 2026-02-01."
detalls: []Cal notar que Cafe i Error es reutilitzen amb $ref: en afegir demà un camp a l'esquema Cafe, apareix automàticament a les dues operacions i a la documentació generada. Això és consistència garantida per construcció, no per revisió humana.
Solució 3
| Problema | Gravetat | Remei | Prevenció |
|---|---|---|---|
| La referència diu array i l'API retorna embolcall | Crítica: tot integrador nou escriu codi que falla a la primera crida | Corregir l'esquema a l'openapi.yaml i publicar |
Proves de contracte a CI (05-04): una resposta que no compleix l'esquema ha de trencar el build |
expandir no documentat |
Alta: funcionalitat invisible que a més ningú no garanteix mantenir | Documentar-lo amb les seves regles (un nivell, màxim 3, 400 si és desconegut) |
Revisió obligatòria a la PR: cap paràmetre nou no es fusiona sense contracte |
Falta Idempotency-Key a l'exemple |
Alta: l'exemple copiat retorna 400 |
Corregir l'exemple i marcar la capçalera com a obligatòria | Executar els exemples com a proves a CI |
| Taula d'errors incompleta | Alta: el 409 comanda_ja_pagada apareix en producció sense previ avís |
Completar-la amb 401, 403, 404, 409, 422 i 502 |
Plantilla d'endpoint amb la taula d'errors com a camp obligatori |
| Changelog abandonat | Mitjana: es perd la confiança i les depreciacions no s'assabenten | Reconstruir-lo a partir de l'històric de Git i reprendre'l | Comprovació a CI: si canvia l'openapi.yaml, ha de canviar el CHANGELOG.md |
Endpoint fantasma /v1/promocions |
Crítica: superfície no documentada, no versionada i probablement no auditada en seguretat | Decidir: documentar-lo o retirar-lo. No hi ha una tercera opció | Comparar rutes reals (mètriques de 04-07) amb les de l'especificació i alertar de les diferències |
El procés que ho evita tot, en una frase: l'especificació és la font de veritat, viu al repositori, es valida a cada pull request i el pipeline falla si la implementació no la compleix. Sense porta automàtica, la deriva és qüestió de temps.
Conclusió
La documentació no és allò que s'escriu després de programar: és la cara visible d'un producte que no té interfície. Ara saps que calen documents diferents per a lectors diferents —inici ràpid, conceptes, tutorials, referència, changelog—, què ha de contenir la referència de cada endpoint (inclosos tots els seus errors, que és el que més s'omet), i per què escriure el contracte en OpenAPI canvia la naturalesa de l'assumpte: deixa de ser un text que descriu l'API i passa a ser el contracte mateix, del qual surten la documentació navegable, els clients, els mocks, les proves i la configuració de la passarel·la. Amb documentació com a codi, revisada en pull request i validada a CI, la deriva deixa de dependre de la bona voluntat.
Amb això tanquem el mòdul 2. Has dissenyat, peça a peça i sobre el paper, el contracte complet de l'API de la Botiga Aroma: el seu mètode de treball i la seva guia d'estil, les seves 24 URIs amb les accions modelades com a subrecursos, els seus mètodes amb la idempotència del pagament resolta, la seva taula de codis i el seu catàleg d'errors de negoci, les seves representacions amb hipermèdia selectiva, les seves col·leccions filtrades i paginades, la seva política de versionat i depreciació, i la seva documentació. Res d'això no ha necessitat encara ni una línia de servidor, i aquesta era exactament la idea: en API-first, el contracte va al davant. Ara toca complir-lo. Al mòdul 3, Desenvolupament d'APIs RESTful, muntarem l'entorn amb Node.js 20, aixecarem el servidor amb Express i convertirem cada decisió d'aquest mòdul en codi: les rutes del mapa d'URIs, la validació que retorna dades_invalides amb els seus detalls, la capa de persistència que tradueix cèntims a euros, l'autenticació que distingeix 401 de 403, el middleware d'errors que emet el format que hem fixat i les proves que verifiquen que la implementació respecta el contracte. Comencem pel principi: 03-01, Configuració de l'entorn de desenvolupament.
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
