it-swarm.com.de

Versionierung REST API

Nachdem ich viel Material zur REST -Versionierung gelesen habe, denke ich daran, die Aufrufe anstelle der API zu versionieren. Zum Beispiel:

http://api.mydomain.com/callfoo/v2.0/param1/param2/param3
http://api.mydomain.com/verifyfoo/v1.0/param1/param2

anstatt zuerst zu haben

http://api.mydomain.com/v1.0/callfoo/param1/param2
http://api.mydomain.com/v1.0/verifyfoo/param1/param2

dann gehe zu 

http://api.mydomain.com/v2.0/callfoo/param1/param2/param3
http://api.mydomain.com/v2.0/verifyfoo/param1/param2

Der Vorteil, den ich sehe, sind:

  • Wenn sich die Anrufe ändern, muss ich nicht meinen gesamten Client neu schreiben, sondern nur die Teile, die von den geänderten Aufrufen betroffen sind.
  • Die Teile des Clients, die gut funktionieren, können so bleiben wie sie sind (wir haben viele Teststunden investiert, um sicherzustellen, dass sowohl die Client- als auch die Serverseite stabil sind)
  • Ich kann permanente oder nicht permanente Weiterleitungen für geänderte Anrufe verwenden.
  • Abwärtskompatibilität wäre ein Kinderspiel, da ich ältere Anrufversionen unverändert lassen kann.

Fehlt mir etwas? Bitte beraten.

30
Ram Iyer

HTTP-Header erforderlich.

Version: 1

Der Header Version ist in RFC 4229 vorläufig registriert, und es gibt einige berechtigte Gründe, die Verwendung eines X-Präfixes oder einer benutzerspezifischen URI zu vermeiden. Ein typischerer Header wurde von yfeldblum unter https://stackoverflow.com/a/2028664 vorgeschlagen:

X-API-Version: 1

Wenn der Header fehlt oder nicht mit dem übereinstimmt, was der Server liefern kann, senden Sie in beiden Fällen einen 412 Precondition Failed Antwortcode mit dem Grund für der Fehlschlag. Dies erfordert, dass Clients die von ihnen unterstützte Version jedes Mal angeben, aber konsistente Antworten zwischen Client und Server erzwingen. (Durch die optionale Unterstützung eines Abfrageparameters ?version= erhalten Clients ein zusätzliches Maß an Flexibilität.)

Dieser Ansatz ist einfach, leicht zu implementieren und normenkonform.

Alternativen

Mir ist bewusst, dass einige sehr kluge und gut gemeinte Leute eine URL-Versionierung und Inhaltsaushandlung vorgeschlagen haben. Beide haben erhebliche Probleme in bestimmten Fällen und in der Form, wie sie normalerweise vorgeschlagen werden.

URL-Versionierung

Die Endpoint/Service-URL-Versionierung funktioniert, wenn Sie alle Server und Clients steuern. Andernfalls müssen Sie neuere Clients behandeln, die auf ältere Server zurückgreifen. Dies führt zu benutzerdefinierten HTTP-Headern, da Systemadministratoren von Serversoftware, die auf heterogenen Servern außerhalb Ihrer Kontrolle bereitgestellt werden, alles Mögliche tun können, um Fehler zu beheben Die URLs, von denen Sie glauben, dass sie leicht zu analysieren sind, wenn Sie so etwas wie verwenden. 302 Vorübergehend verschoben .

Inhaltsverhandlung

Die Inhaltsverhandlung über den Accept -Header funktioniert, wenn Sie sich stark darum kümmern, dem HTTP-Standard zu folgen, aber auch ignorieren möchten, was die HTTP/1.1-Standarddokumente tatsächlich sagen. Der vorgeschlagene MIME-Typ, den Sie normalerweise sehen, hat die Form application/vnd.example.v1+json. Es gibt einige Probleme:

  1. Es gibt natürlich Fälle, in denen die Herstellererweiterungen tatsächlich angemessen sind, aber ein geringfügig anderes Kommunikationsverhalten zwischen Client und Server nicht wirklich zur Definition eines neuen „Medientyps“ passt. Außerdem lautet RFC 2616 (HTTP/1.1) " Die Werte für den Medientyp werden bei der Internet Assigned Number Authority registriert. Der Registrierungsprozess für den Medientyp ist in RFC 1590 beschrieben. Von der Verwendung nicht registrierter Medientypen wird abgeraten. " Ich möchte nicht für jede Version jedes Softwareprodukts, das über eine REST - API verfügt, einen separaten Medientyp sehen.
  2. Alle Subtypbereiche (z. B. application/*) sind nicht sinnvoll. Was nützt es, wenn Sie für REST APIs, die strukturierte Daten zur Verarbeitung und Formatierung an Clients zurückgeben, */* akzeptieren?
  3. Der Accept -Header erfordert einige Anstrengungen, um eine korrekte Analyse durchzuführen. Es gibt eine implizite und eine explizite Rangfolge, die befolgt werden sollte, um das Hin und Her zu minimieren, das erforderlich ist, um die Inhaltsaushandlung tatsächlich korrekt durchzuführen. Wenn Sie Bedenken haben, diesen Standard korrekt umzusetzen, ist dies wichtig, um richtig zu sein.
  4. RFC 2616 (HTTP/1.1) beschreibt das Verhalten für jeden Client, der keinen Accept -Header enthält: "Wenn kein Accept-Header-Feld vorhanden ist, wird angenommen, dass der Der Client akzeptiert alle Medientypen. " Für Clients, die Sie nicht selbst schreiben (wo Sie die geringste Kontrolle haben), ist es das Richtigste, auf Anfragen mit den neuesten und anfälligsten Informationen zu antworten. to-breaking-old-versions-Version, die der Server kennt. Mit anderen Worten, Sie hätten die Versionierung überhaupt nicht implementieren können, und diese Clients würden immer noch auf die gleiche Weise brechen.

Bearbeitet, 2014 :

Ich habe viele der anderen Antworten und die nachdenklichen Kommentare aller gelesen. Ich hoffe, dass ich dies mit ein paar Jahren Feedback verbessern kann:

  1. Verwenden Sie kein 'X-' Präfix . Ich denke, dass Accept-Version 2014 wahrscheinlich sinnvoller ist, und es gibt einige berechtigte Bedenken hinsichtlich der Semantik der Wiederverwendung von Version, die in den Kommentaren angesprochen wurde. Es gibt Überschneidungen mit definierten Headern wie Content-Version und die relative Undurchsichtigkeit der URI, und ich versuche vorsichtig zu sein, die beiden mit Variationen bei der Inhaltsverhandlung zu verwechseln, die der Version-Header effektiv ist. Die dritte 'Version' der URL https://example.com/api/212315c2-668d-11e4-80c7-20c9d048772b unterscheidet sich vollständig von der 'zweiten', unabhängig davon, ob sie Daten oder ein Dokument enthält.
  2. In Bezug auf das, was ich oben über die URL-Versionierung gesagt habe (z. B. Endpunkte wie https://example.com/v1/users), trifft das Gegenteil wahrscheinlich eher zu: Wenn Sie alle Server und Clients kontrollieren, ist die URL/URI-Versionierung wahrscheinlich das, was Sie wollen. Für einen umfangreichen Dienst, der eine einzelne Dienst-URL veröffentlichen könnte, würde ich für jede Version einen anderen Endpunkt verwenden, wie die meisten . Meine besondere Einstellung wird stark von der Tatsache beeinflusst, dass die oben beschriebene Implementierung am häufigsten auf vielen verschiedenen Servern von vielen verschiedenen Organisationen bereitgestellt wird, und, was vielleicht am wichtigsten ist, auf Servern, die ich nicht kontrolliere. Ich möchte immer eine kanonische Service-URL, und wenn auf einer Site noch die Version 3 der API ausgeführt wird, möchte ich definitiv nicht, dass https://example.com/v4/ mit den Webservern zurückkommt. 404 Nicht gefunden Seite (oder noch schlimmer, 200 OK , die ihre Homepage als 500 KB HTML über Mobilfunkdaten an eine iPhone-App zurückgibt.)
  3. Wenn Sie sehr einfache/client/-Implementierungen (und eine breitere Akzeptanz) wünschen, ist es sehr schwierig zu argumentieren, dass das Erfordernis eines benutzerdefinierten Headers in der HTTP-Anforderung für Client-Autoren so einfach ist wie das GETting einer Vanilla-URL. (Obwohl für die Authentifizierung oftmals Ihr Token oder Ihre Anmeldeinformationen in den Headern übergeben werden müssen. Die Verwendung von Version oder Accept-Version als geheimer Handshake zusammen mit einem tatsächlichen geheimen Handshake passt recht gut Gut.)
  4. Die Inhaltsaushandlung mithilfe des Headers Accept eignet sich zum Abrufen verschiedener MIME-Typen für denselben Inhalt (z. B. XML vs. JSON vs. Adobe PDF), ist jedoch für Versionen dieser Dinge nicht definiert (Dublin Core 1.1 vs. JSONP vs. PDF/A) ). Wenn Sie den Header Accept unterstützen möchten, weil es wichtig ist, Industriestandards zu beachten, dann möchten Sie nicht, dass ein erfundener MIME-Typ die Medientypaushandlung stört, die Sie möglicherweise in Ihren Anforderungen verwenden müssen. Es wird garantiert, dass ein maßgeschneiderter API-Versionsheader nicht mit dem häufig verwendeten Accept interferiert, wohingegen das Zusammenführen derselben Verwendung sowohl für den Server als auch für den Client verwirrend ist. Das heißt, der Namensraum, den Sie für ein benanntes Profil in 2013 erwarten RFC6906 - ist aus vielen Gründen einem separaten Header vorzuziehen. Das ist ziemlich clever und Ich denke, die Leute sollten ernsthaft über diesen Ansatz nachdenken .
  5. Das Hinzufügen eines Headers für jede Anforderung ist ein besonderer Nachteil beim Arbeiten in einem zustandslosen Protokoll.
  6. Schädliche Proxy-Server können fast alles tun, um HTTP-Anforderungen und -Antworten zu zerstören. Sie sollten nicht , und während ich nicht über die Cache-Control oder Vary Header in diesem Kontext spreche, sollten alle Service-Ersteller vorsichtig sein Überlegen Sie, wie ihr Inhalt in vielen verschiedenen Umgebungen konsumiert wird.
57
Joe Liversedge

Dies ist Ansichtssache; hier ist meins, zusammen mit der Motivation hinter der Meinung. 

  1. füge die Version in die URL ein.
    Für diejenigen, die sagen, dass sie in den HTTP-Header gehören, sage ich: vielleicht. Das Einfügen der URL ist jedoch der akzeptierte Weg, um dies gemäß den frühen Führern der Branche zu tun. (Google, Yahoo, Twitter und mehr). Das erwarten die Entwickler, und das, was die Entwickler erwarten, dh nach dem Prinzip des geringsten Erstaunens zu handeln, ist wahrscheinlich eine gute Idee. Es macht es absolut nicht "schwieriger für Kunden, ein Upgrade durchzuführen". Wenn die Änderung der URL auf irgendeine Weise ein Hindernis für den Entwickler einer konsumierenden Anwendung darstellt, wie in einer anderen Antwort hier vorgeschlagen, muss der Entwickler ausgelöst werden.

  2. Die Nebenversion überspringen
    Es gibt viele Zahlen. Du wirst nicht rauslaufen. Sie brauchen die Dezimalstelle nicht. Jede Änderung von 1.0 auf 1.1 Ihrer API sollte ohnehin keine bestehenden Kunden beeinträchtigen. Verwenden Sie also einfach die natürlichen Zahlen. Wenn Sie die Separation verwenden möchten, um größere Änderungen zu implizieren, können Sie mit v100 und v200 usw. beginnen, aber selbst dort denke ich YAGNI und es ist ein Overkill. 

  3. Setze die Version ganz links in die URI
    Vermutlich werden in Ihrem Modell mehrere Ressourcen vorhanden sein. Sie müssen alle synchron synchronisiert werden. Es gibt keine Benutzer, die Version 1 der Ressource X und Version 2 der Ressource Y verwenden. Es wird etwas kaputt gehen. Wenn Sie versuchen zu unterstützen, wird beim Hinzufügen von Versionen ein Wartungs-Albtraum erstellt, und es gibt für den Entwickler ohnehin keinen Mehrwert. http://api.mydomain.com/v1/Resource/12345, wobei Resource der Ressourcentyp ist und 12345 durch die Ressourcen-ID ersetzt wird. 

Sie haben nicht gefragt, aber ... 

  1. Verben von Ihrem URL-Pfad auslassen
    REST ist ressourcenorientiert. Sie haben Dinge wie "CallFoo" in Ihrem URL-Pfad, der verdächtig wie ein Verb und anders als ein Nomen aussieht. Das ist falsch. Verwenden Sie die Macht, Luke. Verwenden Sie die Verben, die Bestandteil von REST sind: GET PUT POST, DELETE usw. Wenn Sie die Bestätigung für eine Ressource erhalten möchten, führen Sie GET http://domain/v1/Foo/12345/verification aus. Wenn Sie es aktualisieren möchten, führen Sie POST /v1/Foo/12345 aus. 

  2. Optionale Parameter als Abfrageparameter oder Payload setzen
    Die optionalen Parameter sollten sich nicht im URL-Pfad (vor dem ersten Fragezeichen) befinden, es sei denn, Sie schlagen vor, dass diese optionalen Parameter eine eigenständige Ressource darstellen. Also POST /v1/Foo/12345?action=partialUpdate&param1=123&param2=abc

15
Cheeso

Führen Sie keines dieser Dinge aus, da sie die Version in die URI-Struktur verschieben, und dies hat Nachteile für Ihre Clientanwendungen. Es wird für sie schwieriger, ein Upgrade durchzuführen, um die neuen Funktionen in Ihrer Anwendung zu nutzen.

Stattdessen sollten Sie Ihre Medientypen und nicht Ihre URIs versionieren. Dies gibt Ihnen maximale Flexibilität und Entwicklungsfähigkeit. Weitere Informationen finden Sie unter diese Antwort Ich gab eine andere Frage.

4
Brian Kelly

Ich mag es, den Parameter für den Profilmedientyp zu verwenden:

application/json; profile="http://www.myapp.com/schema/entity/v1"

Mehr Info:

http://tools.ietf.org/html/rfc6906

http://buzzword.org.uk/2009/draft-inkster-profile-parameter-00.html

3
Matthew Madson

Es hängt davon ab, wie Sie Versionen in Ihrer API aufrufen. Wenn Sie Versionen zu verschiedenen Repräsentationen (xml, json usw.) der Entitäten aufrufen, sollten Sie die Accept-Header oder einen benutzerdefinierten Header verwenden. Auf diese Weise ist http für die Arbeit mit Repräsentationen ausgelegt. Es ist REST-fähig, denn wenn ich dieselbe Ressource zur selben Zeit anrufe, aber unterschiedliche Repräsentationen anfordert, haben die zurückgegebenen Entitäten genau die gleichen Informationen und die gleiche Struktur der Eigenschaften, aber bei einem anderen Format ist diese Art der Versionierung kosmetisch.

Wenn Sie dagegen "Versionen" als Änderungen in der Entitätsstruktur verstehen, z. B. Hinzufügen eines Felds "Alter" zur "Benutzerentität". Dann sollten Sie dies aus einer Ressourcenperspektive angehen, die meiner Meinung nach der RESTful-Ansatz ist. Wie von Roy Fielding in seiner Dissertation beschrieben ... Eine REST - Ressource ist eine Zuordnung von einem Bezeichner zu einer Menge von Entitäten ... Daher ist es sinnvoll, wenn Sie die Struktur einer Entität ändern, müssen Sie über eine geeignete Ressource verfügen das zeigt auf diese Version. Diese Art der Versionierung ist strukturell. 

Ich machte einen ähnlichen Kommentar in: http://codebetter.com/howarddierking/2012/11/09/versioning-restful-services/

Wenn Sie mit der URL-Version arbeiten, sollte die Version später und nicht früher in der URL erscheinen:

GET/DELETE/PUT onlinemall.com/grocery-store/customer/v1/{id}
POST onlinemall.com/grocery-store/customer/v1

Eine andere Möglichkeit, dies sauberer zu tun, könnte jedoch bei der Implementierung problematisch sein:

GET/DELETE/PUT onlinemall.com/grocery-store/customer.v1/{id}
POST onlinemall.com/grocery-store/customer.v1

Auf diese Weise kann der Client genau die Ressource anfordern, die er für seine Entität benötigt. Ohne sich mit Kopfzeilen und benutzerdefinierten Medientypen herumschlagen zu müssen, ist dies bei der Implementierung in einer Produktionsumgebung wirklich problematisch.

Wenn Sie die URL spät in der URL haben, können die Clients auch bei der Auswahl der gewünschten Ressourcen mehr Granularität haben, selbst auf Methodenebene.

Das Wichtigste aus Sicht der Entwickler ist jedoch, dass Sie nicht die gesamten Zuordnungen (Pfade) für jede Version zu allen Ressourcen und Methoden verwalten müssen. Das ist sehr wertvoll, wenn Sie über viele Subressourcen (eingebettete Ressourcen) verfügen.

Aus Sicht der Implementierung ist die Implementierung auf Ressourcenebene sehr einfach, zum Beispiel bei der Verwendung von Jersey/JAX-RS:

@Path("/customer")
public class CustomerResource {
    ...
    @GET
    @Path("/v{version}/{id}")
    public IDto getCustomer(@PathParam("version") String version, @PathParam("id") String id) {
         return locateVersion(version, customerService.findCustomer(id));
    }
    ...
    @POST
    @Path("/v1")
    @Consumes(MediaType.APPLICATION_JSON)
    public IDto insertCustomerV1(CustomerV1Dto customer) {
         return customerService.createCustomer(customer);
    }

    @POST
    @Path("/v2")
    @Consumes(MediaType.APPLICATION_JSON)
    public IDto insertCustomerV2(CustomerV2Dto customer) {
         return customerService.createCustomer(customer);
    }
...
}

IDto ist nur eine Schnittstelle für die Rückgabe eines polymorphen Objekts, CustomerV1 und CustomerV2 implementieren diese Schnittstelle.

1
raspacorp

Facebook prüft in der URL . Ich glaube, dass die Versionierung von URLs sauberer und einfacher ist, als in der Realität.

.Net macht es sehr einfach, die Versionierung auf diese Weise durchzuführen:

[HttpPost]
[Route("{version}/someCall/{id}")]
public HttpResponseMessage someCall(string version, int id))
0
RayLoveless