Guida alla progettazione delle API HTTP
- Fondamenti
- Richieste
- Risposte
- Ritorna sempre un codice di stato appropriato
- Fornisci le risorse interamente, quando possibile
- Fornisci gli (UU)IDs delle risorse
- Fornisci dei timestamps standard
- Usa date in formato UTC formattate in ISO8601
- Annida relazioni tramite le chiavi esterne (foreign-key)
- Genera errori strutturati
- Visualizza lo stato del limite delle richieste
- Mantieni il JSON minimizzato in tutte le risposte
- Artefatti
Fondamenti
Questa sezione illustra i principi di progettazione, sui quali si basa questa guida
Concetti separati
Struttura i componenti in maniera semplice mentre li progetti, separando i concetti tra le varie parti del ciclo di richiesta e risposta. Mantenendo la semplicita sui componenti, avrai possibilità di focalizzarti di più su problemi più grandi e più difficili da risolvere.
La richiesta e la risposta saranno fatte in modo da gestire una particolare risorsa oppure una loro collezione. Usa il percorso (path) per indicare un'identità, il body per trasferire il contenuto e gli headers per comunicare dati aggiuntivi (metadata). I parametri di query passati nell'url, possono essere usati come alternativa agli headers, solo in rari casi. Gli headers sono sempre preferiti, perche permettono più flessibilità e permettono di inviare informazioni più dettagliate.
Utlizza una connessione sicura (TLS)
Richiedi una connessione sicura con protocollo TLS per accedere alle APIs, senza eccezioni. Non importa cercare di capire quando è opportuno usare TLS oppure quando non lo è. Semplicemente richiedila sempre.
Idealmente, puoi rifiutare qualsiasi richiesta che non sia fatta utilizzando il protocollo TLS, per evitare scambi di dati ed informazioni non sicuri. Nel caso in cui non possa gestire questo tipo di regola, basta rispondere con un 403 Forbidden
.
I redirects sono sconsigliati in quanto non rappresentano un buon approccio. I clients che si imbattono in molti redirects, raddoppiano il traffico sul server e rendono pressocche inutile il protocollo TLS, in quanto le informazioni sensibili vengono esplicitate durante la prima chiamata http
Richiedi il versioning negli Accept Headers
Un sistema di versioning e transizione tra le versioni può essere uno degli aspetti più difficili da progettare e realizzare nelle tue REST API. Proprio per questo, è meglio cominciare con alcuni accorgimenti che ci aiuteranno a mitigare questo tipo di problemi sin da subito.
Per evitare sorprese, cambiamenti bruschi agli utenti, è certamente buona norma richiedere di specificare la versione delle APIs in tutte le richieste. Il meccanismo di impostare una versione di default dovrebbe essere evitato, in quanto è molto difficile da cambiare in futuro.
L'approccio migliore sarebbe quello di specificare la versione negli headers http, con altri metadata, utilizzando per esempio Accept
header con un Content-Type
specifico, es:
Accept: application/vnd.heroku+json; version=3
Supporta ETag per il caching
Includi un header ETag
in tutte le risposte, identificando la specifica versione della risorsa restituita.
Questo permetterà gli utenti di aggiungere alla cache le risorse e fare delle richieste con questo valore
aggiungendo un If-None-Match
header per determinare se la cache debba essere aggiornata o meno.
Fornisci un parametro Request-Ids per l'analisi
Includi un parametro Request-Id
nell'header per ogni risposta della API, popolato con un valore UUID.
Facendo un log di questi valori nel client, server ed altri servizi ausiliari, è possibile ottenere un meccanismo di tracciabilità, diagnosi e debug delle richieste.
Dividi risposte molto lunghe in piu richieste con range
Risposte molto grandi, dovrebbero essere divise in più richieste usando un header Range
per specificare quando più dati sono disponibili e come recuperarli. Dai un'occhiata alla documentazione Heroku Platform API discussion of Ranges per i dettagli degli headers delle richieste e delle risposte, codici di stato, limiti, ordinamenti e iterazioni
Richieste
La sezione delle richieste fornisce una panoramica della struttura per le richieste API.
Accetta JSON serializzato nei corpi delle richieste
Accetta JSON serializzato nei corpi delle richieste PUT
/PATCH
/POST
oppure in aggiunta ai dati form-encoded. Questo crea simmetria con i il corpo JSON serializzato delle risposte, es:
$ curl -X POST https://service.com/apps \
-H "Content-Type: application/json" \
-d '{"name": "demoapp"}'
{
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "demoapp",
"owner": {
"email": "[email protected]",
"id": "01234567-89ab-cdef-0123-456789abcdef"
},
...
}
Nomi delle risorse
Usa il nome plurale di un nome di risorsa a meno che la risorsa in questione non sia un nome singolare relativo al sistema stesso (per esempio in alcuni sistemi un determinato utente può avere un solo account). Questo mantiene la consistenza quando ti riferisci alle risorse
Azioni
Preferisci dei layout per gli endpoint che non prevedono azioni speciali per una determinata risorsa.
Nel caso in cui sia necessario prevedere la possibilità di azioni speciali, specificale sotto un prefisso standard come actions
, per definirle in modo chiaro:
/resources/:resource/actions/:action
es.
/runs/{run_id}/actions/stop
Usa un formato consistente per i percorsi (path)
Caratteri minuscoli per percorsi e attributi
Usa dei percorsi in minuscolo e separati da trattini, per congruenza con gli hostnames, es:
service-api.com/users
service-api.com/app-setups
Anche per gli attributi utilizza il minuscolo, ma separando le parole con un underscore, in modo che possano essere scritti senza l'utilizzo delle virgolette in JavaScript, es:
service_class: "first"
Supporta anche riferimenti non-id per comodita
In alcuni casi potrebbe essere scomodo per gli utenti finali, fornire ID per identificare una risorsa. Per esempio, un utente può pensare in termini del nome di una app, ma questa app può essere identificata tramite un UUID. In questi casi, potresti prevedere di accettare entrambi: ID e nome, es:
$ curl https://service.com/apps/{app_id_or_name}
$ curl https://service.com/apps/97addcf0-c182
$ curl https://service.com/apps/www-prod
Non accettare comunque soltanto un nome, senza la possibilità di specificare un ID
Minimizza annidamento dei percorsi
Nei model che rappresentano i nostri dati con le relazioni padre/figlio annidate, i percorsi possono diventare veramente molto lunghi, es:
/orgs/{org_id}/apps/{app_id}/dynos/{dyno_id}
Limita l'annidamento, preferendo la localizzazione delle risorse nella radice del persorso. Usa l'annidamento per indicare una raccolta di elementi. Per esempio, per il caso sopra, dove dyno dipende da app che dipende da org:
/orgs/{org_id}
/orgs/{org_id}/apps
/apps/{app_id}
/apps/{app_id}/dynos
/dynos/{dyno_id}
Risposte
La sezione delle risposte fornisce una panoramica sui pattern da utilizzare per le risposte della API
Ritorna sempre un codice di stato appropriato
Ritorna un codice di stato HTTP appropriato per ogni risposta. Le risposte con esito positivo, dovrebbero essere accompagnate da codici di stato come di seguito:
200
: Richiesta completata con successo per una chiamata (sincrona)GET
,DELETE
oPATCH
, oppure per una chiamataPUT
(sincrona) che ha completato l'update di una risorsa201
: Richiesta completata con successo per una chiamata (sincrona)POST
, oPUT
che ha creato una nuova risorsa202
: Richiesta completata con successo per una chiamataPOST
,PUT
,DELETE
, oPATCH
che viene processata in modo asincrono206
: Richiesta completata con successo per una chiamataGET
, dove pero viene restituita una risposta parziale: vedi Dividi risposte molto lunghe in piu richieste con range
Fai molta attenzione all'utilizzo dei codici di errore per l'autenticazione e l'autorizzazione:
401 Unauthorized
: Richiesta fallita per utente non autenticato403 Forbidden
: Richiesta fallita perchè l'utente non è autorizzato ad accedere ad una risorsa
Ritorna codici di errore adatti fornendo informazioni aggiuntive sul tipo di errore:
422 Unprocessable Entity
: La tua richiesta è stata compresa, ma contiene dei parametri non validi.429 Too Many Requests
: Hai superato il limite di richieste, riprova più tardi500 Internal Server Error
: Qualcosa è andato storto, controlla lo stato del sito/servizio ed eventualmente segnala l'errore
Fai sempre riferimento alle specifiche HTTP response code spec per i codici di stato e per gli errori
Fornisci le risorse interamente, quando possibile
Fornisci la rappresentazione dell'intera risorsa (es. oggetto con tutti gli attributi)
quando possible nella risposta. Fornisci sempre la risorsa completa con un codice 200 o 201, incluse nelle richieste
PUT
/PATCH
e DELETE
, es:
$ curl -X DELETE \
https://service.com/apps/1f9b/domains/0fd4
HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
...
{
"created_at": "2012-01-01T12:00:00Z",
"hostname": "subdomain.example.com",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"updated_at": "2012-01-01T12:00:00Z"
}
le risposte con codice 202 non includono l'intera rappresentazione della risorsa, es:
$ curl -X DELETE \
https://service.com/apps/1f9b/dynos/05bd
HTTP/1.1 202 Accepted
Content-Type: application/json;charset=utf-8
...
{}
Fornisci gli (UU)IDs delle risorse
Assegna ad ogni risorssa un attributo id
di default. Usa gli UUIDs a meno che tu
non abbia una buona ragione per non farlo. Non usare gli IDs perchè non sono globalmente
unici, specialmente quando si hanno più instanze del servizio o delle risorse nel servizio, specialmente
gli IDs auto-incrementali.
Visualizza gli UUIDs in formato 8-4-4-4-12
minuscolo, es:
"id": "01234567-89ab-cdef-0123-456789abcdef"
Fornisci dei timestamps standard
Fornisci di default i timestamp created_at
e updated_at
per le risorse, es:
{
// ...
"created_at": "2012-01-01T12:00:00Z",
"updated_at": "2012-01-01T13:00:00Z",
// ...
}
Questi timestamp possono non avere senso per alcune risorse, in questo caso possono essere omessi.
Usa date in formato UTC formattate in ISO8601
Accetta e ritorna le date solo in formato UTC. Visualizza le date nel formato ISO8601, es:
"finished_at": "2012-01-01T12:00:00Z"
Annida relazioni tramite le chiavi esterne (foreign-key)
Serializza i riferimenti delle chiavi esterne con un oggetto annidato, es:
{
"name": "service-production",
"owner": {
"id": "5d8201b0..."
},
// ...
}
Invece di, es:
{
"name": "service-production",
"owner_id": "5d8201b0...",
// ...
}
Questo approccio rende possibile la visualizzazione di più informazioni sulla risorsa in questione senza dover cambiare la struttura della risposta o introdurre altri campi nella risposta stessa, es:
{
"name": "service-production",
"owner": {
"id": "5d8201b0...",
"name": "Alice",
"email": "[email protected]"
},
// ...
}
Genera errori strutturati
Genera i corpi di risposta degli errori in modo consistente e strutturato.
Includi un riferimento id
all'errore in modo che sia leggibile da una macchina, e un
campo message
che sia comprensible all'utente e, opzionalmente un campo url
che porta
l'utente ad una descrizione più dettagliata dell'errore in questione e come risolverlo, es:
HTTP/1.1 429 Too Many Requests
{
"id": "rate_limit",
"message": "Account reached its API rate limit.",
"url": "https://docs.service.com/rate-limits"
}
Documenta il formato dell'errore e il possibile error id
che l'utente può incontrare.
Visualizza lo stato del limite delle richieste
Misura i limiti di richieste dai client per proteggere la stabilità del servizio e mantenere una qualita altà per gli altri clients. Puoi usare un token bucket algorithm per misurare e monitorare il limite delle richieste
In questo caso, ritorna il numero di richieste rimanenti in ogni richiesta nell'header
RateLimit-Remaining
.
Mantieni il JSON minimizzato in tutte le risposte
Gli spazi bianchi extra aumentano inutilmente la dimensione delle richieste/risposte, inoltre molti clients adottano automaticamente processi di "prettify" sulle risposte JSON. Rimane una scelta migliore mantenere le risposte JSON minimizzate, es:
{"beta":false,"email":"[email protected]","id":"01234567-89ab-cdef-0123-456789abcdef","last_login":"2012-01-01T12:00:00Z","created_at":"2012-01-01T12:00:00Z","updated_at":"2012-01-01T12:00:00Z"}
Invece di, es:
{
"beta": false,
"email": "[email protected]",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"last_login": "2012-01-01T12:00:00Z",
"created_at": "2012-01-01T12:00:00Z",
"updated_at": "2012-01-01T12:00:00Z"
}
Puoi considerare di fornire opzionalmente ai clients l'opportunità di recuperare delle risposte
più verbose (es. ?pretty=true
) o utilizzando un header Accept
(es. Accept: application/vnd.heroku+json; version=3; indent=4;
).
Artefatti
La sezione artefatti descrive gli oggetti che vengono utilizzati per gestire la progettazione delle API
Fornisci uno schema JSON interpretabile
Fornisci uno schema JSON interpretabile per descrivere in maniera formale e precisa la tua API.
Puoi usare prmd per gestire lo schema e assicurarti della sua
validità usando il comando prmd verify
.
Fornisci una documentazione comprensibile allo sviluppatore
Fornisci una documentazione comprensibile che lo sviluppatore e i clients possono consultare per usare la tua API.
Se crei uno schema utilizzando prmd come descritto sopra, potrai facilmente generare un documento in formato
Markdown per tutti gli endpoints usando il comando prmd doc
.
Oltre alle specifiche degli endpoints, fornisci una panoramica della API con le seguenti informazioni:
- Autenticazione, specificando come ottenere ed usare un access token
- Stabilità della API e versionamento, specificando come scegliere la versione della API
- I comuni headeer delle richieste e delle risposte
- Errori in formato serializzato
- Esempi di utilizzo della tua API in diversi linguaggi
Fornisci degli esempi
Fornisci dei semplici esempi eseguibili che gli utenti possano provare velocemente tramite i loro terminali, per vedere il funzionamento delle chiamate alle API. Per essere più chiaro possibile, descrivi gli esempi in modo molto dettagliato, per diminuire in maniera considerevole il lavoro dell'utente che dovrà provare ed usare l'API, es:
$ export TOKEN=... # acquire from dashboard
$ curl -is https://$TOKEN@service.com/users
Se usi prmd per generare la documentazione in formato Markdown, otterrai automaticamente degli esempi per ogni endpoint.
Specifica la stabilità della tua API
Specifica la stabilità della tua API oppure dei vari endpoints per quanto riguarda la maturità e la stabilità della stessa, es. tramite dei flag prototype/development/production.
Dai un'occhiata a Heroku API compatibility policy per possibili cambiamenti nella stabilità o nella gestione delle policy.
Una volta che la tua API è pronta per andare in produzione ed è stabile, non fare cambiamenti non retrocompatibili. Se hai bisogno di fare dei cambiamenti non retrocompatibili, crea una nuova API con un nuovo numero di versione.