Mehrere queries gleichzeitig ausführen
Mehrere queries können kombiniert und als eine einzige Operation ausgeführt werden, wobei ihr Zustand und ihre Daten wiederverwendet werden.
Dies unterscheidet sich vom Query Batching, bei dem der GraphQL-Server ebenfalls mehrere queries in einer einzigen Anfrage ausführt, diese queries jedoch lediglich nacheinander und unabhängig voneinander ausgeführt werden.
Diese Funktion verbessert die Performance. Anstatt queries unabhängig voneinander in verschiedenen Anfragen auszuführen (sodass wir zunächst eine Operation gegen den GraphQL-Server ausführen, auf die Antwort warten und dann das Ergebnis verwenden, um eine weitere Operation durchzuführen), können wir sie zusammen ausführen und so die Latenz durch die mehrfachen Anfragen vermeiden.
Multiple Query Execution ermöglicht es uns außerdem, unsere GraphQL-queries besser zu organisieren, indem wir sie in logische Einheiten aufteilen, die voneinander abhängen und basierend auf dem Ergebnis einer vorherigen Operation bedingt ausgeführt werden.
So verwendest du die Multiple Query Execution
Angenommen, wir möchten alle Posts suchen, die den Namen des eingeloggten Benutzers erwähnen. Normalerweise würden wir dafür zwei queries benötigen:
Zuerst rufen wir den name des Benutzers ab:
query GetLoggedInUserName {
me {
name
}
}...und dann, nachdem wir die erste query ausgeführt haben, können wir den abgerufenen name des Benutzers als Variable $search übergeben, um die Suche in einer zweiten query durchzuführen:
query GetPostsContainingString($search: String!) {
posts(filter: { search: $search }) {
id
title
}
}Multiple Query Execution vereinfacht diesen Prozess und ermöglicht es uns, alle Daten abzurufen und die gesamte erforderliche Logik in einer einzigen Anfrage auszuführen:
query GetLoggedInUserName {
me {
name @export(as: "search")
}
}
query GetPostsContainingString @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $search }) {
id
title
}
}Multiple Query Execution wird durch den Einsatz dieser speziellen Direktiven erreicht:
@depends(Operationsdirektive): lässt eine Operation (obqueryodermutation) angeben, welche anderen Operationen zuvor ausgeführt werden müssen@export(Felddirektive): exportiert den Wert eines Feldes aus einer Operation, um ihn als Eingabe in ein Feld einer anderen Operation einzuspeisen@deferredExport(Felddirektive): ähnlich wie@export, aber für die Verwendung mit Multi-Field Directives.
Darüber hinaus stehen die Direktiven @include und @skip auch als Operationsdirektiven zur Verfügung (normalerweise sind sie nur Felddirektiven), und diese können verwendet werden, um eine Operation bedingt auszuführen, wenn sie eine bestimmte Bedingung erfüllt.
Der GraphQL-Server erstellt die Liste der zu ladenden und auszuführenden Operationen, ruft sie aus jedem @depends(on: ...) ab, und exportiert die Werte aus jedem Feld mit @export als dynamische Variable (mit dem unter dem Argument as definierten Namen), die als Eingabe in einer nachfolgenden Operation verwendet werden kann.
Durch die Kombination dieser Direktiven können wir jede komplexe Funktionalität in Zwischenschritte aufteilen, query- und mutation-Operationen abwechseln, ihre Abhängigkeiten in der erforderlichen Reihenfolge hinzufügen und sie alle in einer einzigen Anfrage ausführen, indem wir die äußerste Operation in ?operationName=... definieren (im obigen Beispiel wäre das ?operationName=GetPostsContainingString).
Operationen über @depends definieren
Wenn das GraphQL-Dokument mehrere Operationen enthält, geben wir dem Server über den URL-Parameter ?operationName=... an, welche ausgeführt werden soll; andernfalls wird die letzte Operation ausgeführt.
Ausgehend von dieser initialen Operation sammelt der Server alle auszuführenden Operationen, die durch Hinzufügen der Direktive depends(on: [...]) definiert werden, und führt sie in der entsprechenden Reihenfolge unter Einhaltung der Abhängigkeiten aus.
Das Direktiven-Argument operations empfängt ein Array von Operationsnamen ([String]), oder wir können auch einen einzelnen Operationsnamen (String) angeben.
In dieser query übergeben wir ?operationName=Four, und die ausgeführten Operationen (ob query oder mutation) werden ["One", "Two", "Three", "Four"] sein:
mutation One {
# Do something ...
}
mutation Two {
# Do something ...
}
query Three @depends(on: ["One", "Two"]) {
# Do something ...
}
query Four @depends(on: "Three") {
# Do something ...
}Daten zwischen queries über @export teilen
Die Direktive @export exportiert den Wert eines Feldes (oder einer Gruppe von Feldern) in eine dynamische Variable, die als Eingabe in einem Feld einer anderen query verwendet werden kann.
In dieser query exportieren wir beispielsweise den Namen des eingeloggten Benutzers und verwenden diesen Wert, um nach Posts zu suchen, die diesen String enthalten (beachte, dass die Variable $loggedInUserName nicht in der Operation FindPosts definiert werden muss, da sie dynamisch ist):
query GetLoggedInUserName {
me {
name @export(as: "loggedInUserName")
}
}
query FindPosts @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $loggedInUserName }) {
id
}
}Ausgaben dynamischer Variablen
@export kann 6 verschiedene Ausgaben erzeugen, basierend auf einer Kombination aus:
- Dem Wert des Arguments
type(entwederSINGLE,LISToderDICTIONARY) - Ob die Direktive auf ein einzelnes Feld oder auf mehrere Felder angewendet wird (über das Modul Multi-Field Directives)
Die 6 möglichen Ausgaben sind:
- Typ
SINGLE:- Einzelnes Feld
- Multi-Feld
- Typ
LIST:- Einzelnes Feld
- Multi-Feld
- Typ
DICTIONARY:- Einzelnes Feld
- Multi-Feld
Typ SINGLE / Einzelnes Feld
Die Ausgabe ist ein einzelner Wert, wenn der Parameter type: SINGLE übergeben wird (der als Standardwert festgelegt ist).
In dieser query:
query {
post(by: { id: 1 }) {
title @export(as: "postTitle", type: SINGLE)
}
}...hat die dynamische Variable $postTitle den Wert:
"Hello world!"Beachte, dass wenn SINGLE auf ein Array von Entitäten angewendet wird, der Wert der letzten Entität exportiert wird.
In dieser query:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitle", type: SINGLE)
}
}...hat die dynamische Variable $postTitle den Wert des Posts mit ID 5:
"Everything good?"Typ SINGLE / Multi-Feld
Wenn @export auf mehrere Felder angewendet wird (durch Hinzufügen des Parameters affectAdditionalFieldsUnderPos, der vom Modul Multi-Field Directives bereitgestellt wird), ist der in der dynamischen Variable gesetzte Wert ein Dictionary aus { key: Feld-Alias, value: Feldwert } (vom Typ JSONObject).
Diese query:
query {
post(by: { id: 1 }) {
title
content
@export(
as: "postData",
type: SINGLE,
affectAdditionalFieldsUnderPos: [1]
)
}
}...exportiert die dynamische Variable $postData mit dem Wert:
{
"title": "Hello world!",
"content": "Lorem ipsum."
}Typ LIST / Einzelnes Feld
Die dynamische Variable enthält ein Array mit dem Feldwert aller abgefragten Entitäten (aus dem umschließenden Feld), wenn der Parameter type: LIST übergeben wird.
Bei der Ausführung dieser query (in der die abgefragten Entitäten Posts mit ID 1 und 5 sind):
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitles", type: LIST)
}
}...hat die dynamische Variable $postTitles den Wert:
[
"Hello world!",
"Everything good?"
]Typ LIST / Multi-Feld
Wir erhalten ein Array von Dictionaries (vom Typ JSONObject), die jeweils die Werte der Felder enthalten, auf die die Direktive angewendet wird.
Diese query:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsData",
type: LIST,
affectAdditionalFieldsUnderPos: [1]
)
}
}...exportiert die dynamische Variable $postsData mit dem Wert:
[
{
"title": "Hello world!",
"content": "Lorem ipsum."
},
{
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
]Typ DICTIONARY / Einzelnes Feld
Die dynamische Variable enthält ein Dictionary (vom Typ JSONObject) mit der ID der abgefragten Entität als Schlüssel und den Feldwerten als Wert, wenn der Parameter type: DICTIONARY übergeben wird.
Diese query:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postIDTitles", type: DICTIONARY)
}
}...exportiert die dynamische Variable $postIDTitles mit dem Wert:
{
"1": "Hello world!",
"5": "Everything good?"
}Typ DICTIONARY / Multi-Feld
In dieser Kombination exportieren wir ein Dictionary von Dictionaries: { key: Entitäts-ID, value: { key: Feld-Alias, value: Feldwert } } (mit einem Typ JSONObject, der Einträge vom Typ JSONObject enthält).
Diese query:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsIDProperties",
type: DICTIONARY,
affectAdditionalFieldsUnderPos: [1]
)
}
}...exportiert die dynamische Variable $postsIDProperties mit dem Wert:
{
"1": {
"title": "Hello world!",
"content": "Lorem ipsum."
},
"5": {
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
}Bedingte Ausführung von Operationen
Wenn Multiple Query Execution aktiviert ist, stehen die Direktiven @include und @skip auch als Operationsdirektiven zur Verfügung, und diese können verwendet werden, um eine Operation bedingt auszuführen, wenn sie eine bestimmte Bedingung erfüllt.
In dieser query beispielsweise exportiert die Operation CheckIfPostExists eine dynamische Variable $postExists und nur wenn deren Wert true ist, wird die Mutation ExecuteOnlyIfPostExists ausgeführt:
query CheckIfPostExists($id: ID!) {
# Initialize the dynamic variable to `false`
postExists: _echo(value: false) @export(as: "postExists")
post(by: { id: $id }) {
# Found the Post => Set dynamic variable to `true`
postExists: _echo(value: true) @export(as: "postExists")
}
}
mutation ExecuteOnlyIfPostExists
@depends(on: "CheckIfPostExists")
@include(if: $postExists)
{
# Do something...
}Werte beim Iterieren über ein Array oder JSON-Objekt exportieren
@export respektiert die Kardinalität einer übergeordneten Meta-Direktive.
Insbesondere wenn @export unterhalb einer Meta-Direktive verschachtelt ist, die über Array-Elemente oder JSON-Objekt-Eigenschaften iteriert (d.h. @underEachArrayItem und @underEachJSONObjectProperty), wird der exportierte Wert ein Array sein.
Diese query:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underEachArrayItem
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...erzeugt $contentAttributes mit dem Wert:
[
"List Block",
"Columns Block",
"Columns inside Columns (nested inner blocks)",
"Life is so rich",
"Life is so dynamic"
]Im Gegensatz dazu exportiert dieselbe query, die auf ein bestimmtes Element im Array zugreift, anstatt über alle zu iterieren (durch Ersetzen von @underEachArrayItem durch @underArrayItem(index: 0)), einen einzelnen Wert.
Diese query:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underArrayItem(index: 0)
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...erzeugt $contentAttributes mit dem Wert:
"List Block"Reihenfolge der Direktiv-Ausführung
Wenn es andere Direktiven vor @export gibt, spiegelt der exportierte Wert die Änderungen durch diese vorherigen Direktiven wider.
In dieser query beispielsweise ist das Ergebnis unterschiedlich, je nachdem ob @export vor oder nach @strUpperCase ausgeführt wird:
query One {
id
# First export "root", only then will be converted to "ROOT"
@export(as: "id")
@strUpperCase
again: id
# First convert to "ROOT" and then export this value
@strUpperCase
@export(as: "again")
}
query Two @depends(on: "One") {
mirrorID: _echo(value: $id)
mirrorAgain: _echo(value: $again)
}Ergibt:
{
"data": {
"id": "ROOT",
"again": "ROOT",
"mirrorID": "root",
"mirrorAgain": "ROOT"
}
}Multi-Field Directives
Wenn die Funktion Multi-Field Directives aktiviert ist und du den Wert mehrerer Felder in ein Dictionary exportierst, verwende @deferredExport anstelle von @export, um sicherzustellen, dass alle Direktiven jedes beteiligten Feldes ausgeführt wurden, bevor der Feldwert exportiert wird.
In dieser query beispielsweise hat das erste Feld die Direktive @strUpperCase und das zweite @titleCase angewendet. Bei der Ausführung von @deferredExport werden diese Direktiven auf den exportierten Wert angewendet:
query One {
id @strUpperCase # Will be exported as "ROOT"
again: id @titleCase # Will be exported as "Root"
@deferredExport(as: "props", affectAdditionalFieldsUnderPos: [1])
}
query Two @depends(on: "One") {
mirrorProps: _echo(value: $props)
}Ergibt:
{
"data": {
"id": "ROOT",
"again": "Root",
"mirrorProps": {
"id": "ROOT",
"again": "Root"
}
}
}GraphQL-Spec
Diese Funktionalität ist derzeit nicht Teil der GraphQL-Spec, wurde aber angefragt: