Kompatibilität

Auf dieser Seite wird näher auf die Liste der abwärtskompatiblen und nicht abwärtskompatiblen Änderungen im Abschnitt zur Versionierung eingegangen.

Es ist nicht immer völlig klar, was als nicht abwärtskompatible Änderung zählt. Die hier enthaltenen Angaben sollten als Leitfaden und nicht als umfassende Liste aller möglichen Änderungen betrachtet werden.

Die aufgeführten Regeln beziehen sich nur auf die Clientkompatibilität. Es wird davon ausgegangen, dass sich API-Ersteller ihrer Anforderungen hinsichtlich der Bereitstellung einschließlich Änderungen an den Implementierungsdetails bewusst sind.

Generell sollte die Abwärtskompatibilität der Clients bei Aktualisierungen auf eine neue Nebenversion oder einen Patch erhalten bleiben. Problematisch sind folgende Arten von Inkompatibilitäten:

  • Quellkompatibilität: Wenn für 1.0 geschriebener Code in 1.1 nicht kompiliert werden kann.
  • Binärkompatibilität: Wenn für 1.0 kompilierter Code nicht mit einer Clientbibliothek der Version 1.1 verknüpft/ausgeführt werden kann. Die genauen Details hängen von der Clientplattform ab und können je nach Situation variieren.
  • Verbindungskompatibilität: Wenn eine für 1.0 erstellte Anwendung nicht mit einem Server der Version 1.1 kommunizieren kann.
  • Semantische Kompatibilität: Wenn alles funktioniert, es aber zu unbeabsichtigten oder überraschenden Ergebnissen kommt.

Anders ausgedrückt: Alte Clients sollten mit neuen Servern innerhalb derselben Hauptversionsnummer zusammenarbeiten können. Wenn eine Aktualisierung auf eine neue Nebenversion gewünscht ist, um beispielsweise eine neue Funktion zu nutzen, sollte dies auf einfache Weise möglich sein.

Neben theoretischen, protokollbasierten Abwägungen bestehen aufgrund der vorhandenen Clientbibliotheken, die sowohl automatisch generierten als auch manuell geschriebenen Code enthalten, auch praktische Überlegungen. Testen Sie erwogene Änderungen nach Möglichkeit, indem Sie neue Versionen von Clientbibliotheken erstellen und prüfen, ob diese weiterhin kompatibel sind.

In der nachfolgenden Erläuterung werden Proto-Nachrichten in drei Kategorien unterteilt:

  • Anfragenachrichten (z. B. GetBookRequest)
  • Antwortnachrichten (z. B. ListBooksResponse)
  • Ressourcennachrichten (z. B. Book und alle Nachrichten, die in anderen Ressourcennachrichten verwendet werden)

Für diese Kategorien gelten unterschiedliche Regeln, da Anfragenachrichten immer nur vom Client an den Server, Antwortnachrichten immer nur vom Server an den Client und Ressourcennachrichten in der Regel in beide Richtungen gesendet werden. Speziell bei aktualisierbaren Ressourcen ist zu berücksichtigen, dass diese einen "Read-Modify-Write"-Zyklus haben.

Abwärtskompatible Änderungen

API-Schnittstelle einer API-Dienstdefinition hinzufügen

Hinsichtlich der Protokolle ist dies immer ein sicherer Prozess. Der einzige Nachteil besteht darin, dass der Name der neuen API-Schnittstelle in Clientbibliotheken möglicherweise bereits in manuell geschriebenem Code verwendet wurde. Dies ist bei einer neuen Schnittstelle, die gegenüber bestehenden Schnittstellen vollständig orthogonal ist, unwahrscheinlich. Handelt es sich jedoch um eine vereinfachte Version einer bestehenden Schnittstelle, kann es eher zu Konflikten kommen.

Methode einer API-Schnittstelle hinzufügen

Sofern Sie keine Methode hinzufügen, die mit einer bereits in Clientbibliotheken generierten Methode in Konflikt steht, sollte dies problemlos möglich sein.

(Beispiel, bei dem es zu einem Bruch kommen könnte: Wenn Sie eine GetFoo-Methode haben, erstellt der C #-Codegenerator bereits die Methoden GetFoo und GetFooAsync. Das Hinzufügen einer GetFooAsync-Methode in Ihrer API-Schnittstelle wäre daher aus Sicht der Clientbibliothek eine bahnbrechende Änderung.)

HTTP-Bindung einer Methode hinzufügen

Wenn durch die Bindung keine Mehrdeutigkeiten entstehen, kann der Server so konfiguriert werden, dass er auf eine zuvor abgelehnte URL reagiert. Dies kann bei Anwendung eines bestehenden Vorgangs auf ein neues Namensmuster für Ressourcen durchgeführt werden.

Feld einer Anfragenachricht hinzufügen

Beim Hinzufügen von Anfragefeldern kann die Abwärtskompatibilität erhalten bleiben, solange Clients, die das Feld nicht angeben, in der neuen Version genauso wie in der alten Version behandelt werden.

Das offensichtlichste Beispiel dafür, wo dies falsch gemacht werden kann, ist die Paginierung: Wenn Version 1.0 der API keine Paginierung für eine Sammlung enthält, kann sie in Version 1.1 nicht hinzugefügt werden, es sei denn, die Standardeinstellung page_size wird als unendlich behandelt (was im Allgemeinen eine schlechte Idee ist). Clients der Version 1.0, die davon ausgehen, auf eine Anfrage eine vollständige Ergebnisliste zu erhalten, könnten andernfalls eine abgeschnittene Liste erhalten, ohne zu erkennen, dass die Sammlung weitere Ressourcen umfasst.

Feld einer Antwortnachricht hinzufügen

Eine Antwortnachricht, die keine Ressource ist (z. B. ListBooksResponse), kann erweitert werden, ohne dass Clients beschädigt werden, sofern dies das Verhalten anderer Antwortfelder nicht ändert. Alle Felder, die zuvor durch eine Antwort gefüllt wurden, sollten weiterhin mit derselben Semantik gefüllt werden, selbst wenn dadurch Redundanzen entstehen.

Beispielsweise kann eine Abfrageantwort in 1.0 das boolesche Feld contained_duplicates enthalten, um anzugeben, dass einige Ergebnisse aufgrund von Duplikaten ausgelassen wurden. In 1.1 könnten weitere Details in einem Feld vom Typ duplicate_count angegeben werden. Auch wenn es aus Sicht von 1.1 redundant ist, muss das Feld contained_duplicates ausgefüllt werden.

Wert einer Enumeration hinzufügen

Eine Enumeration, die nur in einer Anfragenachricht verwendet wird, kann frei auf neue Elemente erweitert werden. Mit dem Muster für die Ressourcenansicht kann einer neuen Nebenversion beispielsweise eine neue Ansicht hinzugefügt werden. Da Clients diese Enumeration nie empfangen müssen, brauchen sie auch keine für sie irrelevanten Werte zu erkennen.

Bei Ressourcennachrichten und Antwortnachrichten wird standardmäßig davon ausgegangen, dass Clients ihnen unbekannte ENUM-Werte verarbeiten sollten. API-Ersteller sollten sich jedoch bewusst sein, dass das Schreiben von Anwendungen, die neue ENUM-Elemente richtig verarbeiten, schwierig sein kann. API-Inhaber sollten das erwartete Clientverhalten bei unbekannten ENUM-Werten dokumentieren.

Mit Proto3 können Clients einen ihnen unbekannten Wert empfangen und die Nachricht unter Beibehaltung des Werts neu serialisieren, um den "Read-Modify-Write"-Zyklus nicht zu unterbrechen. Mit dem JSON-Format kann ein numerischer Wert gesendet werden, dessen "Name" unbekannt ist. Der Server weiß in der Regel jedoch nicht, ob der Client einen bestimmten Wert kennt. JSON-Clients erkennen daher möglicherweise, dass sie einen bisher unbekannten Wert empfangen haben. Sie sehen jedoch entweder nur den Namen oder nur die Zahl, aber nicht beides. Dieses Feld sollte durch die Rückgabe desselben Werts an den Server in einem "Read-Modify-Write"-Zyklus nicht geändert werden, da der Server beide Formen erkennen sollte.

Reines Ausgaberessourcenfeld hinzufügen

Felder in einer Ressourcenentität, die nur vom Server gesendet werden, können hinzugefügt werden. Der Server kann validieren, dass jegliche vom Client in einer Anfrage gesendeten Werte gültig sind. Durch Weglassen des Werts darf jedoch kein Fehler ausgegeben werden.

Nicht abwärtskompatible Änderungen

Dienst, Feld, Methode oder ENUM-Wert entfernen oder umbenennen

Grundsätzlich gilt Folgendes: Wenn durch den Clientcode auf etwas verwiesen wird, geht die Abwärtskompatibilität durch Entfernen oder Umbenennen des referenzierten Objekts verloren, sodass eine neue Hauptversion erstellt werden muss. Code, der auf den alten Namen verweist, führt in manchen Sprachen wie C# und Java zu Kompilierungsfehlern, während in anderen Sprachen Fehler bei der Ausführungszeit oder Datenverluste auftreten können. Die Verbindungskompatibilität ist hier irrelevant.

HTTP-Bindung ändern

"Ändern" bedeutet hier tatsächlich "Löschen und Hinzufügen". Nehmen wir an, Sie möchten PATCH unterstützen, aber Ihre veröffentlichte Version unterstützt PUT, oder Sie haben den falschen benutzerdefinierten Verbnamen verwendet. In diesem Fall können Sie die neue Bindung hinzufügen, dürfen aber die alte Bindung nicht entfernen, da durch Entfernen einer Dienstmethode die Abwärtskompatibilität verloren geht.

Feldtyp ändern

Selbst wenn der neue Typ verbindungskompatibel ist, kann sich dadurch der für Clientbibliotheken generierte Code ändern, was eine neue Hauptversion erforderlich macht. Bei kompilierten, statisch orientierten Sprachen kann dies zu Kompilierungszeitfehlern führen.

Namensformat einer Ressource ändern

Der Name einer Ressource darf nicht geändert werden. Dies bedeutet, dass Sammlungsnamen beibehalten werden müssen.

Im Gegensatz zu den meisten nicht abwärtskompatiblen Änderungen wirkt sich dies auch auf Hauptversionen aus. Wenn ein Client über Version 2.0 auf eine Ressource zugreifen kann, die in Version 1.0 erstellt wurde, oder umgekehrt, sollte in beiden Versionen derselbe Ressourcenname verwendet werden.

Genauer gesagt sollte sich auch die Reihe der gültigen Ressourcennamen aus folgenden Gründen nicht ändern:

  • Durch Erhöhung der Einschränkungen kann eine bisher erfolgreiche Anfrage fehlschlagen.
  • Durch Verringerung der Einschränkungen gegenüber der vorherigen Dokumentation kann die Abwärtskompatibilität von Clients, die aufgrund der vorherigen Dokumentation von Annahmen ausgehen, verloren gehen. Clients speichern Ressourcennamen häufig an anderen Orten, wobei die Reihe der zulässigen Zeichen und die Länge des Namens Auswirkungen haben können. Alternativ können Clients den Ressourcennamen selbst validieren, um der Dokumentation zu folgen. Beispiel: Bei der Einführung längerer EC2-Ressourcen-IDs hat Amazon Kunden vorab wiederholt darauf hingewiesen und einen Migrationszeitraum gewährt.

Eine solche Änderung ist möglicherweise nur in der Proto-Dokumentation sichtbar. Wenn Sie daher eine Befehlszeile hinsichtlich ihrer Abwärtskompatibilität prüfen, reicht es nicht, nur die kommentarlosen Änderungen zu prüfen.

Sichtbares Verhalten bestehender Anfragen ändern

Clients sind oft vom Verhalten und der Semantik der API abhängig, selbst wenn dieses Verhalten nicht explizit unterstützt oder dokumentiert wird. Daher sind Änderungen des Verhaltens oder der Semantik von API-Daten aus Sicht der Nutzer meist nicht abwärtskompatibel. Wenn das Verhalten nicht durch Verschlüsselung ausgeblendet wird, sollten Sie davon ausgehen, dass Nutzer es gefunden haben und davon abhängig sind.

Es ist daher auch von Vorteil, Paginierungstoken zu verschlüsseln, selbst wenn die Daten irrelevant sind. Dies verhindert, dass Nutzer eigene Token erstellen und die Abwärtskompatibilität durch Änderungen am Tokenverhalten möglicherweise verloren geht.

URL-Format in der HTTP-Definition ändern

Neben den oben aufgeführten Änderungen an den Ressourcennamen sind hier zwei Arten von Änderungen zu berücksichtigen:

  • Benutzerdefinierte Methodennamen: Diese sind zwar nicht Teil des Ressourcennamens, aber der URL, an die REST-Clients Anfragen senden. Die Abwärtskompatibilität von gRPC-Clients sollte bei Änderungen am benutzerdefinierten Methodennamen erhalten bleiben. In öffentlichen APIs muss jedoch davon ausgegangen werden, dass es sich um REST-Clients handelt.
  • Ressourcenparameternamen: Eine Änderung von v1/shelves/{shelf}/books/{book} in v1/shelves/{shelf_id}/books/{book_id} wirkt sich nicht auf den ersetzten Ressourcennamen aus, kann sich jedoch auf die Codegenerierung auswirken.

Lese-/Schreibfeld einer Ressourcennachricht hinzufügen

Clients führen häufig "Read-Modify-Write"-Vorgänge durch. Die meisten Clients liefern keine Werte für ihnen unbekannte Felder. Dies wird speziell in proto3 nicht unterstützt. Sie können angeben, dass fehlende Felder von Nachrichtentypen (eher als primitive Typen) nicht aktualisiert werden. Dies erschwert jedoch das explizite Entfernen eines solchen Feldwerts aus einer Entität. Primitive Typen (einschließlich string und bytes) können einfach nicht auf diese Weise behandelt werden, da es in proto3 keinen Unterschied gibt, ob ein int32-Feld explizit als 0 angegeben oder überhaupt nicht angegeben wird.

Wenn alle Aktualisierungen mithilfe einer Feldmaske durchgeführt werden, ist dies kein Problem, da der Client unbekannte Felder standardmäßig nicht überschreibt. Dies wäre bei APIs jedoch ungewöhnlich, da sie meist Aktualisierungen der "gesamten Ressource" zulassen.