Nel progettare interfacce Web conformi ai principi REST, è essenziale distinguere tra due categorie fondamentali di URI: Collection URI ed Element (o Item) URI.
Una Collection URI identifica un insieme omogeneo di risorse - pensiamo a una lista di oggetti tutti dello stesso tipo. La sua struttura è minimalista: il path termina con il nome della collezione (ad esempio /students oppure /courses). Invocare un endpoint di questo genere restituisce, di norma, una rappresentazione aggregata dell’intero gruppo (per esempio, l’elenco completo degli studenti iscritti o dei corsi offerti). Questa impostazione è coerente con l’idea di “risorsa astratta” che, in REST, può essere manipolata come unità logica indivisibile: si può interrogare, paginare, filtrare, creare nuovi elementi al suo interno, ma non la si confonde mai con le singole entità che contiene.
Per contro, un Element URI - detto anche Item o Simple URI - identifica un’istanza specifica all’interno di quella collezione e ne consente l’accesso puntuale. La sintassi si estende aggiungendo un identificatore univoco subito dopo il nome della collection, nel formato /collection/identifier, come in http://api.polito.it/students/s123456 o http://api.polito.it/courses/01zqp. In questo modo l’URI diventa un riferimento diretto alla singola risorsa, sulla quale il client può compiere operazioni CRUD mirate: leggere le proprietà di quello studente, modificarle, o eventualmente eliminarne la rappresentazione dai dati persistenti.
Le operazioni offerte da un’API RESTful si appoggiano ai metodi del protocollo HTTP, ognuno dei quali ha un significato semantico ben definito. GET è il metodo impiegato per ottenere la rappresentazione corrente di una risorsa. Quando la risorsa è una collection, la risposta conterrà l’elenco degli elementi che la compongono: quando la risorsa è un singolo element, il payload restituirà le proprietà specifiche di quell’elemento. In entrambi i casi, i dati viaggiano esclusivamente nel corpo della risposta: la richiesta rimane priva di corpo per aderire al principio di sicurezza e idempotenza di GET.
POST viene utilizzato per creare nuove risorse. I dati necessari alla creazione sono inseriti nel corpo della richiesta, mentre l’URI a cui si invia la POST identifica in genere la collection che fungerà da contenitore. A operazione avvenuta con successo, il server restituisce in genere lo stato 201 Created e inserisce nell’header Location l’URI del nuovo elemento, in modo da consentire al client di accedervi immediatamente.
PUT serve invece ad aggiornare un elemento esistente nella sua interezza o nelle sue proprietà principali. Anche in questo caso, il contenuto aggiornato viene inviato nel corpo della richiesta, ma l’URI è quello dell’elemento da modificare. Diversamente da POST, PUT è idempotente: più invii consecutivi con lo stesso payload producono con lo stesso risultato, caratteristica essenziale per il recupero da eventuali ritrasmissioni o errori di rete.
Infine, DELETE è il metodo dedicato alla rimozione di una risorsa. Una richiesta DELETE indirizzata all’URI dell’elemento comporta la sua cancellazione logica o fisica, a seconda delle policy del servizio; l’operazione è anch’essa idempotente, perché ripetere la cancellazione di qualcosa che non esiste più non modifica lo stato del sistema.
Nella progettazione di un’API REST occorrerà spesso esprimere le relazioni che intercorrono fra le varie risorse del dominio. Ogni elemento (o risorsa) può trovarsi in una relazione uno-a-uno (1:1) oppure uno a molti (1:N) con altri elementi: per esempio, uno studente può essere iscritto a più corsi, mentre ogni corso a sua volta è frequentato da un insieme di studenti.
Per restituire queste relazioni in maniera chiara e prevedibile, si adotta una convenzione di URI gerarchici nella forma /collection/identificatore/relazione. La prima parte indica la collezione principale (ad es. students), la seconda l’identificativo univoco della risorsa (ad es. s123456) e, infine, il segmento finale descrive la risorsa correlata (courses). La struttura dell’URI, quindi, già veicola il contesto e riduce l’ambiguità: chi interroga l’endpoint comprende immediatamente quale relazione stia esplorando.
Due esempi chiariscono l’approccio:
http://api.polito.it/students/s123456/courses restituisce l’elenco dei corsi seguiti dallo studente con matricola s123456.http://api.polito.it/courses/01qzp/students fornisce la lista degli studenti iscritti al corso identificato da 01qzp.Quando un’API incontra un errore - sia esso un’eccezione non gestita o la violazione di un vincolo di business - è fondamentale restituire un codice di stato HTTP che comunichi in modo inequivocabile la natura del problema. Usare status code “parlanti” (4xx per colpe del client, 5xx per malfunzionamenti lato server) permette a chi integra il servizio di reagire correttamente, senza dover decifrare descrizioni generiche o, peggio, messaggi HTML pensati per un browser.
Il payload della risposta dovrebbe veicolare informazioni aggiuntive utili a due destinatari distinti. Da un lato c’è lo sviluppatore: ha bisogno di una spiegazione verbosa e in linguaggio naturale che chiarisca cosa sia andato storto e - quando possibile - offra indicazioni pratiche su come rimediare. Dall’altro c’è l’utilizzatore finale dell’applicazione: a lui basta un messaggio sintetico, eventualmente mostrabile in UI che renda comprensibile l’anomalia senza dettagli tecnici superflui.
Un formato JSON strutturato soddisfa entrambe le esigenze. Oltre ai campi developerMessage e userMessage, è buona pratica includere un errorCode numerico stabile: questo consente di tracciare gli errori in modo univoco nei log e di costruire tabelle di mapping per una localizzazione rapida dei messaggi. Infine, un link moreInfo che punti alla documentazione (o a un knowledge base interno) abbassa la soglia di ingresso per chi debba investigare il guasto o aprire un ticket di assistenza.