Arbeiten mit integrierten GraphQL Direktiven

Avatar of Jamie Barton
Jamie Barton am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Direktiven sind eines der besten – und am wenigsten erwähnten – Features von GraphQL.

Lassen Sie uns die Arbeit mit den integrierten Schema- und Operationsdirektiven von GraphQL untersuchen, die alle GraphQL-spezifikationskonformen APIs implementieren müssen. Sie sind äußerst nützlich, wenn Sie mit einem dynamischen Frontend arbeiten, da Sie die Kontrolle haben, die Antwortnutzlast zu reduzieren, je nachdem, womit der Benutzer interagiert.

Ein Überblick über Direktiven

Stellen Sie sich eine Anwendung vor, bei der Sie die angezeigten Spalten in einer Tabelle anpassen können. Wenn Sie zwei oder drei Spalten ausblenden, gibt es wirklich keinen Grund, die Daten für diese Zellen abzurufen. Mit GraphQL-Direktiven können wir jedoch wählen, ob wir diese Felder einschließen oder überspringen möchten.

Die GraphQL-Spezifikation definiert, was Direktiven sind und wo sie verwendet werden können. Insbesondere können Direktiven von Consumer-Operationen (wie einer Abfrage) und vom zugrunde liegenden Schema selbst verwendet werden. Oder, einfach ausgedrückt, Direktiven basieren entweder auf dem Schema oder der Operation. Schema-Direktiven werden verwendet, wenn das Schema generiert wird, und Operationsdirektiven werden ausgeführt, wenn eine Abfrage ausgeführt wird.

Kurz gesagt, Direktiven können für Metadaten, Laufzeittipps, Laufzeit-Parsing (z. B. Rückgabe von Daten in einem bestimmten Format) und erweiterte Beschreibungen (z. B. veraltet) verwendet werden.

Vier Arten von Direktiven

GraphQL bietet vier Hauptdirektiven, wie im Arbeitsentwurf der Spezifikation definiert, wobei eine davon noch nicht als Arbeitsentwurf veröffentlicht ist.

  • @include
  • @skip
  • @deprecated
  • @specifiedBy (Arbeitsentwurf)

Wenn Sie GraphQL genau verfolgen, werden Sie auch feststellen, dass zwei zusätzliche Direktiven in die JavaScript-Implementierung übernommen wurden, die Sie heute ausprobieren können – @stream und @defer. Diese sind noch kein Teil der offiziellen Spezifikation, während die Community sie in realen Anwendungen testet.

@include

Die Direktive @include, ihrem Namen getreu, ermöglicht es uns, Felder bedingt einzuschließen, indem wir ein if-Argument übergeben. Da es sich um eine Bedingung handelt, ist es sinnvoll, eine Variable in der Abfrage zu verwenden, um auf Wahrheit zu prüfen.

Wenn beispielsweise die Variable in den folgenden Beispielen wahr ist, wird das Feld name in die Abfrageantwort aufgenommen.

query getUsers($showName: Boolean) {
  users {
    id
    name @include(if: $showName)
  }
}

Umgekehrt können wir das Feld nicht einschließen, indem wir die Variable $showName zusammen mit der Abfrage auf false setzen. Wir können auch einen Standardwert für die Variable $showName festlegen, sodass sie nicht mit jeder Anfrage übergeben werden muss.

query getUsers($showName: Boolean = true) {
  users {
    id
    name @include(if: $showName)
  }
}

@skip

Wir können dasselbe ausdrücken, was wir gerade getan haben, aber mit der Direktive @skip. Wenn der Wert wahr ist, wird das Feld, wie Sie vielleicht erwarten, übersprungen.

query getUsers($hideName: Boolean) {
  users {
    id
    name @skip(if: $hideName)
  }
}

Während dies für einzelne Felder gut funktioniert, gibt es Zeiten, in denen wir mehr als ein Feld einschließen oder überspringen möchten. Wir könnten die Verwendung von @include und @skip auf mehreren Zeilen wie folgt duplizieren:

query getUsers($includeFields: Boolean) {
  users {
    id
    name @include(if: $includeFields)
    email @include(if: $includeFields)
    role @include(if: $includeFields)
  }
}

Sowohl die Direktiven @skip als auch @include können auf Feldern, Fragment-Spreads und Inline-Fragmenten verwendet werden, was bedeutet, dass wir etwas anderes tun können, wie dieses mit Inline-Fragmenten:

query getUsers($excludeFields: Boolean) {
  users {
    id
    ... on User @skip(if: $excludeFields) {
      name
      email
      role
    }
  }
}

Wenn ein Fragment bereits definiert ist, können wir @skip und @include auch verwenden, wenn wir ein Fragment in die Abfrage aufnehmen:

fragment User on User {
  name
  email
  role
}

query getUsers($excludeFields: Boolean) {
  users {
    id
    ...User @skip(if: $excludeFields)
  }
}

@deprecated

Die Direktive @deprecated erscheint nur im Schema und ist nichts, was ein Benutzer wie oben gezeigt als Teil einer Abfrage bereitstellen würde. Stattdessen wird die Direktive @deprecated vom Entwickler, der das GraphQL-API-Schema wartet, angegeben.

Als Benutzer erhalten wir, wenn wir versuchen, ein Feld abzurufen, das im Schema als veraltet markiert wurde, eine Warnung wie diese, die kontextbezogene Hilfe bietet.

A zoomed in example of a syntax-highlighted GraphQL query for getUsers, which contains a users object with id and title properties. The title property is underlined in yellow with a contextual tooltip open below it showing a warning in yellow and white that suggests using the name field instead.
In diesem Beispiel wurde das Feld „title“ als veraltet markiert und die Direktive gibt einen hilfreichen Hinweis, es zu ersetzen.

Um ein Feld als veraltet zu markieren, müssen wir die Direktive @deprecated innerhalb der Schema Definition Language (SDL) verwenden und einen reason innerhalb der Argumente übergeben, wie folgt:

type User {
  id: ID!
  title: String @deprecated(reason: "Use name instead")
  name: String!
  email: String!
  role: Role
}

Wenn wir dies mit der Direktive @include koppeln, könnten wir das veraltete Feld bedingt abrufen, basierend auf einer Abfragevariable.

fragment User on User {
  title @include(if: $includeDeprecatedFields)
  name
  email
  role
}

query getUsers($includeDeprecatedFields: Boolean! = false) {
  users {
    id
    ...User
  }
}

@specifiedBy

@specifiedBy ist die vierte der Direktiven und derzeit Teil des Arbeitsentwurfs. Sie soll für benutzerdefinierte Skalarimplementierungen verwendet werden und ein url-Argument annehmen, das auf eine Spezifikation für den Skalar verweisen sollte.

Wenn wir beispielsweise einen benutzerdefinierten Skalar für E-Mail-Adressen hinzufügen, möchten wir die URL zur Spezifikation für den regulären Ausdruck übergeben, den wir als Teil davon verwenden. Unter Verwendung des letzten Beispiels und des in RFC #822 definierten Vorschlags würde ein Skalar für EmailAddress wie folgt im Schema definiert werden:

scalar EmailAddress @specifiedBy(url: "https://www.w3.org/Protocols/rfc822/")

Es wird empfohlen, dass benutzerdefinierte Direktiven einen präfixierten Namen haben, um Kollisionen mit anderen hinzugefügten Direktiven zu vermeiden. Wenn Sie ein Beispiel für eine benutzerdefinierte Direktive und deren Erstellung suchen, werfen Sie einen Blick auf GraphQL Public Schema. Es ist eine benutzerdefinierte GraphQL-Direktive, die sowohl Code- als auch Schema-First-Unterstützung für die Annotation bietet, welche Teile einer API öffentlich genutzt werden können.

Zusammenfassung

Das war ein allgemeiner Überblick über GraphQL-Direktiven. Noch einmal, ich glaube, Direktiven sind eine Art unsung hero, die von anderen GraphQL-Funktionen überschattet wird. Wir haben bereits viel Kontrolle mit GraphQL-Schemas, und Direktiven geben uns noch feinere Kontrolle, um genau das zu bekommen, was wir von Abfragen wollen. Das ist die Art von Effizienz, die die GraphQL-API so schnell und letztendlich benutzerfreundlicher macht.

Und wenn Sie eine GraphQL-API erstellen, stellen Sie sicher, dass Sie diese Direktiven in die Introspektionsabfrage aufnehmen. Sie dort zu haben, gibt Entwicklern nicht nur den Vorteil zusätzlicher Kontrolle, sondern auch eine bessere allgemeine Entwicklererfahrung. Denken Sie nur daran, wie hilfreich es wäre, Felder ordnungsgemäß zu @deprecate, damit Entwickler wissen, was sie tun müssen, ohne jemals den Code verlassen zu müssen? Das ist an sich schon mächtig.


Header-Grafik mit freundlicher Genehmigung von Isabel Gonçalves auf Unsplash