Breaking Changes Slecht! API-versiebeheer Goed!
Zoals iedereen die een API heeft gebouwd of regelmatig gebruikt vroeg of laat beseft, zijn breaking changes erg slecht en kunnen ze een zeer ernstige smet zijn op een anderszins nuttige API. Een breaking change is een verandering in het gedrag van een API die de integratie van een gebruiker kan verbreken en kan leiden tot veel frustratie en verlies van vertrouwen tussen de API-provider en gebruiker. Breaking changes vereisen dat gebruikers van tevoren op de hoogte worden gesteld (met begeleidende excuses) in plaats van een verandering die zomaar verschijnt, zoals een verrukkelijke nieuwe functie. De manier om die frustratie te vermijden is door een API te versiescheuren met garanties van de API-eigenaar dat er binnen een enkele versie geen verrassende wijzigingen worden geïntroduceerd.
Dus hoe moeilijk kan het zijn om een API te versiescheuren? De waarheid is dat dit niet moeilijk is, maar wat wel moeilijk is, is enige veiligheid te behouden door niet onnodig te vervallen in een duizelingwekkend aantal versies en subversies toegepast op tientallen API-eindpunten met onduidelijke compatibiliteiten.
We introduceerden drie jaar geleden v1 van de API en realiseerden ons niet dat het tot op de dag van vandaag sterk zou blijven. Dus hoe zijn we erin geslaagd om al meer dan twee jaar de beste e-mailleverings-API te bieden en toch dezelfde API-versie te behouden? Hoewel er veel verschillende meningen zijn over hoe REST API's te versiescheuren, hoop ik dat het verhaal van onze bescheiden maar krachtige v1 je kan begeleiden op jouw weg naar API-versiescheuren verlichting.
REST Is Best
De SparkPost API is ontstaan toen we Message Systems waren, voor onze avonturen in de cloud. Destijds waren we druk bezig met de laatste voorbereidingen voor de bètalancering van Momentum 4. Dit was een grote upgrade naar versie 3.x, onze marktleidende on-premise MTA. Momentum 4 bevatte een geheel nieuwe UI, realtime analyses, en vooral een nieuwe web-API voor berichtinjectie en -generatie, het beheren van sjablonen en het verkrijgen van e-mailstatistieken. Onze visie was een API-first architectuur - waar zelfs de UI zou communiceren met API-eindpunten.
Een van de vroegste en beste beslissingen die we namen, was het aannemen van een RESTful stijl. Sinds de late jaren 2000 zijn representational state transfer (REST) gebaseerde web-API's de de-facto standaard van cloud-API's. Het gebruik van HTTP en JSON maakt het gemakkelijk voor ontwikkelaars, ongeacht welke programmeertaal ze gebruiken - PHP, Ruby en Java - om met onze API te integreren zonder zich zorgen te maken over onze onderliggende technologie.
Kiezen voor de RESTful architectuur was eenvoudig. Het kiezen van een versiebeheersconventie was niet zo eenvoudig. In eerste instantie hebben we de vraag van versiebeheer uitgesteld door de bèta helemaal niet te versiescheuren. Echter, binnen een paar maanden was de bèta in handen van een paar klanten en begonnen we onze clouddienst uit te bouwen. Tijd om te versiescheuren. We hebben twee versieerconventies geëvalueerd. De eerste was om de versieer direct in de URI te plaatsen en de tweede was om een Accept-header te gebruiken. De eerste optie is explicieter en minder ingewikkeld, wat gemakkelijker is voor ontwikkelaars. Aangezien we van ontwikkelaars houden, was dit een logische keuze.
API Governance
Met een versieerconventie geselecteerd hadden we meer vragen. Wanneer zouden we de versie verhogen? Wat is een breaking change? Zouden we de hele API of alleen bepaalde eindpunten opnieuw versiescheuren? Bij SparkPost hebben we meerdere teams die aan verschillende delen van onze API werken. Binnen die teams werken mensen op verschillende momenten aan verschillende eindpunten. Daarom is het erg belangrijk dat onze API consistent is in het gebruik van conventies. Dit was groter dan versiebeheer.
We hebben een bestuursgroep opgezet, inclusief ingenieurs die elk team vertegenwoordigen, een lid van het Product Management-team en onze CTO. Deze groep is verantwoordelijk voor het vaststellen, documenteren en handhaven van onze API-conventies over alle teams heen. Een API governance Slack-kanaal komt ook goed van pas voor levendige debatten over het onderwerp.
De bestuursgroep identificeerde een aantal manieren waarop wijzigingen in de API kunnen worden geïntroduceerd die voordelig zijn voor de gebruiker en geen breaking change vormen. Deze omvatten:
Een nieuwe bron of API-eindpunt
Een nieuwe optionele parameter
Een verandering aan een niet-openbaar API-eindpunt
Een nieuwe optionele sleutel in het JSON POST-body
Een nieuwe sleutel geretourneerd in de JSON-responsbody
Omgekeerd omvatte een breaking change alles wat de integratie van een gebruiker zou kunnen verbreken, zoals:
Een nieuwe vereiste parameter
Een nieuwe vereiste sleutel in POST-bodies
Verwijdering van een bestaand eindpunt
Verwijdering van een bestaande eindpuntaanroeimethode
Een wezenlijk ander intern gedrag van een API-aanroep - zoals een verandering naar het standaardgedrag.
De Grote 1.0
Terwijl we deze conventies documenteerden en bespraken, kwamen we ook tot de conclusie dat het in ieders (inclusief ons eigen!) belang was om breaking changes in de API te vermijden, aangezien het beheren van meerdere versies nogal wat overhead met zich meebrengt. We besloten dat er een paar dingen waren die we moesten oplossen met onze API voordat we ons aan "v1" committeren.
Het verzenden van een eenvoudige e-mail vereiste veel te veel moeite. Om "de eenvoudige dingen eenvoudig te houden" hebben we het POST-body bijgewerkt om ervoor te zorgen dat zowel eenvoudige als complexe gebruikssituaties worden ondersteund. Het nieuwe formaat was ook beter bestand tegen de toekomst. Ten tweede pakten we een probleem met het Metrics-eindpunt aan. Dit eindpunt gebruikte een "group_by" parameter die het formaat van de GET-responsbody zou veranderen, zodat de eerste sleutel de waarde van de group by-parameter zou zijn. Dat leek niet erg RESTful, dus we splitsten elke group by in een apart eindpunt. Ten slotte hebben we elk eindpunt gecontroleerd en hier en daar kleine wijzigingen aangebracht om ervoor te zorgen dat ze aan de standaarden voldeden.
Nauwkeurige Documentatie
Het is belangrijk om nauwkeurige en bruikbare API-documentatie te hebben om breaking changes, van de opzettelijke of onbedoelde soort, te voorkomen. We besloten om een eenvoudige API-documentatie aanpak te gebruiken met behulp van een Markdown-taal genaamd API Blueprint en beheerde onze documentatie in Github. Onze gemeenschap draagt bij aan en verbetert deze open source documentatie. We onderhouden ook een niet-openbare set documentaties in Github voor interne API's en eindpunten.
In het begin publiceerden we onze documentatie naar Apiary, een geweldig hulpmiddel voor het prototypen en publiceren van API-documentatie. Het embedden van Apiary in onze website werkte echter niet op mobiele apparaten, dus we gebruiken nu Jekyll om statische documentatie te genereren. Onze nieuwste SparkPost API-documentatie laadt nu snel en werkt goed op mobiele apparaten, wat belangrijk is voor ontwikkelaars die niet altijd achter hun computer zitten.
Het Scheiden van Implementatie van Release
We leerden al vroeg de waardevolle truc van het scheiden van een implementatie van een release. Op deze manier is het mogelijk om regelmatig wijzigingen te implementeren als ze klaar zijn via continuous delivery en implementatie, maar we kondigen of documenteren ze niet altijd openbaar tegelijkertijd. Het is niet ongebruikelijk voor ons om een nieuw API-eindpunt of een verbetering aan een bestaand API-eindpunt te implementeren en het vanuit de UI of met interne tools te gebruiken voordat we het openbaar documenteren en ondersteunen. Op die manier kunnen we enkele aanpassingen eraan maken voor bruikbaarheid of conformiteit aan standaarden zonder ons zorgen te maken over het maken van een gevreesde breaking change. Zodra we tevreden zijn met de wijziging voegen we deze toe aan onze openbare documentatie.
Doh!
Het is alleen eerlijk om toe te geven dat er momenten zijn geweest waarop we niet aan onze idealen van "geen breaking changes" hebben voldaan en hieruit valt te leren. Bij een gelegenheid besloten we dat het beter zou zijn voor gebruikers als een bepaalde eigenschap standaard op waar in plaats van vals stond. Nadat we de wijziging hadden geïmplementeerd, ontvingen we verschillende klachten van gebruikers omdat het gedrag onverwacht was veranderd. We hebben de wijziging teruggedraaid en een accountniveau-instelling toegevoegd - een veel gebruikersvriendelijkere aanpak.
Af en toe worden we verleid om breaking changes te introduceren als gevolg van bugfixes. We besloten echter om deze eigenaardigheden met rust te laten in plaats van de integraties van klanten te verbreken voor de liefhebber van consistentie.
Er zijn zeldzame gevallen waarin we de serieuze beslissing hebben genomen om een breaking change te maken - zoals het afschaffen van een API-resource of -methode - in het belang van de grotere gebruikersgemeenschap en alleen nadat we hebben bevestigd dat er weinig tot geen impact is op gebruikers. Bijvoorbeeld, we maakten bewust de keuze om het reactiever gedrag van de Suppression API te wijzigen, maar alleen na zorgvuldig de voordelen en invloeden op de gemeenschap afgewogen te hebben en zorgvuldig de veranderingen aan onze gebruikers te communiceren. We zouden echter nooit een verandering introduceren die een verre mogelijkheid heeft om rechtstreeks invloed te hebben op het verzenden van de productie-e-mails van een gebruiker.