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

  1. La documentació és part del producte
  2. Tipus de documentació i a qui serveix cadascun
  3. Anatomia de la referència d'un endpoint
  4. Documentació dirigida per contracte: OpenAPI
  5. Un fragment real: GET /cafes en OpenAPI
  6. Què desbloqueja una especificació llegible per màquines
  7. Documentació com a codi
  8. Bones pràctiques de redacció
  9. Mantenir la documentació viva
  10. Balanç del contracte de la Botiga Aroma

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

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

3. Hauries de rebre:

{ "dades": [ { "id": "caf_001", "nom": "Etiòpia Yirgacheffe", "preuEuros": 14.50 } ], "total": 137 }

Funciona? Continua amb [Crear la teva primera comanda](./tutorial-comanda).

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.

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

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

  1. Un fragment real: GET /cafes en OpenAPI

Aquest é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 limit per defecte 20 i màxim 100, i el topall de desplacament (02-06).
  • El desempat per id a l'ordenació, documentat a la descripció.
  • L'embolcall dades/total (02-05) i el format d'error amb codi/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 torrefaccio pot créixer (02-07).
  • La capçalera Link com 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ó.

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

  1. Documentació com a codi

La documentació es tracta exactament igual que el codi:

  • Viu al repositori, al costat de la implementació. openapi.yaml a l'arrel, guies a docs/.
  • 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.

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

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

  1. 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: 409 i 422 sense documentar generen incidències que ningú no sap explicar.
  • Exemples que no es poden executar. Prova cada curl de 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, limit màxim i profunditat d'expansió són part del contracte: sense ells, l'integrador els descobreix amb un 400 en 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/cafes retorna 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/comandes no inclou la capçalera Idempotency-Key, que és obligatòria.
  • La taula d'errors de POST /v1/comandes/{id}/pagament només llista 400 i 500.
  • El changelog s'acaba fa vuit mesos.
  • Hi ha un endpoint /v1/promocions en 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." }'

**Resposta**

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

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