Was ist ein empfohlenes Muster für die Planung von REST-Endpunkten für vorausschauende Änderungen?

25

Der Versuch, eine API für externe Anwendungen mit Weitblick auf Veränderungen zu entwerfen, ist nicht einfach, aber ein wenig Vorausdenken kann das Leben später einfacher machen. Ich versuche, ein Schema zu erstellen, das zukünftige Änderungen unterstützt und dabei abwärtskompatibel bleibt, indem Handler früherer Versionen beibehalten werden.

In diesem Artikel geht es in erster Linie darum, nach welchem ​​Muster für alle definierten Endpunkte für ein bestimmtes Produkt / Unternehmen vorgegangen werden soll.

Basisschema

Angesichts einer Basis-URL-Vorlage von habe https://rest.product.com/ich mir ausgedacht, dass sich alle Dienste /apizusammen mit /authund anderen nicht auf Rest basierenden Endpunkten wie befinden /doc. Daher kann ich die Basisendpunkte wie folgt festlegen:

https://rest.product.com/api/...
https://rest.product.com/auth/login
https://rest.product.com/auth/logout
https://rest.product.com/doc/...

Service-Endpunkte

Nun zu den Endpunkten. Die Sorge um POST, GET, DELETEist nicht das primäre Ziel dieses Artikels und die Sorge über diese Handlungen selbst.

Endpunkte können in Namespaces und Aktionen unterteilt werden. Jede Aktion muss sich auch so präsentieren, dass grundlegende Änderungen des Rückgabetyps oder der erforderlichen Parameter unterstützt werden.

Wenn Sie einen hypothetischen Chat-Dienst nutzen, über den registrierte Benutzer Nachrichten senden können, haben wir möglicherweise die folgenden Endpunkte:

https://rest.product.com/api/messages/list/{user}
https://rest.product.com/api/messages/send

Fügen Sie jetzt die Versionsunterstützung für zukünftige API-Änderungen hinzu, die möglicherweise nicht mehr funktionieren. Wir könnten entweder die Versionssignatur nach /api/oder nach hinzufügen /messages/. Ausgehend vom sendEndpunkt könnten wir dann für v1 Folgendes haben.

https://rest.product.com/api/v1/messages/send
https://rest.product.com/api/messages/v1/send

Meine erste Frage lautet also: Was ist ein empfohlener Ort für die Versionskennung?

Controller-Code verwalten

Jetzt haben wir also festgestellt, dass wir frühere Versionen unterstützen müssen, um irgendwie mit Code für jede der neuen Versionen fertig zu werden, die mit der Zeit veraltet sein können. Angenommen, wir schreiben Endpunkte in Java, könnten wir dies durch Pakete verwalten.

package com.product.messages.v1;
public interface MessageController {
    void send();
    Message[] list();
}

Dies hat den Vorteil, dass der gesamte Code durch Namespaces getrennt wurde, wobei jede Änderung dazu führen würde, dass eine neue Kopie der Service-Endpunkte erstellt wird. Dies hat den Nachteil, dass der gesamte Code kopiert und Fehlerbehebungen, die auf neue und frühere Versionen angewendet werden sollen, für jede Kopie angewendet / getestet werden müssen.

Ein anderer Ansatz besteht darin, Handler für jeden Endpunkt zu erstellen.

package com.product.messages;
public class MessageServiceImpl {
    public void send(String version) {
        getMessageSender(version).send();
    }
    // Assume we have a List of senders in order of newest to oldest.
    private MessageSender getMessageSender(String version) {
        for (MessageSender s : senders) {
            if (s.supportsVersion(version)) {
                return s;
            }
        }
    }
}

Dies isoliert nun die Versionsverwaltung für jeden Endpunkt selbst und macht Fehlerbehebungen für den Backport kompatibel, da sie in den meisten Fällen nur einmal angewendet werden müssen. Dies bedeutet jedoch, dass wir für jeden einzelnen Endpunkt einiges mehr Arbeit aufwenden müssen, um dies zu unterstützen.

Es gibt also meine zweite Frage: "Wie lässt sich REST-Servicecode am besten so gestalten, dass er frühere Versionen unterstützt?"

Brett Ryan
quelle

Antworten:

13

Es gibt also meine zweite Frage: "Wie lässt sich REST-Servicecode am besten so gestalten, dass er frühere Versionen unterstützt?"

Eine sehr sorgfältig entworfene und orthogonale API muss wahrscheinlich niemals auf abwärts inkompatible Weise geändert werden. Der beste Weg ist also, keine zukünftigen Versionen zu haben.

Natürlich werden Sie das beim ersten Versuch wahrscheinlich nicht wirklich verstehen. So:

  • Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Versionsverwaltung Stellen Sie sicher, dass Ihre Partner wissen, dass sich die API ändern kann, und dass ihre Anwendungen prüfen sollten, ob sie die neueste Version verwenden. und raten Benutzern, ein Upgrade durchzuführen, wenn ein neues verfügbar ist. Zwei alte Versionen zu unterstützen ist schwierig, fünf zu unterstützen ist unhaltbar.
  • Widerstehen Sie dem Drang, die API-Version mit jedem "Release" zu aktualisieren. Die neuen Funktionen können normalerweise in die aktuelle Version übernommen werden, ohne vorhandene Clients zu beschädigen. Sie sind neue Funktionen. Das Letzte, was Sie möchten, ist, dass Clients die Versionsnummer ignorieren , da sie sowieso meistens abwärtskompatibel ist. Aktualisieren Sie die API-Version nur, wenn Sie absolut nicht vorankommen können, ohne die vorhandene API zu beschädigen.
  • Wenn eine neue Version erstellt werden soll, sollte der allererste Client die abwärtskompatible Implementierung der vorherigen Version sein. Die "Wartungs-API" sollte selbst in der aktuellen API implementiert sein. Auf diese Weise sind Sie nicht in der Lage, mehrere vollständige Implementierungen zu verwalten. nur die aktuelle Version und mehrere "Shells" für die alten Versionen. Das Ausführen eines Regressionstests für die jetzt veraltete API auf dem abwärtskompatiblen Client ist eine gute Möglichkeit, sowohl die neue API als auch die Kompatibilitätsebene zu testen.
SingleNegationElimination
quelle
3

Die erste URI-Entwurfsoption drückt besser aus, dass Sie die gesamte API versionieren. Die zweite könnte so interpretiert werden, dass die Nachrichten selbst versioniert werden. Das ist also besser, IMO:

rest.product.com/api/v1/messages/send

Für die Client-Bibliothek halte ich die Verwendung von zwei vollständigen Implementierungen in separaten Java-Paketen für sauberer, benutzerfreundlicher und wartungsfreundlicher.

Davon abgesehen gibt es bessere Methoden zum Entwickeln von APIs als die Versionierung. Ich stimme Ihnen zu, dass Sie darauf vorbereitet sein sollten, aber ich denke, dass die Versionierung die Methode des letzten Auswegs ist, um vorsichtig und sparsam eingesetzt zu werden. Es ist ein relativ großer Aufwand für Kunden, ein Upgrade durchzuführen. Vor einiger Zeit habe ich einige dieser Gedanken in einen Blogbeitrag geschrieben:

http://theamiableapi.com/2011/10/18/api-design-best-practice-plan-for-evolution/

Für die REST-API-Versionierung finden Sie diesen Beitrag von Mark Nottingham hilfreich:

http://www.mnot.net/blog/2011/10/25/web_api_versioning_smackdown

Ferenc Mihaly
quelle
3

Ein anderer Ansatz für die API-Versionierung ist die Verwendung von Version in HTTP-Headern. Mögen

POST /messages/list/{user} HTTP/1.1
Host: http://rest.service.com
Content-Type: application/json
API-Version: 1.0      <----- like here
Cache-Control: no-cache

Sie können den Header analysieren und im Backend entsprechend behandeln.

Bei diesem Ansatz müssen die Clients nicht die URL ändern, sondern nur den Header. Und das macht die REST-Endpunkte immer sauberer.

Wenn einer der Clients den Versionsheader nicht gesendet hat, senden Sie entweder eine 400 - Bad-Anfrage oder Sie können sie mit einer abwärtskompatiblen Version Ihrer API verarbeiten.

sincerekamal
quelle