API REST: come identificare una risorsa? ..con gli UUID
7 minuti di lettura
Quando si progettano API REST il concetto di risorsa è fondamentale. Uno dei problemi principali che gli sviluppatori si pongono è infatti come fare ad identificare correttamente una risorsa. In questo articolo esploro le tecniche più comuni, con pro e contro di ognuna. Infine spiego il mio approccio preferito, che da sia flessibilità ma anche stabilità in contesti multi ambiente.
TLDR: se siete impazienti ecco i punti principali
- Un'API REST può esporre le risorse utilizzando UUID come identificatori, in questo modo si evita all'API di essere facilmente enumerabile (pagando un pò di spazio in più per la memorizzazione).
- Se la risorsa ha chiavi logiche ben definite, conviene utilizzare l'UUID-V3 generato proprio dalla chiave logica.
- Se la risorsa non è identificata da chiavi logiche si può usare un UUID random, come l'UUID-V4
- I servizi Backend interni dovrebbero usare e memorizzare anche la primary key immutabile (magari numerica ed auto-generata dal DB se il sistema è relazionale)
1. Chiavi Primarie auto-generate dal Database
Questo è uno degli identificatori più usati, specialmente se la risorsa viene trattata lato backend come una riga di un database relazione (o comune).
Sicuramente è il modo pià facile per identificare la risorsa REST, dato che il database ne garantisce l'unicità e immutabilità (si spera).
Troverete questi ID numerici sicuramente in servizi REST derivati da sistemi legacy o monolitici: per esempio una vecchia app che ha bisogno di una GUI più moderna o di esporre dati a terze parti. Di solito la risorsa assomiglierà ad un'URI del genere:
https://music.app/song/1
Di solito le risorse (canzoni in questo caso) sono in corrispondenza biunivoca con le righe di una tabella di un database relazionale. Nell'esempio
riusciamo ad identificare la risorsa con l'id 1
.
Vantaggi
- Molto semplice, l'identificativo corrisponde di solito alla primary key e può essere auto-generato.
- L'ID è obbligatorio, ed immutabile. Non è editabile, e questo è veramente un grande vantaggio.
Svantaggi
- Potrebbe non essere appropriato (da solo) per sistemi distribuiti (o almeno se non corredato dal tipo di risorsa)
- In contesti multi ambiente (es: dev, staging, produzione) la sincronizzazione è molto difficile. Avrete sempre ambienti con configurazioni dove magari la chiave logica è la stessa ma la chiave primaria no (perché auto-generata). In situazioni dove gli ambienti sono il punto di testing e UAT per i vostri clienti, sarà un inferno migrare dati e/o configurazioni (fidatevi, ci sono passato)
- Dal punto di vista della sicurezza questa soluzione apre a scenari di enumerazione. Basta infatti cambiare l'identificatore numerico dell'API per scoprire ed enumerare le altre risorse.
2. Usare la Chiave Logica
Un'altra tecnica molto utilizzata è quella di identificare la risorsa REST con chiave logica. Di solito questo funziona molto bene per le risorse che possono essere trattate come un dominio finito.
Ad esempio i giorni della settimana, i nomi delle nazioni o le province italiane. Quindi delle buone risorse potrebbero assomigliare a:
https://my.app/weekday/monday
https://my.app/country/italy
Vantaggi
- Interpretabile facilmente per utenti e sviluppatori delle API. L'identificatore suggerisce anche il contenuto della risorsa.
- Più facile da sincronizzare tra i vari ambienti. Non ci sono sovrapposizioni di ID, o riferimenti errati.
- La chiave logica può anche essere usata come rotta (route) nelle web app che fanno uso della risorsa (ad esempio una pagina di dettaglio sui dati esposti dall'API).
Svantaggi
- Dobbiamo garantire l'univocità. Il backend quindi diventa più sofisticato, se sono permesse le modifiche.
- Non è adatta (da sola) a sistemi relazionali, se hai intenzione di usarla come chiave primaria. A meno che il dominio non sia chiuso e garantita l'immutabilità.
- Di solito le chiavi logiche sono mutabili. Quando modifichi una risorsa, e cambi la chiave logica, devi pensare a modificare anche tutti i riferimenti (FK).
- Quando la chiave logica è composta, le cose si complicano. L'identificativo della risorsa deve essere generato a partire da più campi.
Quindi nella vita reale, è possibile trovare API REST che espongono le loro risorse mediante chiavi logiche, ma di solito lato backend vengono usate in ogni caso chiavi primarie univoche e immutabili per non avere problemi nei riferimenti e/o degradare le performance.
3. Chiavi logiche e UUID: memorizza la chiave univoca (PK), esponi un UUID-V3
Questa è la migliore opzione che mi sento di consigliare, e che uso sempre anche nelle mie API e nei servizi di cui sono responsabile.
- In generale, uso sempre chiavi primarie auto-generate magari dal database lato backend. Quindi i riferimenti (FK) sono sempre numerici e immutabili.
- Poi "decoro" uan risorsa con una chiave logica. Per esempio un codice o un nome, di solito stringa senza spazi o caratteri particolari. Per risorse complesse ricorro ad una chiave logica composta, concatenando i campi utili.
- Tutte le API REST che espongo, hanno come identificato un UUID-V3, generato a partire dal tipo di risorsa e dalla chiave logica.
UUID-V3 sono identificatori unici ed universali generati a partire dall'hash MD5 di un namespace e di un name (due stringhe). Di solito il namespace è a sua volta un altro UUID che identifica in modo univoco il servizio o l'app mentre il name è formato dalla tipologia di risorsa + la chiave logica.
Per esempio immagina un'applicazione che identifica gli utenti mediante username. Potresti esporre sia la chiave logica (username) e l'UUID-V3:
https://my.app/users/andrea
https://my.app/users/92068a6a-822a-3865-9970-bd4bc3e96f2c
// NB. the UUID è sempre lo stesso per l'utente andrea. Ho usato
// la stringa user_andrea come chiave logica
// https://uuidonline.com/?version=3&namespace=mysystem_andrea
Vantaggi
- Il Backend usa una chiave primaria PK numerica, generata magari da un DB relazionale ed in modo seriale.
- UUID-V3 sono ideali per la sincronizzazione verso l'esterno o altri ambienti. A partire dalla chiave logica l'UUID-V3 è sempre lo stesso. Si riesce subito ad identificare la risorsa, anche se tra ambienti diversi la chiave primaria cambia.
- Puoi esporre sia un UUID-V3 (per risorse complesse) che la chiave logica (per risorse su domini chiusi). Puoi switchare tra i due a seconda del bisogno. Per esempio la GUI potrebbe usare una chiave logica.
Svantaggi
- Il Backend è più sofisticato. Potresti dover esporre sia le chiavi logiche che gli UUID.
- Quando aggiorni la chiave logica (se permesso dal tuo sistema) hai bisogno di modificare anche l'UUID. Ma dato che la chiave primaria (numerica auto-generata) non cambia, questa operazioni non è onerosa.
4. UUID Random: Se non ho una chiave logica allora usiamo UUID-v4
Capita molto spesso che le risorse non abbiamo una vera chiave logica associata. Ad esempio l'identificativo di una transazione, o di una sessione utente su una web app. In questo caso se lato backend si utilizza un sistema relazionale, la via più semplice è generare un id univoco auto-incrementato, come abbiamo visto al punto 2.
Però se non vogliamo legarci all'id di un database, o magari non stiamo usando un sistema tradizionale può avere senso generare un UUID randomico e in questo caso ci viene in aiuto la versione UUID-V4. Ad esempio:
https://my.app/orders/92068a6a-822a-1865-9870-ad4bc3e96f2a
Vantaggi
- Molto semplice da implementare. L'UUID è random ed associato ad una singola risorsa.
- L'approccio è ottimo se si vuole evitare l'enumerazione delle risorse da parte di chi utilizza l'API.
Svantaggi
- La sincronizzazione tra ambienti diversi diventa difficile. Non abbiamo un modo per identificare se una risorsa è diversa da un'altra se gli ambienti generano UUID random e non ci sono chiavi logiche.
- Se l'UUID è l'unica chiave primaria dovete fare attenzione a costruire indici complessi o join nei database tradizionali, le performance sicuramente sono inferiori rispetto a chiavi primarie numeriche e facilmente indicizzabili.
Anche qui se dovete usare questo approccio vi consigli lato backend di usare in ogni caso chiavi primarie univoche e immutabili per non avere problemi nei riferimenti e/o degradare le performance.
Per concludere
- Gli UUID sono un ottimo approccio per identificare risorse REST, specialmente se non vi ponete problemi relativi allo spazio necessario alla loro memorizzazione e se li utilizzate solo come interfaccia verso l'esterno.
- Avete a disposizione diverse versioni di UUID, fate attenzione a scegliere quella che più si adatta al vostro caso d'uso.
- Se potete, ed ha senso, lato backend utilizzate comunque una chiave primaria numerica, ed utilizzate gli UUID solo come identificatore verso l'esterno.