API-Änderungen

GraphQL-Abkündigung ohne Versionsnummer

5 Min. Lesezeit

Eine REST-API kann /v2/ neben /v1/ ausliefern und Aufrufer in ihrem eigenen Tempo wechseln lassen. GraphQL hat ein Schema an einem Endpunkt, und jeder Client, die Mobile-App auf letztjährigem Build und das interne Dashboard von heute Morgen, fragt denselben Graphen ab. Es gibt keine URL zum Forken. Ein Feld abzukündigen bedeutet, es an Ort und Stelle als abgekündigt zu markieren, in einem Schema, von dem schon alle abhängen, was die Disziplin anders macht als bei REST, obwohl das zugrunde liegende Problem, Aufrufern zu sagen, dass etwas verschwindet, dasselbe ist, das API-Abkündigung allgemein behandelt.

Wie markiert GraphQL ein Feld als abgekündigt, wenn es keine Version zum Erhöhen gibt?

Mit der @deprecated-Direktive, angewendet direkt auf das Feld:

type Product {
  price: Float @deprecated(reason: "Use priceV2 for multi-currency support.")
  priceV2: Money
}

Das Feld bleibt abfragbar. Es verschwindet nicht, gibt keinen 404 zurück, ändert das Verhalten nicht; es trägt nur eine maschinenlesbare Notiz, die die meisten GraphQL-Tools, GraphiQL, Apollo Studio, Schema-Linter, jedem zeigen, der das Schema durchsucht oder eine Query dagegen schreibt. Das ist der gesamte Mechanismus. Es gibt keinen separaten Abkündigungs-Endpunkt, keinen Header, kein vom Spec verlangtes Begleitdokument, was sowohl den Reiz als auch die Falle ausmacht: Die Direktive ist leicht hinzuzufügen und leicht zu ignorieren, weil nichts einen Client zwingt, hinzuschauen.

Sieht überhaupt jemand den Abkündigungsgrund?

Nur Leute, die das Schema direkt nutzen, durch Introspektion oder einen schemabewussten Editor, und das ist ein kleineres Publikum als die üblichen Leser eines API-Changelogs. Eine Mobile-App, die vor sechs Monaten gegen eine Query gebaut wurde, hat diese Query schon in ihr Binary eingebacken; sie wird weiter nach price fragen und weiter eine Antwort bekommen, abgekündigt oder nicht, bis jemand die App mit dem neuen Feld neu baut und ein Update ausliefert. Die Direktive sagt einer Entwicklerin, die neuen Code schreibt, das alte Feld nicht zu nutzen. Für den bereits ausgelieferten und laufenden Client tut sie nichts.

MechanismusWen er erreicht
@deprecated-DirektiveEntwicklerinnen, die das Schema durchsuchen oder neue Queries schreiben
CI-Fehler durch Schema-LinterDas Team, dem die Client-Codebase gehört, sofern es einen betreibt
Ein Changelog-EintragWer auch immer ihn liest, auch ein Client-Team ohne Linter
Nichts (Feld funktioniert einfach)Ein bereits gebauter Client, der das alte Feld nutzt

Sollte ein abgekündigtes Feld trotzdem einen Changelog-Eintrag bekommen?

Ja, und der leistet mehr als die Direktive allein, weil ein Changelog Leute erreicht, die die Direktive nicht erreicht: ein Partnerteam, das den Graphen konsumiert, ohne sein Schema zu durchsuchen, ein Client, der gegen eine monatealte gecachte Kopie des Schemas gebaut wurde, jede, die es nur durch Lesen von Prosa bemerken würde. API-Changelog behandelt allgemein, was ein Eintrag einem Aufrufer schuldet; ein GraphQL-Eintrag schuldet eine Sache, die REST selten ausbuchstabieren muss, weil REST-Aufrufer sie aus der Versionsnummer ableiten: ob das alte Feld heute noch funktioniert, mit Warnung noch funktioniert, oder tatsächlich aufgehört hat, Daten zurückzugeben. Die Direktive allein beantwortet davon nichts für eine Leserin, die das Schema nie geöffnet hat.

Wann ist ein Feld tatsächlich sicher aus dem Schema zu entfernen?

Erst wenn Query-Logs zeigen, dass niemand mehr danach fragt, was eine Nutzungsfrage ist, keine Kalenderfrage. Ein Feld kann ein Jahr lang @deprecated tragen und trotzdem tragend sein für einen Client, der nie neu gebaut wurde; es auf einem festen Zeitplan zu entfernen, so wie es ein REST-Sunset-Header oft tut, bricht diesen Client ohne Warnung, auf die er reagieren kann, weil GraphQL ihm nichts gibt, worauf er reagieren könnte, außer der Direktive, die er nie gelesen hat. Loggt die feldweise Nutzung, bevor ihr euch auf ein Entfernungsdatum festlegt, und behandelt eine Query-Zahl über null als Bremse, nicht als Countdown.

Trägt das Hinzufügen eines Felds dasselbe Risiko wie bei einer REST-API?

Weniger, für ein neues Feld, weil ein GraphQL-Client nur die Felder erhält, um die er explizit bittet. priceV2 neben price hinzuzufügen kann eine bestehende Query nicht auf die Weise brechen, wie das Hinzufügen eines Felds zu einer REST-JSON-Antwort einen strikten Deserializer brechen kann, weil nichts den Client zwingt, das neue Feld anzufragen. Einen Wert zu einem bestehenden Enum hinzuzufügen ist die Ausnahme, die es wert ist, im selben Atemzug zu nennen: ein Client, der über jeden Enum-Wert erschöpfend switcht, was streng typisierte Sprachen fördern, bricht in dem Moment, in dem ein neuer Wert auftaucht, egal ob irgendeine Query danach gefragt hat. Die Sicherheit gilt nur für Felder und Union-Mitglieder, die ein Client bewusst anfragt; sie gilt nicht für eine geschlossene Menge, die der Code eines Clients von Hand aufzählt.

Was braucht ein GraphQL-Changelog-Eintrag, das ein REST-Eintrag nicht braucht?

Die Query-Form, nicht nur den Feldnamen, weil „das Feld price ist abgekündigt” genau das fehlende Stück ist, das eine Aufruferin tatsächlich braucht: welche Typen und welche Queries es berühren. Ein nützlicher Eintrag nennt den Typ, das Feld, das Ersatzfeld und, wenn ihr es generieren könnt, die tatsächlichen Queries in Produktion, die noch die alte Form anfragen. Dieses letzte Stück, die Abkündigungsmeldung mit echter Nutzung zu verknüpfen, ist das, was REST-Aufrufer aus Server-Logs zu einer URL kostenlos bekommen und GraphQL-Aufrufer nicht, weil jede Query denselben Endpunkt trifft, egal was sie anfragt.

Kann außer einem Feld noch etwas anderes die @deprecated-Direktive tragen?

Enum-Werte, mit derselben Direktive an der Definition des Werts selbst statt am Feld:

enum ShippingMethod {
  STANDARD
  EXPRESS
  OVERNIGHT @deprecated(reason: "Use EXPRESS with priority: true instead.")
}

Die Spec definiert @deprecated für genau zwei Stellen, eine Feld-Definition oder einen Enum-Wert, und nichts sonst im Stand des stabilen Releases; Deprecation auf Argument- oder Input-Feld-Ebene existiert nur in späterer Draft-Sprache, nicht in dem, was die meisten Server heute implementieren. Ein so markierter Enum-Wert bleibt ein gültiger Wert, den ein Server weiterhin zurückgeben oder akzeptieren kann, dasselbe nicht-brechende Versprechen wie bei einem abgekündigten Feld, was ihn sicher macht, bevor der Wert wirklich entfernt wird.

FAQ

Unterstützt GraphQL irgendetwas wie einen Sunset-Header für einen ganzen Endpunkt? Nein, weil es meist nur einen Endpunkt gibt. Das Timing der Abkündigung lebt auf Feldebene, im Grundtext der @deprecated-Direktive und in was auch immer für ein Changelog oder Migrationsguide ein Team daneben veröffentlicht, nicht in einem Response-Header, den ein Client programmatisch lesen kann.

Kann ein abgekündigtes Feld später mit einem anderen Typ neu hinzugefügt werden? Nur als neuer Feldname. Denselben Feldnamen mit geändertem Typ wieder einzuführen ist genau der Breaking Change, den der Abkündigungszyklus vermeiden soll; gebt dem Ersatz einen eigenen Namen, so wie priceV2 es tut, und lasst den alten vollständig ausklingen, bevor der Name wieder frei wird.

Sollte der @deprecated-Grundtext auf den Changelog-Eintrag verlinken? Ja, wenn das Schema-Tooling das unterstützt. Das Grund-Feld akzeptiert einen einfachen String, und eine URL darin ist der kürzeste Weg von einer Entwicklerin, die auf Introspektions-Output starrt, zur ausführlicheren Erklärung, die ein Changelog-Eintrag geben kann.

Ist eine GraphQL-Schema-Änderung je abwärtskompatibel auf eine Art, die REST nicht ist? Additive Feldänderungen ja, aus dem oben genannten Grund: Clients bekommen nur, wonach sie fragen. Neue Enum-Werte sind die Ausnahme, weil ein Client, der eine geschlossene Menge aufzählt, an einem Wert brechen kann, den er nicht erwartet hat. Entfernungen und Typänderungen sind genau so brechend wie ihre REST-Äquivalente.


Die technischen Aussagen in diesem Artikel wurden nicht unabhängig geprüft. Wenn etwas nicht stimmt, sagen Sie es uns, und wir korrigieren es.

Mehr bei changeloop: Entwicklerdokumentation, Changelog-Tools im Vergleich

changeloop
Das Team hinter einem Changelog, das den Kreis schließt. Ihre Nutzer fragen, Ihr Team liefert, und wer gefragt hat, erfährt davon.