Versionierungs-APIs

9

Angenommen, Sie haben ein großes Projekt, das von einer API-Basis unterstützt wird. Das Projekt liefert auch eine öffentliche API, die Endbenutzer verwenden können.

Manchmal müssen Sie Änderungen an der API-Basis vornehmen, die Ihr Projekt unterstützt. Beispielsweise müssen Sie eine Funktion hinzufügen, die eine API-Änderung oder eine neue Methode erfordert oder die Änderung eines der Objekte oder des Formats eines dieser Objekte erfordert, die an oder von der API übergeben werden.

Angenommen, Sie verwenden diese Objekte auch in Ihrer öffentlichen API, ändern sich die öffentlichen Objekte auch jedes Mal, wenn Sie dies tun. Dies ist unerwünscht, da Ihre Clients möglicherweise darauf angewiesen sind, dass die API-Objekte identisch bleiben, damit ihr Parsing-Code funktioniert. (Husten C ++ WSDL-Clients ...)

Eine mögliche Lösung besteht darin, die API zu versionieren. Wenn wir jedoch "Version" der API sagen, muss dies auch bedeuten, dass die API-Objekte versioniert und für jede geänderte Methodensignatur doppelte Methodenaufrufe bereitgestellt werden. Also hätte ich dann für jede Version meiner API ein einfaches altes clr-Objekt, was wiederum unerwünscht erscheint. Und selbst wenn ich das mache, werde ich sicherlich nicht jedes Objekt von Grund auf neu erstellen, da dies zu riesigen Mengen an dupliziertem Code führen würde. Vielmehr wird die API wahrscheinlich die privaten Objekte erweitern, die wir für unsere Basis-API verwenden, aber dann stoßen wir auf dasselbe Problem, da hinzugefügte Eigenschaften auch in der öffentlichen API verfügbar wären, wenn dies nicht der Fall sein sollte.

Was ist also eine Vernunft, die normalerweise auf diese Situation angewendet wird? Ich weiß, dass viele öffentliche Dienste wie Git für Windows eine versionierte API verwalten, aber ich habe Probleme, mir eine Architektur vorzustellen, die dies unterstützt, ohne große Mengen an doppeltem Code, der die verschiedenen versionierten Methoden und Eingabe- / Ausgabeobjekte abdeckt.

Mir ist bewusst, dass Prozesse wie die semantische Versionierung versuchen, ein gewisses Maß an Sicherheit zu schaffen, wenn öffentliche API-Unterbrechungen auftreten sollten. Das Problem ist eher, dass es so scheint, als ob viele oder die meisten Änderungen das Brechen der öffentlichen API erfordern, wenn die Objekte nicht stärker voneinander getrennt sind, aber ich sehe keinen guten Weg, dies zu tun, ohne Code zu duplizieren.

Fall
quelle
1
I don't see a good way to do that without duplicating code- Ihre neue API kann immer Methoden in Ihrer alten API aufrufen oder umgekehrt.
Robert Harvey
2
AutoMapper zur Rettung, leider ja - Sie benötigen unterschiedliche Versionen jedes Vertrags. Vergessen Sie nicht, dass alle Objekte, auf die in Ihrem Vertrag verwiesen wird, Teil dieses Vertrags sind. Das Ergebnis ist, dass Ihre eigentliche Implementierung eine eigene Einzelversion von Modellen haben sollte und Sie die Einzelversion in die verschiedenen Vertragsversionen umwandeln müssen. AutoMapper kann Ihnen hier helfen und die internen Modelle intelligenter machen als die Vertragsmodelle. In Abwesenheit von AutoMapper habe ich Erweiterungsmethoden verwendet, um einfache Übersetzungen zwischen internen Modellen und Vertragsmodellen zu erstellen.
Jimmy Hoffa
Gibt es dafür eine bestimmte Plattform / einen bestimmten Kontext? (dh DLLs, eine REST-API usw.)
GrandmasterB
.NET mit MVC- und Webforms-UI-, DLL-Klassen. Wir haben sowohl eine Ruhe- als auch eine Seifen-API.
Fall

Antworten:

6

Wenn Sie eine API verwalten, die von Dritten verwendet wird, müssen Sie zwangsläufig Änderungen vornehmen. Der Grad der Komplexität hängt von der Art der Änderung ab. Dies sind die wichtigsten Szenarien:

  1. Neue Funktionalität zur vorhandenen API hinzugefügt
  2. Alte Funktionalität von API veraltet
  3. Bestehende Funktionen in der API ändern sich in irgendeiner Weise

Neue Funktionalität zur vorhandenen API hinzufügen

Dies ist das am einfachsten zu unterstützende Szenario. Das Hinzufügen neuer Methoden zu einer API sollte keine Änderungen an vorhandenen Clients erfordern. Dies kann sicher für Clients bereitgestellt werden, die die neue Funktionalität benötigen, da für keinen vorhandenen Client Aktualisierungen vorliegen.

Alte Funktionalität von API veraltet

In diesem Szenario müssen Sie vorhandenen Verbrauchern Ihrer API mitteilen, dass die Funktionalität langfristig nicht unterstützt wird. Bis Sie entweder die Unterstützung für die alte Funktionalität einstellen (oder bis alle Clients auf die neue Funktionalität aktualisiert haben), müssen Sie die alte und die neue API-Funktionalität gleichzeitig beibehalten. Wenn es sich um eine Bibliothek handelt, die bereitgestellt wird, können die meisten Sprachen alte Methoden als veraltet / veraltet markieren. Wenn es sich um einen Dienst eines Drittanbieters handelt, ist es normalerweise am besten, unterschiedliche Endpunkte für die alte / neue Funktionalität zu haben.

Bestehende Funktionen in der API ändern sich in irgendeiner Weise

Dieses Szenario hängt von der Art der Änderung ab. Wenn keine Eingabeparameter mehr verwendet werden müssen, können Sie einfach den Dienst / die Bibliothek aktualisieren, um die jetzt zusätzlichen Daten zu ignorieren. In einer Bibliothek müsste die überladene Methode intern die neue Methode aufrufen, für die weniger Parameter erforderlich sind. In einem gehosteten Dienst muss der Endpunkt zusätzliche Daten ignorieren und beide Clienttypen bedienen und dieselbe Logik ausführen.

Wenn die vorhandene Funktionalität neue erforderliche Elemente hinzufügen muss, müssen Sie über zwei Endpunkte / Methoden für Ihren Dienst / Ihre Bibliothek verfügen. Bis zum Aktualisieren der Clients müssen Sie beide Versionen unterstützen.

andere Gedanken

Rather, the API is likely to extend the private objects we are using for our base API, but then we run into the same problem because added properties would also be available in the public API when they are not supposed to be.

Stellen Sie keine internen privaten Objekte über Ihre Bibliothek / Ihren Dienst bereit. Erstellen Sie Ihre eigenen Typen und ordnen Sie die interne Implementierung zu. Auf diese Weise können Sie interne Änderungen vornehmen und den Aktualisierungsaufwand für externe Clients minimieren.

The problem is more that it seems like many or most changes require breaking the public API if the objects aren't more separated, but I don't see a good way to do that without duplicating code.

Die API, unabhängig davon, ob es sich um einen Dienst oder eine Bibliothek handelt, muss am Integrationspunkt mit den Clients stabil sein. Je mehr Zeit Sie benötigen, um die Ein- und Ausgänge zu ermitteln und als separate Einheiten zu speichern, desto mehr Kopfschmerzen ersparen Sie sich später. Machen Sie den API-Vertrag zu einer eigenen Entität und ordnen Sie ihn den Klassen zu, die die eigentliche Arbeit leisten. Die Zeitersparnis bei der Änderung interner Implementierungen sollte die zusätzliche Zeit, die zum Definieren der zusätzlichen Schnittstelle benötigt wurde, mehr ausgleichen.

Betrachten Sie diesen Schritt nicht als "Code duplizieren". Obwohl sie ähnlich sind, handelt es sich um separate Entitäten, deren Erstellung sich lohnt. Während externe API-Änderungen fast immer eine entsprechende Änderung der internen Implementierung erfordern, sollten interne Implementierungsänderungen die externe API nicht immer ändern.

Beispiel

Angenommen, Sie bieten eine Zahlungsverarbeitungslösung an. Sie verwenden PaymentProviderA, um Kreditkartentransaktionen durchzuführen. Später erhalten Sie über den Zahlungsprozessor von PaymentProviderB einen günstigeren Tarif. Wenn Ihre API Felder für Kreditkarten- / Rechnungsadressen Ihres Typs anstelle der Darstellung von PaymentProviderA verfügbar macht, ist die API-Änderung 0, da die Schnittstelle dieselbe bleibt (hoffentlich trotzdem, wenn PaymentProviderB Daten benötigt, die von PaymentProviderA nicht benötigt wurden, müssen Sie eine der beiden auswählen unterstützen Sie beide oder behalten Sie die schlechtere Rate mit PaymentProviderA).

Phil Patterson
quelle
Vielen Dank für diese sehr detaillierte Antwort. Kennen Sie Beispiele für Open Source-Projekte, die ich lesen könnte, um zu erfahren, wie bestehende Projekte dies getan haben? Ich würde gerne einige konkrete Beispiele dafür sehen, wie sie Code organisiert haben, der es ihnen ermöglicht, dies zu tun, ohne nur ihren verschiedenen POCO-Konstruktionscode in die verschiedenen Versionsobjekte zu kopieren, denn wenn Sie gemeinsam genutzte Methoden aufrufen, müssen Sie ihn aufteilen der freigegebenen Methode, um dieses Objekt für das versionierte Objekt bearbeiten zu können.
Fall
1
Mir sind keine guten Beispiele auf Anhieb bekannt. Wenn ich an diesem Wochenende etwas Zeit hätte, könnte ich eine Test-App erstellen, die auf GitHub verfügbar ist, um zu zeigen, wie einige dieser Dinge implementiert werden könnten.
Phil Patterson
1
Der schwierige Teil ist, dass es auf hoher Ebene so viele verschiedene Möglichkeiten gibt, die Kopplung zwischen Clients und Servern zu minimieren. WCF verfügt über eine Schnittstelle namens IExtensibleDataObject, über die Sie Daten, die nicht im Vertrag enthalten sind, vom Client übergeben und über die Leitung an den Server senden können. Google hat Protobuf für die Kommunikation zwischen Systemen erstellt (es gibt Open Source-Implementierungen für .NET, Java usw.). Darüber hinaus gibt es viele nachrichtenbasierte Systeme, die ebenfalls funktionieren können (vorausgesetzt, Ihr Prozess kann asynchron ausgeführt werden).
Phil Patterson
Es könnte keine schlechte Idee sein, ein bestimmtes Beispiel für die Codeduplizierung zu zeigen, die Sie entfernen möchten (als neue Frage zum Stapelüberlauf), und zu sehen, welche Lösungen die Community findet. Es ist schwierig, die Frage allgemein zu beantworten. Ein bestimmtes Szenario könnte also besser sein.
Phil Patterson