REST è il nuovo SAPONE

Scritto da Pascal Chambon, recensito da Raphaël Gomès

Aggiornamento: questo articolo tratta principalmente dell'ecosistema RESTish, che ora costituisce una parte importante dei servizi web. Per un'analisi più approfondita del REST originale e di HATEOAS, vedere il mio articolo di follow-up.

Come mai REST significa tanto LAVORO?
Questo è sia un paradosso, sia un gioco di vergogna.

Tuffiamoci ulteriormente nei problemi artificiali nati da questa filosofia progettuale.

ATTENZIONE: attraverso questo documento, troverai molte domande tecniche semi-retoriche. Non fraintenderli, NON significano che i servizi web RESTish non possono risolvere questi problemi. Significano solo che gli utenti hanno un onere aggiuntivo di decisioni da prendere, di estensioni da integrare, di soluzioni personalizzate da applicare, e questo è un problema in sé.

La gioia dei verbi REST

Il riposo non è CRUD, i suoi sostenitori faranno in modo di non confondere questi due. Tuttavia pochi minuti dopo gioiranno del fatto che i metodi HTTP hanno semantiche ben definite per creare (POST), recuperare (GET), aggiornare (PUT / PATCH) ed eliminare (DELETE) risorse.

Saranno lieti di professare che questi pochi "verbi" sono sufficienti per esprimere qualsiasi operazione. Beh, certo che lo sono; allo stesso modo in cui una manciata di verbi sarebbe sufficiente per esprimere qualsiasi concetto in inglese: "Oggi ho aggiornato CarDriverSeat con il mio corpo e creato un EngineIgnition, ma FuelTank si è cancellato da solo"; essere possibile non lo rende meno imbarazzante. A meno che tu non sia un ammiratore della lingua Toki Pona.

Se il punto deve essere minimalista, almeno lascia che sia fatto bene. Sai perché PUT, PATCH e DELETE non sono mai stati implementati nei moduli del browser web? Perché sono inutili e dannosi. Possiamo semplicemente usare GET per la lettura e POST per la scrittura. O POST esclusivamente, quando la memorizzazione nella cache a livello HTTP è indesiderata. Altri metodi ti ostacolano al meglio, nel peggiore dei casi ti rovinano la giornata.

Vuoi usare PUT per aggiornare la tua risorsa? OK, ma alcune Specifiche Sacre affermano che l'immissione dei dati deve essere equivalente alla rappresentazione ricevuta tramite un GET. Quindi cosa fai con i numerosi parametri di sola lettura restituiti da GET (ora di creazione, ora dell'ultimo aggiornamento, token generato dal server ...)? Li ometti e violi i principi PUT? Li includi comunque e ti aspetti un "conflitto HTTP 409" se non corrispondono ai valori sul lato server (costringendoti a emettere un GET ...)? Dai loro valori casuali e ti aspetti che i server li ignorino (la gioia degli errori silenziosi)? Scegli il tuo veleno, REST chiaramente non ha idea di quale attributo sia di sola lettura e questo non sarà risolto in qualunque momento presto. Nel frattempo, un GET dovrebbe pericolosamente restituire la password (o il numero di carta di credito) che è stato inviato in un precedente POST / PUT; buona fortuna anche con questi parametri di sola scrittura.

Ho dimenticato di menzionare che PUT porta anche condizioni di gara pericolose, in cui diversi clienti avranno la precedenza sulle modifiche reciproche, mentre volevano solo aggiornare campi diversi?

Vuoi usare PATCH per aggiornare la tua risorsa? Bello, ma come il 99% delle persone che usano questo verbo, invierai solo un sottoinsieme di campi di risorse nel payload della tua richiesta, sperando che il server comprenda correttamente l'operazione prevista (e tutti i suoi possibili effetti collaterali); molti parametri delle risorse sono profondamente collegati o si escludono a vicenda (ad es. si tratta di una carta di credito o di un token paypal, nelle informazioni di fatturazione di un utente), ma il design RESTful nasconde anche queste informazioni importanti. Ad ogni modo, violeresti ancora una volta le specifiche: PATCH non dovrebbe semplicemente inviare un sacco di campi per essere ignorato. Dovresti invece fornire un "set di istruzioni" da applicare alle risorse. Quindi eccoti di nuovo qui, prendi il tuo cartone e la tua tazza di caffè, dovrai decidere come esprimere queste istruzioni. Spesso con specifiche artigianali, poiché la sindrome non inventata qui è uno standard di fatto nel mondo REST. (Modifica: i sostenitori del REST hanno retrocesso su questo argomento, con Json Merge Patch, un'alternativa a formati come Json Patch)

Vuoi ELIMINARE risorse? OK, ma spero che non sia necessario fornire dati contestuali sostanziali; come una scansione PDF della richiesta di terminazione da parte dell'utente. DELETE proibisce di avere un payload. Un vincolo che gli architetti REST spesso respingono, poiché la maggior parte dei server web non applica questa regola alle richieste che riceve. Quanto sarebbe compatibile, comunque, una richiesta DELETE con 2 MB di stringa di query base64 collegata? (Modifica: RFC 2616, che indica che i payload senza semantica dovrebbero essere ignorati, è ora obsoleto)

Gli appassionati di REST affermano facilmente che "le persone stanno sbagliando" e che le loro API non sono "in realtà RESTful". Ad esempio, molti sviluppatori usano PUT per creare una risorsa direttamente sul suo URL finale (/ myresourcebase / myresourceid), mentre il "buon modo" (modifica: secondo molti) di farlo è POST su un URL principale (/ myresourcebase ) e lascia che il server indichi, con un'intestazione HTTP "Posizione", l'URL della nuova risorsa (modifica: non è comunque un reindirizzamento HTTP). La buona notizia è: non importa. Questi principi rigorosi sono come Big Endian vs Little Endian, occupano i filosofi per ore, ma hanno un impatto minimo sui problemi della vita reale, vale a dire "fare cose".

A proposito ... la creazione manuale di URL è sempre molto divertente. Sai quante implementazioni identificano correttamente gli identificatori urlencode () durante la creazione degli URL REST? Non così tanti. Preparati per brutte rotture e attacchi SSRF / CSRF.

Quando ti dimentichi di urlencode i nomi utente in 1 dei tuoi 30 URL artigianali.

La gioia della gestione degli errori REST

Su ogni programmatore è in grado di far funzionare un "caso nominale". La gestione degli errori è una di queste funzionalità che deciderà se il tuo codice è un software affidabile o una grande pila di fiammiferi.

HTTP fornisce un elenco di codici di errore pronti all'uso. Fantastico, vediamo.

L'uso di "HTTP 404 non trovato" per avvisare di una risorsa inesistente sembra RESTful come diamine, non è vero? Peccato: il tuo nginx è stato configurato in modo errato per 1 ora, quindi i tuoi consumatori API hanno ricevuto solo 404 errori e hanno eliminato centinaia di account, pensando che fossero stati eliminati….

I nostri clienti, dopo aver eliminato per errore i loro gigabyte di immagini di gattini.

L'uso di "HTTP 401 non autorizzato" quando un utente non ha credenziali di accesso a un servizio di terze parti sembra accettabile, non è vero? Tuttavia, se una chiamata Ajax nel browser Safari riceve questo codice di errore, potrebbe far sussultare il cliente finale con una richiesta di password molto inaspettata [lo ha fatto, anni fa, YMMV].

HTTP esisteva molto prima di "RESTful webservices" e l'ecosistema web è pieno di ipotesi sul significato dei suoi codici di errore. Utilizzarli per trasportare errori di applicazione è come usare le bottiglie di latte per smaltire i rifiuti tossici: inevitabilmente, un giorno, ci saranno problemi.

Alcuni codici di errore HTTP standard sono specifici di Webdav, altri di Microsoft e i pochi rimanenti hanno definizioni così confuse da non essere di alcun aiuto. Alla fine, come la maggior parte degli utenti REST, probabilmente utilizzerai codici HTTP casuali, come "HTTP 418 I am a teapot" o numeri non assegnati, per esprimere le eccezioni specifiche dell'applicazione. Oppure restituirai spudoratamente "HTTP 400 Bad Request" per tutti gli errori funzionali, quindi inventerai il tuo formato di errore goffo, con booleani, codici interi, slug e messaggi tradotti inseriti in un payload arbitrario. Oppure ti arrenderai del tutto sulla corretta gestione degli errori; restituirai semplicemente un messaggio, in linguaggio naturale, e speri che il chiamante sia un essere umano in grado di analizzare il problema e agire. Buona fortuna interagendo con tali API da un programma autonomo.

La gioia dei concetti REST

REST ha fatto carriera vantandosi di concetti che qualsiasi architetto di servizi nella sua mente giusta già rispetta, o di principi che non segue nemmeno. Ecco alcuni estratti, presi da pagine Web di alto livello.

REST è un'architettura client-server. Il client e il server hanno entrambi una serie diversa di preoccupazioni. Che scoop nel mondo del software.

REST fornisce un'interfaccia uniforme tra i componenti. Bene, come qualsiasi altro protocollo, quando viene applicato come lingua franca di un intero ecosistema di servizi.

REST è un sistema a strati. I singoli componenti non possono vedere oltre il livello immediato con cui interagiscono. Sembra una conseguenza naturale di qualsiasi architettura ben progettata, liberamente accoppiata; Stupefacente.

Il riposo è fantastico, perché è SENZA TEMPO. Sì, c'è probabilmente un enorme database dietro il servizio web, ma non ricorda lo stato del client. O, sì, sì, in realtà ricorda la sua sessione di autenticazione, i suoi permessi di accesso ... ma è comunque apolide. O più precisamente, altrettanto apolidi di qualsiasi protocollo basato su HTTP, come il semplice RPC menzionato in precedenza.

Con REST, puoi sfruttare la potenza di HTTP CACHING! Bene, ecco finalmente un punto conclusivo: una richiesta GET e le sue intestazioni di controllo della cache sono davvero amichevoli con le cache web. Detto questo, le cache locali (Memcached ecc.) Non sono sufficienti per il 99% dei servizi web? Le cache fuori controllo sono bestie pericolose; quante persone vogliono esporre le loro API in chiaro, in modo che una vernice o un proxy in viaggio possano continuare a fornire contenuti obsoleti, molto tempo dopo che una risorsa è stata aggiornata o cancellata? Forse anche consegnandolo "per sempre", se si è verificato un errore di configurazione? Un sistema deve essere sicuro per impostazione predefinita. Ammetto perfettamente che alcuni sistemi fortemente caricati vogliono beneficiare della memorizzazione nella cache HTTP, ma costa molto meno esporre alcuni endpoint GET per interazioni di sola lettura pesanti, piuttosto che passare tutte le operazioni a REST e alla sua dubbia gestione degli errori.

Grazie a tutto ciò, REST ha ALTE PRESTAZIONI! Ne siamo sicuri? Qualunque designer API lo sappia: localmente, vogliamo API a grana fine, per essere in grado di fare quello che vogliamo; e da remoto, vogliamo API a grana grossa, per limitare l'impatto dei round trip di rete. Ecco di nuovo un dominio in cui il REST "di base" fallisce miseramente. La suddivisione dei dati tra "risorse", ciascuna istanza sul proprio endpoint, porta naturalmente al problema della query N + 1. Per ottenere i dati completi di un utente (account, abbonamenti, informazioni di fatturazione ...), è necessario emettere tutte le richieste HTTP; e non puoi parallelizzarli, dal momento che non conosci in anticipo gli ID univoci delle risorse correlate. Questo, oltre all'incapacità di recuperare solo parte degli oggetti delle risorse, crea naturalmente brutti colli di bottiglia (modifica: sì, puoi aggiungere estensioni come Documenti composti / parziali nella tua installazione per aiutarti).

REST offre una migliore compatibilità. Come mai? Perché così tanti servizi web REST hanno "/ v2 /" o "/ v3 /" nei loro URL di base? Le API compatibili con le versioni precedenti e successive non sono difficili da ottenere, con linguaggi di alto livello, purché vengano seguite semplici regole quando si aggiungono / deprezzano i parametri. Per quanto ne so, REST non porta nulla di nuovo sull'argomento.

REST è SEMPLICE, tutti conoscono HTTP! Bene, tutti conoscono anche i ciottoli, ma le persone sono felici di avere blocchi migliori quando costruiscono la loro casa. Allo stesso modo in cui XML è un meta-linguaggio, HTTP è un meta-protocollo. Per avere un vero protocollo applicativo (come i "dialetti" sono in XML), dovrai specificare molte cose; e finirai con l'ennesimo protocollo RPC, come se non ce ne fossero già abbastanza.

REST è così semplice, può essere interrogato da qualsiasi shell, con CURL! OK, in realtà, ogni protocollo basato su HTTP può essere interrogato con CURL. Perfino SAPONE. L'emissione di un GET è particolarmente semplice, certo, ma buona fortuna con la scrittura manuale di payload JSON o xml POST; le persone di solito usano file di fixture o, molto più pratici, client API a tutti gli effetti istanziati direttamente nell'interfaccia della riga di comando della loro lingua preferita.

"Il cliente non ha bisogno di alcuna conoscenza preliminare del servizio per poterlo utilizzare". Questa è di gran lunga la mia citazione preferita. L'ho trovato numerose volte, in forme diverse, soprattutto quando la parola d'ordine HATEOAS si nascondeva in giro; a volte con alcune frasi "tranne" attente (ma insufficienti) che seguono. Tuttavia, non so in quale mondo fantastico vivono queste persone, ma in questo un programma client non è una colonia di formiche; non sfoglia le API remote in modo casuale e quindi decide come gestirle al meglio, in base al riconoscimento di modelli o alla magia nera. Piuttosto il contrario; il cliente ha grandi aspettative su cosa significhi mettere questo campo su questo URL con questo valore, e il server dovrebbe rispettare meglio il semantico concordato durante l'integrazione, altrimenti potrebbe scatenarsi l'inferno.

Quando chiedi come dovrebbe funzionare HATEOAS.

Come fare REST giusto e veloce?

Dimentica la parte "giusta". Il riposo è come una religione, nessun semplice mortale potrà mai comprendere l'estensione del suo genio, né "farlo bene".

Quindi la vera domanda è: se sei costretto a esporre o consumare servizi web in un modo RESTful, come correre attraverso questo lavoro e passare ad attività più costruttive al più presto?

Aggiornamento: si scopre che in realtà ci sono molti "standard" e sforzi di industrializzazione per REST, anche se non li avevo mai incontrati personalmente (forse perché poche persone li usano?). Maggiori informazioni nel mio articolo di follow-up.

Come industrializzare l'esposizione sul lato server?

Ogni framework Web ha il suo modo di definire l'endpoint URL. Quindi aspettati alcune grandi dipendenze, o un buon livello di boilerplate scritto a mano, per collegare l'API esistente al tuo server preferito come set di endpoint REST.

Librerie come Django-Rest-Framework automatizzano la creazione di API REST, agendo come wrapper incentrati sui dati al di sopra degli schemi SQL / noSQL. Se vuoi solo fare "CRUD su HTTP", potresti stare bene con loro. Ma se vuoi esporre API comuni "fai-per-me", con flussi di lavoro, vincoli, impatti di dati complessi e simili, avrai difficoltà a piegare qualsiasi framework REST per soddisfare le tue esigenze.

Prepararsi a connettere, uno per uno, ciascun metodo HTTP di ciascun endpoint, alla chiamata del metodo corrispondente; con una buona dose di gestione delle eccezioni fatta a mano, per tradurre le eccezioni passanti nei corrispondenti codici di errore e payload.

Come industrializzare l'integrazione lato client?

Per esperienza, suppongo che non lo sia.

Per ogni integrazione API, dovrai consultare lunghi documenti e seguire ricette dettagliate su come eseguire ciascuna delle N possibili operazioni.

Dovrai creare manualmente gli URL, scrivere serializzatori e deserializzatori e imparare a risolvere le ambiguità dell'API. Aspettati un bel po 'di tentativi ed errori prima di domare la bestia.

Sai come compensano i provider di servizi Web e facilitare l'adozione?

Semplice, scrivono le loro implementazioni client ufficiali.

PER. OGNI. MAGGIORE. LINGUAGGIO. E. PIATTAFORMA.

Di recente mi sono occupato di un sistema di gestione delle iscrizioni. Forniscono client per PHP, Ruby, Python, .NET, iOS, Android, Java ... oltre ad alcuni contributi esterni per Go e NodeJS.

Ogni client vive nel proprio repository Github. Ognuno con il proprio grande elenco di commit, ticket di tracciamento dei bug e richieste pull. Ognuno con i propri esempi di utilizzo. Ognuno con la propria architettura scomoda, a metà strada tra ActiveRecord e proxy RPC.

Questo è sorprendente. Quanto tempo è dedicato allo sviluppo di involucri così strani, invece di migliorare il servizio web reale, prezioso, da fare?

Sisifo sta sviluppando l'ennesimo client per la sua API.

Conclusione

Per decenni, quasi ogni linguaggio di programmazione ha funzionato con lo stesso flusso di lavoro: invio di input a un callable e ottenimento di risultati o errori come output. Questo ha funzionato bene. Discretamente.

Con Rest, questo si è trasformato in un folle lavoro di mappatura delle mele alle arance e elogiando le specifiche HTTP per violarle meglio pochi minuti dopo.

In un'era in cui i MICROSERVIZI sono sempre più comuni, come mai un compito così semplice - collegare librerie su reti - rimane così artificiosamente ingombrante e ingombrante?

Non dubito che alcune persone intelligenti là fuori forniranno casi in cui REST brilla; mostreranno il loro protocollo fatto in casa basato su REST, permettendo di scoprire e fare operazioni CRUD su alberi di oggetti arbitrari, grazie ai collegamenti ipertestuali; spiegheranno come il design REST sia così brillante, che non ho letto abbastanza articoli e tesi sui suoi concetti.

Non mi interessa. Gli alberi sono riconosciuti dai loro frutti. Ciò che mi ha richiesto alcune ore di programmazione e ha funzionato in modo molto efficace, con un semplice RPC, ora richiede settimane e non riesco a smettere di inventare nuovi modi di fallire o infrangere le aspettative. Lo sviluppo è stato sostituito da armeggiare.

La chiamata di procedura remota quasi trasparente era ciò di cui il 99% delle persone aveva veramente bisogno, ei protocolli esistenti, per quanto imperfetti, facevano il lavoro giusto. Questa monomania di massa per il minimo comune denominatore del web, HTTP, ha provocato principalmente un enorme spreco di tempo e materia grigia.

REST ha promesso semplicità e consegnato complessità.
REST ha promesso robustezza e fragilità.
REST ha promesso l'interoperabilità e l'eterogeneità fornita.
REST è il nuovo SAPONE.

Epilogo

Il futuro potrebbe essere brillante. Ci sono ancora tantissimi protocolli eccellenti disponibili, in formato binario o di testo, con o senza schema, alcuni che sfruttano le nuove capacità di HTTP2 ... quindi andiamo avanti, gente. Non possiamo rimanere per sempre nell'età della pietra dei servizi web.

Modifica: molte persone hanno chiesto questi protocolli alternativi, l'argomento meriterebbe la propria storia, ma si potrebbe dare un'occhiata a XMLRPC e JSONRPC (semplice ma abbastanza rilevante), o JSONWSP (include schemi), o livelli specifici del linguaggio come Pyro o RMI per uso interno o nuovi bambini nel blocco come GraphQL e gRPC per API pubbliche ...

Modificato il 12 dicembre 2017:

  • normalizzare i titoli delle sezioni
  • rimuovere alcuni errori di battitura
  • correggere la formulazione errata di "reindirizzamento HTTP" dopo le operazioni POST
  • aggiungere suggerimenti di protocolli alternativi

Modificato il 28 dicembre 2017:

  • risolto il mixup tra "metodi HTTP" e "verbi REST"

Modificato il 7 gennaio 2018

  • correggi parole ambigue

Modificato il 19 gennaio 2018

  • correggi le parole errate sui commenti "PUT vs GET"
  • precisa la nozione di "API reali" (non CRUD)
  • menzionare il rischio di override con PUT
  • aggiorna i paragrafi sui problemi PATCH e DELETE

Modificato il 19 gennaio 2018

  • correggi la formulazione della sindrome di Non-inventato-qui

Modificato il 2 febbraio 2018

  • aggiungere collegamenti all'articolo di follow-up su The Original REST, nei capitoli "introduzione" e "come industrializzare"

Modificato il 14 aprile 2019

  • aggiungere chiarimenti sulla "domanda semi-retorica" ​​e suggerimenti su estensioni come documenti composti / parziali

Modificato il 6 luglio 2019

  • correggi errori di battitura e collegamenti francesi