Wir alle lieben es, glänzende neue Tools zu haben, hassen aber die lästige Pflicht, sie ständig zu aktualisieren. Dies gilt für alles: Betriebssysteme, Apps, APIs, Linux-Pakete. Es ist schmerzhaft, wenn unser Code aufgrund eines Updates nicht mehr funktioniert, und es ist doppelt so schmerzhaft, wenn das Update nicht einmal von uns initiiert wurde.
Bei der Web-API-Entwicklung besteht ständig das Risiko, dass der Code Ihrer Benutzer bei jedem neuen Update beschädigt wird. Wenn es sich bei Ihrem Produkt um eine API handelt, werden diese Updates jedes Mal erschreckend sein. Die Hauptprodukte von Monite sind unsere API und unser White-Label-SDK. Da wir ein API-First-Unternehmen sind, legen wir großen Wert darauf, dass unsere API stabil und benutzerfreundlich bleibt. Daher steht das Problem der Breaking Changes ganz oben auf unserer Prioritätenliste.
Eine gängige Lösung besteht darin, Ihren Kunden veraltete Warnungen zukommen zu lassen und Breaking Changes selten zu veröffentlichen. Plötzlich können Ihre Veröffentlichungen Monate dauern und einige Funktionen müssen bis zur nächsten Veröffentlichung verborgen bleiben oder sogar nicht zusammengeführt werden. Dies verlangsamt Ihre Entwicklung und zwingt Ihre Benutzer, ihre Integration alle paar Monate zu aktualisieren.
Wenn Sie Releases schneller machen, müssen Ihre Benutzer ihre Integration zu oft aktualisieren. Wenn Sie die Zeit zwischen den Veröffentlichungen verlängern, werden Sie als Unternehmen langsamer vorankommen. Je unbequemer Sie es für die Benutzer machen, desto bequemer wird es für Sie sein und umgekehrt. Dies ist sicherlich kein optimales Szenario. Wir wollten uns in unserem eigenen Tempo weiterentwickeln, ohne bestehende Kunden zu beeinträchtigen, was mit einem regulären Einstellungsansatz unmöglich wäre. Aus diesem Grund haben wir uns für eine alternative Lösung entschieden: API-Versionierung.
Es ist eine ganz einfache Idee: Geben Sie alle Breaking Changes jederzeit frei, verstecken Sie sie jedoch unter einer neuen API-Version. Es bietet Ihnen das Beste aus beiden Welten: Die Integrationen der Benutzer werden nicht routinemäßig unterbrochen und Sie können sich mit jeder gewünschten Geschwindigkeit bewegen. Die Benutzer migrieren, wann immer sie wollen – ganz ohne Druck.
Angesichts der Einfachheit der Idee scheint sie für jedes Unternehmen perfekt zu sein. Das ist es, was Sie in einem typischen Ingenieurblog erwarten würden. Leider ist es nicht so einfach.
API-Versionierung ist schwierig, sehr schwierig. Seine illusorische Einfachheit verschwindet schnell, sobald man mit der Umsetzung beginnt. Leider warnt Sie das Internet nie wirklich, da es überraschend wenige Ressourcen zu diesem Thema gibt. Die absolute Mehrheit von ihnen streitet darüber, wo die API-Version platziert werden soll, aber nur wenige wenige Artikel versuchen zu antworten: „Wie implementieren sie?“ Die häufigsten sind:
Getrennte Bereitstellungen können sehr teuer und schwierig zu unterstützen sein, das Kopieren einzelner Routen lässt sich nicht sehr gut auf große Änderungen skalieren und das Kopieren der gesamten Anwendung erzeugt so viel zusätzlichen Code, dass Sie bereits nach wenigen Versionen darin ertrinken.
Selbst wenn Sie versuchen, das günstigste zu wählen, wird Ihnen die Belastung durch die Versionierung bald einfallen. Auf den ersten Blick wird es sich einfach anfühlen: Fügen Sie hier ein weiteres Schema, dort einen weiteren Zweig in der Geschäftslogik hinzu und duplizieren Sie am Ende ein paar Routen. Aber wenn genügend Versionen vorhanden sind, wird Ihre Geschäftslogik schnell unüberschaubar, viele Ihrer Entwickler werden Anwendungsversionen und API-Versionen verwechseln und beginnen, die Daten in Ihrer Datenbank zu versionieren, und Ihre Anwendung wird nicht mehr zu warten sein.
Vielleicht hoffen Sie, dass Sie nie mehr als zwei oder drei API-Versionen gleichzeitig haben; dass Sie alte Versionen alle paar Monate löschen können. Dies gilt, wenn Sie nur eine kleine Anzahl interner Verbraucher unterstützen. Aber Kunden außerhalb Ihres Unternehmens werden die Erfahrung nicht genießen, alle paar Monate zu einem Upgrade gezwungen zu werden.
API-Versionierung kann schnell zu einem der teuersten Teile Ihrer Infrastruktur werden, daher ist es wichtig, im Vorfeld sorgfältige Recherchen durchzuführen. Wenn Sie nur interne Verbraucher unterstützen, haben Sie es vielleicht einfacher mit etwas wie GraphQL, aber es kann schnell genauso teuer werden wie die Versionierung.
Wenn Sie ein Startup sind, wäre es ratsam, die API-Versionierung auf die späteren Phasen Ihrer Entwicklung zu verschieben, wenn Sie über die Ressourcen verfügen, es richtig zu machen. Bis dahin könnten Abwertungen und additive Änderungsstrategien ausreichen. Ihre API wird nicht immer gut aussehen, aber Sie sparen zumindest eine Menge Geld, indem Sie die explizite Versionierung vermeiden.
Nach ein paar Versuchen und vielen Fehlern standen wir am Scheideweg: Unsere oben erwähnten früheren Versionierungsansätze waren zu teuer in der Wartung. Als Ergebnis unserer Schwierigkeiten habe ich die folgende Liste von Anforderungen erstellt, die an ein perfektes Versionierungs-Framework gestellt werden:
Leider gab es kaum oder gar keine Alternativen zu unseren bestehenden Ansätzen. Da kam mir eine verrückte Idee: Was wäre, wenn wir versuchen würden, etwas Anspruchsvolles, etwas Perfektes für den Job zu entwickeln – so etwas wie die API-Versionierung von Stripe?
Als Ergebnis unzähliger Experimente haben wir jetzt Cadwyn: ein Open-Source-API-Versionierungs-Framework, das den Ansatz von Stripe nicht nur umsetzt, sondern maßgeblich darauf aufbaut. Wir werden über die Fastapi- und Pydantic-Implementierung sprechen, aber die Grundprinzipien sind sprach- und rahmenunabhängig.
Das Problem aller anderen Versionierungsansätze besteht darin, dass wir zu viel duplizieren. Warum sollten wir die gesamte Route, den Controller oder sogar die Anwendung duplizieren, wenn nur ein winziger Teil unseres Vertrags gebrochen wurde?
Wenn API-Betreuer mit Cadwyn eine neue Version erstellen müssen, wenden sie die bahnbrechenden Änderungen auf ihre neuesten Schemata, Modelle und Geschäftslogiken an. Dann erstellen sie eine Versionsänderung – eine Klasse, die alle Unterschiede zwischen der neuen Version und einer früheren Version zusammenfasst.
Nehmen wir zum Beispiel an, dass unsere Kunden früher einen Benutzer mit einer Adresse erstellen konnten, jetzt möchten wir ihnen jedoch erlauben, mehrere Adressen anstelle einer einzigen anzugeben. Die Versionsänderung würde so aussehen:
class ChangeUserAddressToAList(VersionChange): description = ( "Renamed `User.address` to `User.addresses` and " "changed its type to an array of strings" ) instructions_to_migrate_to_previous_version = ( schema(User).field("addresses").didnt_exist, schema(User).field("address").existed_as(type=str), ) @convert_request_to_next_version_for(UserCreateRequest) def change_address_to_multiple_items(request): request.body["addresses"] = [request.body.pop("address")] @convert_response_to_previous_version_for(UserResource) def change_addresses_to_single_item(response): response.body["address"] = response.body.pop("addresses")[0]
instructions_to_migrate_to_ previous_version werden von Cadwyn verwendet, um Code für ältere API-Versionen von Schemas zu generieren, und die beiden Konverterfunktionen sind der Trick, der es uns ermöglicht, so viele Versionen zu verwalten, wie wir möchten. Der Vorgang sieht wie folgt aus:
Nachdem unsere API-Betreuer die Versionsänderung erstellt haben, müssen sie sie zu unserem VersionBundle hinzufügen, um Cadwyn mitzuteilen, dass diese Versionsänderung in einer Version enthalten sein wird:
VersionBundle( Version( date(2023, 4, 27), ChangeUserAddressToAList ), Version( date(2023, 4, 12), CollapseUserAvatarInfoIntoAnID, MakeUserSurnameRequired, ), Version(date(2023, 3, 15)), )
Das ist es: Wir haben eine bahnbrechende Änderung hinzugefügt, aber unsere Geschäftslogik verarbeitet nur eine einzige Version – die neueste. Selbst nachdem wir Dutzende von API-Versionen hinzugefügt haben, ist unsere Geschäftslogik immer noch frei von Versionierungslogik, ständigem Umbenennen, Wenns und Datenkonvertern.
Versionsänderungen hängen von der öffentlichen Schnittstelle der API ab und wir fügen fast nie Breaking Changes in bestehende API-Versionen ein. Das bedeutet, dass die Version, sobald wir sie veröffentlicht haben, nicht beschädigt wird.
Da Versionsänderungen Breaking Changes innerhalb von Versionen beschreiben und es keine Breaking Changes in alten Versionen gibt, können wir sicher sein, dass unsere Versionsänderungen völlig unveränderlich sind – es wird nie einen Grund für Änderungen geben. Unveränderliche Entitäten sind viel einfacher zu warten, als wenn sie Teil der Geschäftslogik wären, da sie sich ständig weiterentwickelt. Versionsänderungen werden auch nacheinander angewendet – sie bilden eine Kette von Transformatoren zwischen Versionen, die jede Anfrage auf jede neuere Version und jede Antwort auf jede ältere Version migrieren kann.
API-Verträge sind viel komplexer als nur Schemata und Felder. Sie bestehen aus allen Endpunkten, Statuscodes, Fehlern, Fehlermeldungen und sogar Verhaltensweisen der Geschäftslogik. Cadwyn verwendet dasselbe DSL, das wir oben beschrieben haben, um Endpunkte und Statuscodes zu verwalten, aber Fehler und Verhaltensweisen der Geschäftslogik sind eine andere Geschichte: Sie können nicht mit einem DSL beschrieben werden, sie müssen in die Geschäftslogik eingebettet werden.
This makes such version changes much more expensive to maintain than all others because they affect business logic. We call this property a "side effect" and we try to avoid them at all costs because of their maintenance burden. All version changes that want to modify business logic will need to be marked as having side effects. It will serve as a way to know which version changes are "dangerous":
class RequireCompanyAttachedForPayment(VersionChangeWithSideEffects): description = ( "User must now have a company_id in their account " "if they want to make new payments" )
It will also allow API maintainers to check that the client request uses an API version that includes this side effect:
if RequireCompanyToBeAttachedForPayment.is_applied: validate_company_id_is_attached(user)
Cadwyn has many benefits: It greatly reduces the burden on our developers and can be integrated into our infrastructure to automatically generate the changelog and improve our API docs.
However, the burden of versioning still exists and even a sophisticated framework is not a silver bullet. We do our best to only use API versioning when absolutely necessary. We also try to make our API correct on the first try by having a special "API Council". All significant API changes are reviewed there by our best developers, testers, and tech writers before any implementation gets moving.
Special thanks to Brandur Leach for his API versioning article at Stripe and for the help he extended to me when I implemented Cadwyn: it would not be possible without his help.
Das obige ist der detaillierte Inhalt vonAPI-Versionierung bei Monite. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!