API Versioning mit Ruby on Rails: Welche Edelsteine sind die Besten?

API Versioning mit Ruby on Rails: Welche Edelsteine sind die Besten?

Die API-Versionierung hilft, das Verhalten einer API für verschiedene Clients zu ändern. Eine API-Version wird von einer eingehenden Clientanforderung bestimmt und basiert entweder auf der Anforderungs-URL oder auf den Anforderungsheadern. Es gibt eine Reihe gültiger Ansätze für die Versionierung.

Wann ist die API-Versionierung erforderlich?

Die API-Versionierung kann in bestimmten Fällen ignoriert werden, z. B. wenn eine API als interner Client fungiert oder wenn eine API, die Sie bereits verwendet haben, einige kleinere Änderungen erfährt (z. B. Hinzufügen neuer Felder oder neuer Daten zur Antwort).

Wenn Sie jedoch einige wichtige Änderungen an Ihrem Code oder an der Geschäftslogik Ihrer App vornehmen und diese Änderungen sich auf vorhandene Clients auswirken, ist die API-Versionierung die einzige Möglichkeit, alte Clients nicht zu beschädigen.

Wie kann eine API-Version vom Client angegeben werden?
Hier ist eine Liste von Orten, an denen API-Versionen allgemein angegeben sind:

1. URL-Pfadparameter:

Die API-Version wird in den URL-Pfad eingefügt

HTTP GET:

https://domain.com/api/v2/resources

2. URL Get Parameter oder Anfrage body Parameter
HTTP GET:

https://domain.com/api/resources?version=v2

3. Akzeptieren Sie Header als versionierten Medientyp

HTTP GET:

https: // domain / api / bücher

Akzeptieren:

application / vnd.your_app_name.v2 + json

4. Benutzerdefinierter Header

HTTP GET:

https: // domain / api / bücher

API-VERSION: 2

Es gibt eine anhaltende Debatte darüber, wie man eine API-Version richtig spezifiziert.

URLs gelten nicht als ideal für diese Aufgabe, da sie eine Ressource, aber nicht die Version dieser Ressource darstellen. Dies ist jedoch der einfachste Ansatz und eignet sich zum Testen.

Ein benutzerdefinierter Header wird als übermäßig angesehen, da die HTTP-Spezifikation bereits den Accept-Header besitzt, der denselben Zweck erfüllt.

Die Header-API-Versionierung akzeptiert die beste Option gemäß der HTTP-Spezifikation. Es ist jedoch nicht einfach, solche APIs im Vergleich zu anderen Ansätzen zu testen. Da es nicht ausreicht, eine API-URL zu öffnen, müssen Sie eine Anfrage mit korrekten Headern verfassen.

Wenn es darum geht, welche Version einer API es zu wählen gilt, stimmen die meisten Entwickler der Verwendung der ersten API-Version als Standard zu. Wenn Ihr API-Client (iOS / Android-Gerät, Webbrowser usw.) keine erforderliche API-Version angibt, muss Ihre API die allererste Version der Antwort zurückgeben, da die einzige sichere Annahme ist, dass dieser Client zuvor erstellt wurde Die API hatte überhaupt eine Versionierung.

API Versionierung mit Ruby on Rails
Rails hat eine große Menge an Edelsteinen zum Erstellen von APIs mit Versionierung. Sehen wir uns ihre Fähigkeiten im Detail an.

Versionist
Dieses Schmuckstück unterstützt drei Versionierungsstrategien: HTTP-Header, URL-Pfad und Anforderungsparameter. Routen, Controller, Presenter / Serializer, Tests und Dokumentation sind Namensräume. Dies isoliert den Code einer API-Version von einem anderen. Dies kann übertrieben erscheinen, da die meisten Änderungen in Ansichten oder Serialisierern vorgenommen werden. Aber es ist korrekter, da das Isolieren von Logik innerhalb von Namespaces ein sauberer und offensichtlicherer Ansatz ist als der Umgang mit einer Mischung verschiedener Versionen innerhalb eines Controllers.

Um Routineaufgaben zu automatisieren, bietet versionist Rails-Generatoren zum Generieren neuer Versionen Ihrer API sowie neuer Komponenten innerhalb einer bestehenden Version. Es stellt auch einen Rails-Generator bereit, der eine vorhandene API-Version in eine neue API-Version kopiert. Dies funktioniert jedoch nicht gemäß dem DRY-Ansatz, da dies zu einer Code-Duplizierung führt. Ich habe diese Generatoren noch nie benutzt. Normalerweise erstelle ich alle benötigten Controller und Serialisierer manuell. Ich kopiere auch nicht den gesamten Code von der vorherigen Version; Ich erben nur von der vorherigen Versionskontrolle.

Ein wesentlicher Nachteil des Versions-Edelsteins ist, dass der von ihm bereitgestellte API-Versions-Mechanismus keine Rückfälle auf die vorherige Version unterstützt, wenn die angegebene Logik nicht in die neue Version kopiert wurde. Das Juwel erwartet, dass der gesamte erforderliche Code in jeder neuen Version dupliziert wird. Aber wenn Sie nur ein Antwortformat ändern müssen, scheint dies übertrieben. Dieses Juwel ist aber immer noch ziemlich gut. Es ist leicht und konzentriert sich nur auf die API-Versionierung. Das ist im Vergleich zu einigen Edelsteinen, die bestimmte Methoden der API-Versionierung diktieren (z. B. rocket_pants und versioncake), nett.

Hier sehen Sie ein Beispiel für versionierte Routen aus dem Versionist-Juwel, das den Accept-Header mit dem versionierten Medientyp verwendet:
Namensraum: versionist_api do

api_version (

Header: {

Name: „Akzeptieren“,

Wert: ‚application / vnd.versionist_api.v2 + json‘

},

Modul: „V2“,

Standardwerte: {format:: json}

) machen

Ressourcen: nur Bücher: [: index,: create,: show,: update,: destroy]

Ende

api_version (

Header: {

Name: ‚Akzeptieren‘,

Wert: ‚application / vnd.versionist_api.v1 + json‘

},

Modul: ‚V1‘,

Standard: Wahr,

Standardwerte: {format:: json}

) machen

Ressourcen: nur Bücher: [: index,: create,: show,: update,: destroy]

Ende

Ende

Versionskuchen
Dieses Juwel hat einen anderen Ansatz. In den meisten Fällen erfolgt die Versionierung für API-Ansichten und Controller sind nicht Namespaced. Eine nette Eigenschaft von Versioncake ist, dass es Rückfälle zu früheren Versionen hat. Zusammen mit path, query param, accept header und custom header bietet es auch die Möglichkeit, einen eigenen Versionierungsansatz zu erstellen, der ein Anforderungsobjekt akzeptiert. Auf diese Weise können Entwickler eine API-Version an jeder beliebigen Stelle in der Anforderung in einer beliebigen Form angeben.

Da versioncake keinen Controller für jede Version unterstützt, verfügt es über spezielle Methoden, um auf die angeforderte Version und die neueste Version innerhalb der Instanz des Controllers zuzugreifen. Dies kann jedoch dazu führen, dass ein unerfahrener Entwickler schlechten Code schreibt, wenn er innerhalb von Controllern eine bedingte Logik hat, die von diesen Versionsparametern abhängt. In diesem Fall ist es besser, das Factory-Muster zu verwenden, bei dem die Controller-Aktion als einzelnes Objekt für jede Version implementiert wird (das interactor-Juwel kann für diesen Zweck verwendet werden).

Versioncake verfügt über eine Vielzahl von Funktionen (Details finden Sie in der Vergleichstabelle), einschließlich einiger exotischer Funktionen wie der Versionsabwertung. In einer Hinsicht sieht es wie eine vollständige Lösung für die API-Versionierung aus; aber in einem anderen scheint es ein bisschen schwer, da einige seiner zusätzlichen Funktionen möglicherweise nicht in generischen API-Anwendungsfällen verwendet werden.

Ein weiterer Nachteil von Versioncake ist, dass es sichtorientiert ist. Edelsteine wie jbuilder und rabl können mit versioncake verwendet werden, da ihre Vorlagen als Ansichten gespeichert werden. Aber modernere und populärere Edelsteine wie active_model_serializers können nicht mit versioncake verwendet werden. Dies kann in Ordnung sein, wenn Sie es vorziehen, einige Ansichtsteile als Teilbereiche zu verwenden (z. B. wenn Felder von Version 1 in einer Antwort der Version 2 vorhanden sind); Mit active_model_serializers können Sie die normale Vererbung von Ruby-Klassen verwenden.
Traube
Grape ist nicht nur ein API-Versions-Tool. Es ist ein REST-ähnliches API-Framework. Grape wurde entwickelt, um auf Rack zu laufen oder bestehende Webanwendungsframeworks wie Rails und Sinatra zu ergänzen, indem es eine einfache domänenspezifische Sprache zur Verfügung stellt, um einfach RESTful APIs zu entwickeln.

Was die API-Versionsverwaltung betrifft, bietet traube vier Strategien an: URL-Pfad, Accept-Header (ähnlich dem versionierten Medientyp-Ansatz), Accept-Version-Header und Request-Parameter.

Es ist auch möglich, Rückfälle zu früheren Versionen zu haben, die die spezifische Codeorganisation verwenden, die hier beschrieben wird: http://code.dblock.org/2013/07/19/evolving-apis-using-grape-api-versioning.htmlHier ist ein kurzes Beispiel von API Versioning Fallbacks in Trauben:

Und hier ist ein Modul für die Standardkonfiguration der ersten Version:

Modul GrapeApi

Modul V1

Modul Defaults

Erweitern Sie ActiveSupport :: Concern

enthalten tun

# Dies würde die erste API-Version auf die zweite als Fallback reagieren lassen

Version [‚v2‘, ‚v1‘], unter Verwendung von:: header, vendor: ‚grape_api‘

# ….

Ende

Ende

Ende

Und die zweite Version:

Modul GrapeApi

Modul V2

Modul Defaults

Erweitern Sie ActiveSupport :: Concern

enthalten tun

# Version „v2“, mit:: Pfad

Version ‚v2‘ unter Verwendung von:: header, vendor: ‚grape_api‘

Ende

Ende

Ende
Bei trave_api / base.rb wird die zweite Version vor der ersten Version installiert. Damit können Anfragen an Version 2 mit V2-Logik (falls vorhanden) bearbeitet werden oder auf Version 1 zurückgreifen.

Modul GrapeApi

Klasse Base <Grape :: API

Montieren Sie GrapeApi :: V2 :: Base

Montieren GrapeApi :: V1 :: Base

Ende

Ende
Für mich sind die oben beschriebenen API-Versionsrückrufe schwer und komplex. Sie müssen fast alle grundlegenden Konfigurationsdateien und den Code für jede Version kopieren, was kein DRY-Ansatz ist.

Wahrscheinlich ist es sinnvoller, Traube als Rack-Anwendung zu verwenden, anstatt sie in Verbindung mit Rails / Sinatra zu verwenden, da Sie die Rails-Struktur duplizieren müssen, um sie ordnungsgemäß zu verwenden. Und wenn Sie Schienen haben, brauchen Sie keine Trauben hinzuzufügen. Da dieses Framework nicht so populär ist wie Rails, haben Sie möglicherweise Ihre eigene Integration mit einigen externen Diensten wie NewRelic erstellt. Weitere Informationen finden Sie in diesem Artikel über Vor- und Nachteile bestehender Rails-API-Lösungen.
Aus meiner Erfahrung verfügt jedes neue API-Server-Projekt über ein Admin-Panel, das normalerweise mit dem Juwel "active_admin" erstellt wird. Deshalb macht es keinen Sinn, in diesem Fall den Traubenstein zu verwenden. Dasselbe gilt für die Verwendung einer Nur-API-Rails-Konfiguration. Wenn ein Projekt ein Admin-Panel auf demselben Server benötigt, können wir diese API-only-Konfiguration nicht verwenden.

rocket_pants
Dieses Juwel ist nicht nur für API-Versionierung. Es hat viele weitere Funktionen zum Erstellen von APIs.

Für den Moment ist rocket_pants jedoch nicht kompatibel mit den neuesten Rails 5. Vielleicht wird sich das in Zukunft ändern. Wegen dieser Einschränkung konnte ich es nicht manuell versuchen. Wie bei Versionierungsstrategien hat rocket_pants nur die Path-Parameter-Unterstützung. Es hat Namespace-Routen und Controller. Als Teil der Standardkonfiguration für die Versionierung von Ansichten verwendet es die serializable_hash-Methode. Es verwendet die serializable_hash-Methode der Model-Ebene, was bedeutet, dass die Versionierung von Ansichten nicht möglich ist. Aber gemäß der Dokumentation des Edelsteins hat es Unterstützung für den Edelstein "active_model_serializers". In Ihrem Expose-Aufruf, der für das Rendern von Daten verwendet wird, ist es möglich, einen Serializer mit Serializer oder: each_serializer-Optionen zu übergeben. Auf diese Weise können Sie einen Namespaced-Serializer für eine bestimmte API-Version angeben.

Mit dem Juwel rock_pants ist die API-Versionierung in Routen wie folgt definiert:
api Version: 2 tun

get 'x', an: 'test_a # item'

Ende

api-Versionen: 1..3 do

Holen Sie 'y', zu: 'test_b # item'

Ende

Ich gehe davon aus, dass dies in der Routenanfrage für Version 2 als Ersatz für "y" funktionieren könnte.

Da dieses Juwel veraltet ist, denke ich, dass es besser ist, es zu vermeiden, aber Sie könnten entscheiden, dass Sie eine benutzerdefinierte Lösung ähnlich wie es erstellen möchten, deshalb haben wir es hier erwähnt.
api-Versionen

Dieses Juwel ist auch veraltet (zum Zeitpunkt des Schreibens des letzten Commit wurde am 23. Februar 2015 gemacht), aber es ist immer noch kompatibel mit Rails 5, also konnte ich damit spielen.

Dieses Schmuckstück unterstützt nur eine Versionierungsstrategie: Kopfversion akzeptieren (versionierter Medientyp). Einige Leute in der Rails-Community halten diesen Versionierungsansatz für den zutreffendsten. Lesen Sie hier für weitere Details: http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http#i_want_my_api_to_be_versioned

Ich bevorzuge auch diesen Versionierungsansatz.

Versionierte Routen mit diesem Edelstein sehen so aus:
api vendor_string: "api_versionen",

Standardversion: 1,

Pfad: 'api_versions', Modul: 'api_versions'

Version 1 tun

Cache als: 'v1' tun

Ressourcen: nur Bücher: [: index,: create,: show,: update,: destroy]

Controller: Test, Pfad: 'test' do

bekomme 'some_action'

Ende

Ende

Ende

Version 2 tun

erben von: 'v1'

Ende

Ende
Mit dem API-Versionen-Juwel können Sie Routendefinitionen von einer früheren Version übernehmen. Dies erfordert jedoch, dass Sie auch für diese Version einen Controller mit einer bestimmten Aktion haben. Der Edelstein hat eine Generator-Aufgabe (api_versions: bump), die Controller für die nächste Version erstellt. Controller, die von diesen Generatorsteuerungen generiert wurden, erben von früheren. Die Vererbung scheint standardmäßig die richtige Lösung zu sein, verglichen mit dem Versionist-Juwel, das Kopien von früheren Controller-Versionen erstellt.

Aber es ist nicht so toll, alle Controller für jede neue API-Version zu erstellen, wenn Sie nur eine kleine Änderung an einer API-Antwort vornehmen. Die bessere und logischere Lösung wäre, wenn dieses Juwel einen API-Fallbackmechanismus bereitstellen könnte, der die Logik der vorherigen API-Version direkt wiederverwenden würde, aber leider haben API-Versionen solche Fähigkeiten nicht.
acts_as_api
Dieses Gem ist nicht für die API-Versionierung gedacht, sondern bietet eine einfache domänenspezifische Langung, um die Darstellung von Modelldaten zu definieren, die in API-Antworten gerendert werden sollen. Es ist jedoch immer noch möglich, es für die API-Versionierung zu verwenden.

So habe ich mit acts_as_api experimentiert, um die Versionierung von API-Antworten zu erhalten:

Klasse Buch <ApplicationRecord

gehört zu: Autor

access_nested_attributes_for: Autor

acts_as_api

api_accessible: base_fields do | t |

t.add: id

t.add: Titel

t.add: Beschreibung

Ende

api_accessible: v1, erweitern:: base_fields do | t |

t.add Lambda {| Buch |

"# {buch.autor.first_name} # {book.author.last_name}"

}, als:: Autor_Name

Ende

api_accessible: v2, erweitern:: base_fields do | t |

t.add: aktualisiert_at

t.add: Autor

Ende

Ende
Ich empfehle jedoch nicht, dieses Juwel zu verwenden, da es das MVC-Prinzip stark verletzt, indem die API-Antwortdefinition auf Modellebene platziert wird. Sicher, Sie könnten diese Definitionen in Bedenken / Module extrahieren und in Modelle einbeziehen. Trotzdem ist es eine sehr merkwürdige Vorgehensweise.

Vergleich der Rails-API-Versions-Tools:

Streng genommen ist dieser Ansatz nicht Rails-spezifisch. Kurz gesagt, es ist ein Versionsverwaltungssystem, das um eine Reihe von Toren herum aufgebaut ist. Ein Gatter ist einem Flag ähnlich, das verwendet wird, um zu bestimmen, welche Funktionalität erlaubt ist. Gates sind mit Daten verbunden, wenn eine bestimmte API-Änderung vorgenommen wurde. Das Backend behält das Datum der ersten Anfrage des Clients. Von diesem Zeitpunkt an wird jede Clientanforderung durch diese Gatter gefiltert, die eine bedingte Logik aufweisen, die zulässige Anforderungsparameter und Antwortformate definiert. Diese Gattergruppe definiert die API-Version eines Benutzers für eine bestimmte Ressource.Version-Gate-Ansatz

Sicher, das ist eine sehr komplexe Lösung. In seiner einfachsten Form kann dies zu einer Unordnung vieler bedingter Anweisungen in Controllern und Ansichten führen. Wenn ich eine solche Lösung implementieren müsste, würde ich wahrscheinlich ein einzelnes Objekt für jedes Tor erstellen. Dann sollten diese Gatter in einer Anforderungsverarbeitungskette registriert und nacheinander verarbeitet werden. Dies sollte helfen, ein Durcheinander von if-Anweisungen zu vermeiden.