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 /api
zusammen mit /auth
und 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
, DELETE
ist 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 send
Endpunkt 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?"