Modifiche significative cattive! Il versionamento delle API è buono!
Come chiunque abbia costruito o utilizzi regolarmente un'API realizza prima o poi, le modifiche significative sono molto dannose e possono rappresentare una seria macchia su un'API altrimenti utile. Una modifica significativa è un cambiamento nel comportamento di un'API che può interrompere l'integrazione di un utente e provocare molta frustrazione e perdita di fiducia tra il fornitore dell'API e l'utente. Le modifiche significative richiedono che gli utenti vengano avvisati in anticipo (con annessi mea culpa) piuttosto che con un cambiamento che si presenta all'improvviso, come una nuova funzionalità deliziosa. Il modo per evitare quella frustrazione è di versionare un'API con assicurazioni da parte del proprietario dell'API che non ci saranno cambiamenti sorprendenti introdotti all'interno di una singola versione.
Quindi, quanto può essere difficile versionare un'API? La verità è che non lo è, ma ciò che è difficile è mantenere un certo equilibrio evitando di scivolare in un numero vertiginoso di versioni e sottoversioni applicate su dozzine di endpoint API con compatibilità poco chiare.
Abbiamo introdotto la v1 dell'API tre anni fa e non ci siamo resi conto che saremmo stati in grado di mantenerla così a lungo. Quindi come abbiamo continuato a fornire la migliore API per la consegna delle email per oltre due anni mantenendo comunque la stessa versione API? Sebbene ci siano molte opinioni diverse su come versionare le REST API, spero che la storia della nostra umile ma potente v1 possa guidarti nel tuo percorso verso l'illuminazione del versionamento delle API.
REST è il migliore
La SparkPost API ha avuto origine quando eravamo Message Systems, prima delle nostre avventure nel cloud. All'epoca eravamo impegnati a fare le ultime preparazioni per il lancio beta di Momentum 4. Questo era un grande aggiornamento alla versione 3.x, il nostro MTA leader di mercato in locale. Momentum 4 includeva un'interfaccia utente completamente nuova, analisi in tempo reale e, soprattutto, una nuova API web per l'inserimento e la generazione dei messaggi, gestione dei template e ottenimento delle metriche email. La nostra visione era di un'architettura prima l'API – dove anche l'interfaccia utente avrebbe interagito con gli endpoint API.
Una delle prime e migliori decisioni che abbiamo preso è stata quella di adottare uno stile RESTful. Dalla fine degli anni 2000, il trasferimento di stato rappresentazionale (REST) basato su API web è lo standard de facto delle API cloud. Utilizzando HTTP e JSON è facile per gli sviluppatori, indipendentemente dal linguaggio di programmazione che utilizzano – PHP, Ruby e Java – integrarsi con la nostra API senza conoscere o preoccuparsi della nostra tecnologia sottostante.
Scegliere di utilizzare l'architettura RESTful è stato facile. Scegliere una convenzione di versionamento non è stato così semplice. Inizialmente abbiamo rimandato la questione del versionamento non versionando affatto la beta. Tuttavia, dopo un paio di mesi la beta era nelle mani di alcuni clienti e abbiamo iniziato a costruire il nostro servizio cloud. È ora di versionare. Abbiamo valutato due convenzioni di versionamento. La prima era di mettere il versionamento direttamente nell'URI e la seconda era di utilizzare un'intestazione Accept. La prima opzione è più esplicita e meno complicata, il che è più facile per gli sviluppatori. Poiché amiamo gli sviluppatori, è stata la scelta logica.
Governance API
Con una convenzione di versionamento selezionata avevamo più domande. Quando avremmo alzato la versione? Cosa costituisce una modifica significativa? Avremmo revisionato l'intera API o solo determinati endpoint? Presso SparkPost, abbiamo più team che lavorano su diverse parti della nostra API. All'interno di questi team, le persone lavorano su diversi endpoint in tempi diversi. Pertanto, è molto importante che la nostra API sia coerente nell'uso delle convenzioni. Questo era più grande del versionamento.
Abbiamo istituito un gruppo di governance composto da ingegneri rappresentanti di ciascun team, un membro del team di gestione del prodotto e il nostro CTO. Questo gruppo è responsabile dell'istituzione, documentazione e applicazione delle convenzioni API in tutti i team. Un canale Slack di governance API è anche utile per dibattiti vivaci sull'argomento.
Il gruppo di governance ha identificato diversi modi in cui possono essere introdotte modifiche all'API che sono vantaggiose per l'utente e non costituiscono una modifica significativa. Questi includono:
Una nuova risorsa o endpoint API
Un nuovo parametro opzionale
Una modifica a un endpoint API non pubblico
Una nuova chiave opzionale nel corpo POST JSON
Una nuova chiave restituita nel corpo di risposta JSON
Al contrario, un cambiamento significativo includeva qualsiasi cosa che potesse interrompere l'integrazione di un utente, come ad esempio:
Un nuovo parametro obbligatorio
Una nuova chiave obbligatoria nei corpi POST
Rimozione di un endpoint esistente
Rimozione di un metodo di richiesta dell'endpoint esistente
Un comportamento interno materialmente diverso di una chiamata API – come un cambiamento nel comportamento predefinito.
Il grande 1.0
Documentando e discutendo queste convenzioni, siamo arrivati anche alla conclusione che fosse nel migliore interesse di tutti (incluso il nostro!) evitare di apportare modifiche significative all'API poiché gestire più versioni comporta un bel po' di oneri. Abbiamo deciso che c'erano alcune cose da sistemare con la nostra API prima di impegnarci in "v1".
Inviare una semplice email richiedeva davvero troppa fatica. Per "mantenere le cose semplici" abbiamo aggiornato il corpo POST per garantire che siano soddisfatti sia i casi d'uso semplici che quelli complessi. Il nuovo formato era inoltre più a prova di futuro. In secondo luogo, abbiamo affrontato un problema con l'endpoint Metrics. Questo endpoint utilizzava un parametro "group_by" che cambiava il formato del corpo di risposta GET in modo tale che la prima chiave fosse il valore del parametro di raggruppamento. Questo non sembrava molto RESTful, quindi abbiamo diviso ogni raggruppamento in un endpoint separato. Infine, abbiamo esaminato ciascun endpoint e apportato piccole modifiche qua e là per garantire che fossero conformi agli standard.
Documentazione accurata
È importante avere una documentazione API accurata e utilizzabile per evitare modifiche significative, sia intenzionali che involontarie. Abbiamo deciso di utilizzare un approccio semplice alla documentazione API sfruttando un linguaggio Markdown chiamato API Blueprint e gestire la nostra documentazione in Github. La nostra comunità contribuisce e migliora questi documenti open source. Manteniamo anche un insieme non pubblico di documenti in Github per API e endpoint interni.
Inizialmente, abbiamo pubblicato i nostri documenti su Apiary, un ottimo strumento per il prototyping e la pubblicazione di documentazione API. Tuttavia, incorporare Apiary nel nostro sito web non funziona sui dispositivi mobili, quindi ora utilizziamo Jekyll per generare documenti statici. La nostra documentazione API di SparkPost ora si carica rapidamente e funziona bene sui dispositivi mobili, il che è importante per gli sviluppatori che non sono sempre seduti al computer.
Separare il deployment dal rilascio
Abbiamo imparato presto il prezioso trucco di separare un deployment da un rilascio. In questo modo è possibile apportare frequentemente modifiche quando sono pronte attraverso la consegna continua e il deployment, ma non annunciamo o documentiamo sempre tutto allo stesso tempo. Non è raro per noi implementare un nuovo endpoint API o un miglioramento di un endpoint API esistente e utilizzarlo dall'interno dell'interfaccia utente o con strumenti interni prima di documentarlo pubblicamente e supportarlo. In questo modo possiamo apportare alcune modifiche per l'usabilità o la conformità agli standard senza preoccuparci di apportare una temuta modifica significativa. Una volta che siamo soddisfatti del cambiamento, lo aggiungiamo alla nostra documentazione pubblica.
Doh!
È giusto ammettere che ci sono stati momenti in cui non siamo stati all'altezza dei nostri ideali di "nessuna modifica significativa" e queste situazioni valgono la pena essere apprese. In un'occasione abbiamo deciso che sarebbe stato meglio per gli utenti se una certa proprietà predefinita fosse stata vera invece che falsa. Dopo aver implementato il cambiamento, abbiamo ricevuto diverse lamentele dagli utenti poiché il comportamento era cambiato inaspettatamente. Abbiamo revocato il cambiamento e aggiunto una impostazione a livello di account – un approccio decisamente più user-friendly.
Occasionalmente siamo tentati di introdurre modifiche significative come risultato di correzioni di bug. Tuttavia, abbiamo deciso di lasciare inalterate queste peculiarità piuttosto che rischiare di interrompere le integrazioni dei clienti per il bene della coerenza.
Ci sono rari casi in cui abbiamo preso la seria decisione di apportare una modifica significativa – come depennare una risorsa o un metodo API – nell'interesse della comunità degli utenti e solo dopo aver confermato che c'è poco o nessun impatto sugli utenti. Ad esempio, abbiamo deliberatamente scelto di alterare il comportamento di risposta dell'API di soppressione, ma solo dopo aver attentamente valutato i benefici e gli impatti sulla comunità e comunicato attentamente il cambiamento ai nostri utenti. Tuttavia, non introdurremmo mai un cambiamento che ha una remota possibilità di influenzare direttamente l'invio delle email di produzione di un utente.