Mehrfache Query-Ausführung
Kombiniere mehrere queries in einer einzigen Query und teile dabei den Zustand zwischen ihnen, während sie in der gewünschten Reihenfolge ausgeführt werden.
Beschreibung
Die mehrfache Query-Ausführung kombiniert mehrere queries in einer einzigen Query und stellt sicher, dass sie in der angeforderten Reihenfolge ausgeführt werden. Operationen können über dynamische Variablen Zustand miteinander austauschen – diese werden nur einmal berechnet, können aber mehrfach innerhalb des Dokuments gelesen werden.
query SomeQuery {
id @export(as: "rootID")
}
query AnotherQuery
@depends(on: "SomeQuery")
{
_echo(value: $rootID )
}Diese Funktion bietet mehrere Vorteile:
- Sie verbessert die Performance: Statt eine Query gegen den GraphQL-Server auszuführen, auf deren Antwort zu warten und das Ergebnis dann für eine weitere Query zu verwenden, können wir die queries zu einer einzigen zusammenfassen und in einer einzigen Anfrage ausführen – so wird die Latenz durch mehrere HTTP-Verbindungen vermieden.
- Sie ermöglicht es uns, unsere GraphQL-queries in atomare Operationen (oder logische Einheiten) zu gliedern, die voneinander abhängen und die basierend auf dem Ergebnis einer vorherigen Operation bedingt ausgeführt werden können.
Die mehrfache Query-Ausführung 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.
Aktivierte Direktiven
Wenn die mehrfache Query-Ausführung aktiviert ist, werden folgende Direktiven im GraphQL-Schema verfügbar:
@depends(Operationsdirektive): Damit eine Operation (obqueryodermutation) angeben kann, welche anderen Operationen zuvor ausgeführt werden müssen@export(Felddirektive): Um den Wert eines Felds aus einer Query als dynamische Variable zu exportieren, die als Eingabe für ein Feld oder eine Direktive in einer anderen Query verwendet wird@exportFrom(Felddirektive): Ähnlich wie@export, aber zum Exportieren des Werts einer bereichsbezogenen dynamischen Variablen (übergeben via@passOnwards(as: "...")oder@applyField(passOnwardsAs: "..."))@deferredExport(Felddirektive): Ähnlich wie@export, aber zur Verwendung mit Multi-Field Directives
Zusätzlich werden die Direktiven @include und @skip auch als Operationsdirektiven verfügbar gemacht (normalerweise sind sie nur Felddirektiven), und diese können verwendet werden, um eine Operation bedingt auszuführen, wenn sie eine bestimmte Bedingung erfüllt.
@depends
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 Berücksichtigung der Abhängigkeiten aus.
Das Direktiven-Argument operations nimmt ein Array von Operationsnamen ([String]) entgegen, oder es kann auch ein einzelner Operationsname (String) angegeben werden.
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 ...
}@export
Die Direktive @export exportiert den Wert eines Felds (oder einer Gruppe von Feldern) in eine dynamische Variable, die als Eingabe für ein Feld oder eine Query in einer anderen Query verwendet werden kann.
In dieser Query exportieren wir zum Beispiel den Namen des eingeloggten Benutzers und verwenden diesen Wert, um nach Beiträgen zu suchen, die diesen String enthalten (beachte, dass die Variable $loggedInUserName, da sie dynamisch ist, nicht in der Operation FindPosts definiert werden muss):
query GetLoggedInUserName {
me {
name @export(as: "loggedInUserName")
}
}
query FindPosts @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $loggedInUserName }) {
id
}
}@exportFrom
Es ist ähnlich wie @export, exportiert aber anstelle des Feldwerts den Wert einer bereichsbezogenen dynamischen Variablen, die via @passOnwards(as: "...") oder @applyField(passOnwardsAs: "...") übergeben wird.
In dieser Query verwenden wir zum Beispiel @applyField, um die Elemente des Arrays zu modifizieren und diesen neuen Wert der bereichsbezogenen dynamischen Variablen $replaced zuzuweisen. Anschließend nutzen wir @exportFrom, um diesen Wert über die dynamische Variable $replacedList global zugänglich zu machen, sodass er von einer nachfolgenden Query abgerufen werden kann.
query One {
originalList: _echo(value: ["Hello everyone", "How are you?"])
@underEachArrayItem(
passValueOnwardsAs: "value"
affectDirectivesUnderPos: [1, 2]
)
@applyField(
name: "_strReplace"
arguments: {
search: " "
replaceWith: "-"
in: $value
},
passOnwardsAs: "replaced"
)
@exportFrom(
scopedDynamicVariable: $replaced,
as: "replacedList"
)
}
query Two @depends(on: "One") {
transformedList: _echo(value: $replacedList)
}Dies ergibt:
{
"data": {
"originalList": [
"Hello everyone",
"How are you?"
],
"transformedList": [
"Hello-everyone",
"How-are-you?"
]
}
}@deferredExport
Wenn das Feature Multi-Field Directives aktiviert ist und wir den Wert mehrerer Felder in ein Dictionary exportieren, verwende @deferredExport anstelle von @export, um sicherzustellen, dass alle Direktiven der beteiligten Felder ausgeführt wurden, bevor der Feldwert exportiert wird.
In dieser Query ist dem ersten Feld die Direktive @strUpperCase zugewiesen und dem zweiten @strTitleCase. 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 @strTitleCase # 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"
}
}
}@skip und @include (in Operationen)
Wenn die mehrfache Query-Ausführung aktiviert ist, sind die Direktiven @include und @skip auch als Operationsdirektiven verfügbar, und diese können verwendet werden, um eine Operation bedingt auszuführen, wenn sie eine bestimmte Bedingung erfüllt.
In dieser Query exportiert die Operation CheckIfPostExists beispielsweise eine dynamische Variable $postExists, und nur wenn ihr 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...
}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
- Mehrere Felder
- Typ
LIST:- Einzelnes Feld
- Mehrere Felder
- Typ
DICTIONARY:- Einzelnes Feld
- Mehrere Felder
Typ SINGLE / Einzelnes Feld
Die Ausgabe ist ein einzelner Wert, wenn der Parameter type: SINGLE übergeben wird (dieser ist als Standardwert festgelegt).
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 Beitrags mit ID 5:
"Everything good?"Typ SINGLE / Mehrere Felder
Wenn @export auf mehrere Felder angewendet wird (durch Hinzufügen des Parameters affectAdditionalFieldsUnderPos, der vom Modul Multi-Field Directives bereitgestellt wird), ist der auf der dynamischen Variablen gesetzte Wert ein Dictionary von { key: field alias, value: field value } (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 einschließenden Feld), indem der Parameter type: LIST übergeben wird.
Beim Ausführen dieser Query (bei der die abgefragten Entitäten Beiträge 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 / Mehrere Felder
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, indem 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 / Mehrere Felder
In dieser Kombination exportieren wir ein Dictionary von Dictionaries: { key: entity ID, value: { key: field alias, value: field value } } (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."
}
}Werte beim Iterieren eines Arrays oder JSON-Objekts exportieren
@export respektiert die Kardinalität jeder umgebenden 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), ist der exportierte Wert ein Array.
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 (indem @underEachArrayItem durch @underArrayItem(index: 0) ersetzt wird), 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 Direktiven-Ausführung
Wenn vor @export andere Direktiven vorhanden sind, spiegelt der exportierte Wert die Änderungen durch diese vorherigen Direktiven wider.
In dieser Query ist das Ergebnis zum Beispiel je nachdem, ob @export vor oder nach @strUpperCase stattfindet, unterschiedlich:
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"
}
}Ausführung in Persisted Queries
Wenn eine GraphQL-Query mehrere Operationen in einer Persisted Query enthält, können wir den entsprechenden Endpunkt aufrufen und dabei den URL-Parameter ?operationName=... mit dem Namen der auszuführenden Operation übergeben; andernfalls wird die letzte Operation ausgeführt.
Um beispielsweise die Operation GetPostsContainingString in einer Persisted Query mit dem Endpunkt /graphql-query/posts-with-user-name/ auszuführen, müssen wir aufrufen:
https://mysite.com/graphql-query/posts-with-user-name/?operationName=GetPostsContainingStringBeispiele
Inhalte von einem externen API-Endpunkt importieren:
query FetchDataFromExternalEndpoint
{
_sendJSONObjectItemHTTPRequest(input: { url: "https://site.com/wp-json/wp/posts/1" } )
@export(as: "externalData")
@remove
}
query ManipulateDataIntoInput @depends(on: "FetchDataFromExternalEndpoint")
{
title: _objectProperty(
object: $externalData,
by: {
path: "title.rendered"
}
) @export(as: "postTitle")
excerpt: _objectProperty(
object: $externalData,
by: {
key: "excerpt"
}
) @export(as: "postExcerpt")
}
mutation CreatePost @depends(on: "ManipulateDataIntoInput")
{
createPost(input: {
title: $postTitle
excerpt: $postExcerpt
}) {
id
}
}Die Daten eines Beitrags abrufen, transformieren und erneut speichern:
query GetPostData(
$postId: ID!
) {
post(by: {id: $postId}) {
id
title @export(as: "postTitle")
rawContent @export(as: "postContent")
}
}
query AdaptPostData(
$replaceFrom: String!,
$replaceTo: String!
)
@depends(on: "GetPostData")
{
adaptedPostTitle: _strReplace(
search: $replaceFrom
replaceWith: $replaceTo
in: $postTitle
)
@export(as: "adaptedPostTitle")
adaptedPostContent: _strReplace(
search: $replaceFrom
replaceWith: $replaceTo
in: $postContent
)
@export(as: "adaptedPostContent")
}
mutation StoreAdaptedPostData(
$postId: ID!
)
@depends(on: "AdaptPostData")
{
updatePost(input: {
id: $postId,
title: $adaptedPostTitle,
contentAs: { html: $adaptedPostContent },
}) {
status
errors {
__typename
...on ErrorPayload {
message
}
}
post {
id
title
rawContent
}
}
}Einen Beitrag aktualisieren, wenn er existiert, oder andernfalls eine Fehlermeldung anzeigen:
query GetPost($id: ID!) {
post(by:{id: $id}) {
id
title
}
_notNull(value: $__post) @export(as: "postExists")
}
query FailIfPostNotExists($id: ID!)
@skip(if: $postExists)
@depends(on: "GetPost")
{
errorMessage: _sprintf(
string: "There is no post with ID '%s'",
values: [$id]
) @remove
_fail(
message: $__errorMessage
data: {
id: $id
}
) @remove
}
mutation UpdatePost($id: ID!, $postTitle: String)
@include(if: $postExists)
@depends(on: "GetPost")
{
updatePost(input: {
id: $id,
title: $postTitle,
}) {
status
errors {
__typename
...on ErrorPayload {
message
}
}
post {
id
title
rawContent
}
}
}
query MaybeUpdatePost
@depends(on: [
"FailIfPostNotExists",
"UpdatePost"
])
{
id @remove
}Den Benutzer vor der Ausführung einer Mutation einloggen und danach sofort ausloggen:
mutation LogUserIn(
$username: String!
$password: String!
) {
loginUser(by: {
credentials: {
usernameOrEmail: $username,
password: $password
}
}) @remove {
status
user {
id
username
}
}
}
mutation AddComment(
$customPostId: ID!
$commentContent: HTML!
)
@depends(on: "LogUserIn")
{
addCommentToCustomPost(input: {
customPostID: $customPostId,
commentAs: { html: $commentContent }
}) {
status
errors {
__typename
...on ErrorPayload {
message
}
}
comment {
id
parent {
id
}
content
date
author {
name
email
}
}
}
}
mutation LogUserOut
@depends(on: "AddComment")
{
logoutUser @remove {
status
userID
}
}
query ExecuteAllAddCommentOperations
@depends(on: "LogUserOut")
{
id @remove
}Den Benutzer vor der Ausführung einer Mutation bedingt einloggen, wenn Zugangsdaten angegeben wurden:
query ExportUserLogin(
$username: String
) {
_notNull(value: $username)
@export(as: "hasUsername")
@remove
}
mutation MaybeLogUserIn(
$username: String
$password: String
)
@depends(on: "ExportUserLogin")
@include(if: $hasUsername)
{
loginUser(by: {
credentials: {
usernameOrEmail: $username,
password: $password
}
}) @remove {
status
user {
id
username
}
}
}
mutation AddComment(
$customPostId: ID!
$commentContent: HTML!
)
@depends(on: "MaybeLogUserIn")
{
addCommentToCustomPost(input: {
customPostID: $customPostId,
commentAs: { html: $commentContent }
}) {
status
errors {
__typename
...on ErrorPayload {
message
}
}
comment {
id
parent {
id
}
content
date
author {
name
email
}
}
}
}
mutation MaybeLogUserOut
@depends(on: "AddComment")
@include(if: $hasUsername)
{
logoutUser @remove {
status
userID
}
}
query ExecuteAllAddCommentOperations
@depends(on: "MaybeLogUserOut")
{
id @remove
}GraphQL-Spezifikation
Diese Funktionalität ist derzeit nicht Teil der GraphQL-Spezifikation, wurde aber angefragt: