Konzepte, Ideen, Strategien
Konzepte, Ideen, StrategienStrategien für die Versionierung von Feldern und Direktiven

Strategien für die Versionierung von Feldern und Direktiven

Lies zuerst die Anleitung Das Schema durch Feldversionierung weiterentwickeln, die die Funktion „field versioning" in Gato GraphQL erklärt.

Gato GraphQL ermöglicht es Feldern und Direktiven, das Argument versionConstraint zu empfangen, um zu wählen, welche spezifische Version (d. h. Implementierung) des Feldes/der Direktive verwendet werden soll:

query GetPosts {
  posts(versionConstraint: "^1.0") {
    id
    title(versionConstraint: ">=2.1")
    excerpt @strUpperCase(versionConstraint: "~1.5.3")
  }
}

Was sollte passieren, wenn wir das Argument versionConstraint nicht angeben? Zu welcher Version sollte zum Beispiel das Feld surname in der nachstehenden Query aufgelöst werden?

query GetSurname {
  account(id: 1) {
    # Welche Version soll verwendet werden? 1.0.0? 2.0.0?
    surname
  }
}

Hier haben wir zwei Anliegen:

  1. Entscheiden, welche die Standardversion ist, wenn keine angegeben wird
  2. Den Client darüber informieren, dass es mehrere Versionen zur Auswahl gibt

Bevor wir diese Anliegen angehen, müssen wir herausfinden, wie gut GraphQL kontextuelles Feedback beim Ausführen einer Query liefert.

Kontextuelles Feedback beim Ausführen von Queries

Wir müssen auf einen weniger idealen Umstand bei GraphQL hinweisen: Es bietet derzeit keine guten kontextuellen Informationen beim Ausführen von Queries. Das zeigt sich deutlich bei Deprecations, wo Deprecation-Daten nur über Introspection angezeigt werden, indem die Felder isDeprecated und deprecationReason der Typen Field und Enum abgefragt werden:

{
  __type(name: "Account") {
    name
    fields {
      name
      isDeprecated
      deprecationReason
    }
  }
}

Die Antwort lautet:

{
  "data": {
    "__type": {
      "name": "Account",
      "fields": [
        {
          "name": "id",
          "isDeprecated": false,
          "deprecationReason": null
        },
        {
          "name": "name",
          "isDeprecated": false,
          "deprecationReason": null
        },
        {
          "name": "surname",
          "isDeprecated": true,
          "deprecationReason": "Use `personSurname`"
        },
        {
          "name": "personSurname",
          "isDeprecated": false,
          "deprecationReason": null
        }
      ]
    }
  }
}

Wenn jedoch eine Query mit einem veralteten Feld ausgeführt wird…

query GetSurname {
  account(id: 1) {
    surname
  }
}

…erscheinen die Deprecation-Informationen nicht in der Antwort:

{
  "data": {
    "account": {
      "surname": "Owens"
    }
  }
}

Das bedeutet, dass der Entwickler, der die Query ausführt, aktiv Introspection-Queries ausführen muss, um herauszufinden, ob das Schema aktualisiert und ein Feld als veraltet markiert wurde. Das passiert vielleicht… ab und zu? Möglicherweise nie?

Es wäre eine erhebliche Verbesserung für die Überarbeitung veralteter Queries, wenn die GraphQL-API Deprecation-Informationen beim Ausführen von Queries mit veralteten Feldern bereitstellen würde. Idealerweise könnten diese Informationen unter einem neuen Top-Level-Eintrag deprecations bereitgestellt werden, der nach errors und vor data erscheint (gemäß dem Vorschlag der Spezifikation für das Antwortformat).

Da ein Top-Level-Eintrag deprecations nicht Teil der Spezifikation ist, fügt die Funktion „Proactive Feedback" von Gato GraphQL Unterstützung für besseres Feedback in der Antwort auf die Query hinzu, indem der generische Top-Level-Eintrag extensions verwendet wird, der eine Erweiterung des Protokolls nach Bedarf ermöglicht:

Deprecation-Informationen in der Query-Antwort

Versionen über Warnungen bekannt machen

Wir haben gerade gelernt, dass der GraphQL-Server den Top-Level-Eintrag extensions verwenden kann, um Deprecations bereitzustellen. Wir können dieselbe Methodik verwenden, um einen warnings-Eintrag hinzuzufügen, in dem wir den Entwickler darüber informieren, dass ein Feld versioniert wurde. Diese Informationen stellen wir nicht immer bereit; nur wenn die Query ein versioniertes Feld betrifft und das Argument versionConstraint fehlt.

Die Standardversion für ein Feld festlegen

Es gibt verschiedene Ansätze, die wir verwenden können, darunter:

  1. versionConstraint obligatorisch machen
  2. Die alte Version standardmäßig bis zu einem bestimmten Datum verwenden, an dem die neue Version zur Standardversion wird
  3. Die neueste Version standardmäßig verwenden und die Query-Entwickler ermutigen, explizit anzugeben, welche Version verwendet werden soll

Lass uns jede dieser Strategien erkunden und ihre Antworten beim Ausführen dieser Query ansehen:

query GetSurname {
  account(id: 1) {
    surname
  }
}

1. versionConstraint obligatorisch machen

Das ist die offensichtlichste Lösung: Den Client daran hindern, die Versionseinschränkung nicht anzugeben, indem das Feldargument obligatorisch gemacht wird. Wenn es dann nicht angegeben wird, gibt die Query einen Fehler zurück.

Das Ausführen der Query antwortet mit:

{
  "errors": [
    {
      "message": "Argument 'versionConstraint' in field 'surname' cannot be empty"
    }
  ],
  "data": {
    "account": {
      "surname": null
    }
  }
}

2. Die alte Version standardmäßig bis zu einem bestimmten Datum verwenden, an dem die neue Version zur Standardversion wird

Die alte Version weiter verwenden, bis zu einem bestimmten Datum, an dem die neue Version zur Standardversion wird. Während dieser Übergangszeit werden die Query-Entwickler aufgefordert, über den neuen extensions.warnings-Eintrag in der Query explizit eine Versionseinschränkung zur alten Version hinzuzufügen, bevor dieses Datum erreicht ist.

Das Ausführen der Query könnte antworten mit:

{
  "extensions": {
    "warnings": [
      {
        "message": "Field 'surname' has a new version: '2.0.0'. This version will become the default one on January 1st. We advise you to use this new version already and test that it works fine; if you find any problem, please report the issue in https://github.com/mycompany/myproject/issues. To do the switch, please add the 'versionConstraint' field argument to your query (using Composer's semver constraint rules; see https://getcomposer.org/doc/articles/versions.md#writing-version-constraints): surname(versionConstraint:\"^2.0\"). If you are unable to switch to the new version, please make sure to explicitly point to the current version '1.0.0' before January 1st: surname(versionConstraint:\"^1.0\"). In case of doubt, please contact us at name@company.com.",
    ]
  },
  "data": {
    "account": {
      "surname": "Owens"
    }
  }
}

3. Die neueste Version verwenden und Benutzer ermutigen, explizit anzugeben, welche Version verwendet werden soll

Die neueste Version des Feldes verwenden, wenn versionConstraint nicht gesetzt ist, und die Query-Entwickler ermutigen, explizit zu definieren, welche Version verwendet werden soll, indem die Liste aller verfügbaren Versionen für dieses Feld über einen neuen extensions.warnings-Eintrag angezeigt wird:

Das Ausführen der Query könnte antworten mit:

{
  "extensions": {
    "warnings": [
      {
        "message": "Field 'surname' has more than 1 version. Please add the 'versionConstraint' field argument to your query to indicate which version to use (using Composer's semver constraint rules; see https://getcomposer.org/doc/articles/versions.md#writing-version-constraints). To use the latest version, use: surname(versionConstraint:\"^2.0\"). Available versions: '2.0.0', '1.0.0'.",
    ]
  },
  "data": {
    "account": {
      "surname": "Owens"
    }
  }
}

Versionierung von Direktiven

Wir können dieselben Strategien verwenden, um Direktiven zu versionieren. Zum Beispiel, wenn die Query ohne Angabe der Versionseinschränkung ausgeführt wird:

query {
  post(by: { id: 1 }) {
    title @strTitleCase
  }
}

Es könnte eine Standardversion angenommen und eine Warnmeldung für den Entwickler erstellt werden, die Query zu überarbeiten:

Eine versionierte Direktive ohne Versionseinschränkungen abfragen