Wykonywanie wielu queries
Połącz wiele queries w jedną query, współdzieląc stan między nimi i wykonując je w żądanej kolejności.
Opis
Wykonywanie wielu queries łączy kilka queries w jedną query, zapewniając, że są one wykonywane w tej samej żądanej kolejności. Operacje mogą komunikować stan między sobą za pomocą zmiennych dynamicznych, które są obliczane tylko raz, ale mogą być odczytywane wielokrotnie w całym dokumencie.
query SomeQuery {
id @export(as: "rootID")
}
query AnotherQuery
@depends(on: "SomeQuery")
{
_echo(value: $rootID )
}Ta funkcja oferuje kilka korzyści:
- Poprawia wydajność: zamiast wykonywać query na serwerze GraphQL, czekać na odpowiedź, a następnie używać tego wyniku do wykonania kolejnej query, możemy połączyć queries w jedną i wykonać je w jednym żądaniu, unikając tym samym opóźnienia wynikającego z wielu połączeń HTTP.
- Pozwala nam zarządzać naszymi queries GraphQL jako atomowymi operacjami (lub jednostkami logicznymi), które zależą od siebie i mogą być wykonywane warunkowo na podstawie wyniku poprzedniej operacji.
Wykonywanie wielu queries różni się od query batching, w którym serwer GraphQL również wykonuje wiele queries w jednym żądaniu, ale te queries są po prostu wykonywane jedna po drugiej, niezależnie od siebie.
Włączone dyrektywy
Gdy wykonywanie wielu queries jest włączone, następujące dyrektywy są udostępniane w schemacie GraphQL:
@depends(dyrektywa operacji): aby operacja (czy toqueryczymutation) wskazywała, jakie inne operacje muszą być wykonane wcześniej@export(dyrektywa pola): aby wyeksportować wartość pola z jednej query jako zmienną dynamiczną, do użycia jako wejście w jakimś polu lub dyrektywie w innej query@exportFrom(dyrektywa pola): podobna do@export, ale służy do eksportowania wartości zmiennej dynamicznej o ograniczonym zasięgu (przekazywanej przez@passOnwards(as: "...")lub@applyField(passOnwardsAs: "..."))@deferredExport(dyrektywa pola): podobna do@export, ale przeznaczona do użycia z Multi-Field Directives
Ponadto dyrektywy @include i @skip są również udostępniane jako dyrektywy operacji (normalnie są tylko dyrektywami pól), i mogą być używane do warunkowego wykonania operacji, jeśli spełnia pewien warunek.
@depends
Gdy dokument GraphQL zawiera wiele operacji, wskazujemy serwerowi, którą z nich wykonać za pomocą parametru URL ?operationName=...; w przeciwnym razie zostanie wykonana ostatnia operacja.
Zaczynając od tej początkowej operacji, serwer zbierze wszystkie operacje do wykonania, które są definiowane przez dodanie dyrektywy depends(on: [...]), i wykona je we właściwej kolejności, respektując zależności.
Argument operations dyrektywy przyjmuje tablicę nazw operacji ([String]) lub możemy też podać pojedynczą nazwę operacji (String).
W tej query przekazujemy ?operationName=Four, a wykonane operacje (czy to query czy mutation) będą ["One", "Two", "Three", "Four"]:
mutation One {
# Do something ...
}
mutation Two {
# Do something ...
}
query Three @depends(on: ["One", "Two"]) {
# Do something ...
}
query Four @depends(on: "Three") {
# Do something ...
}@export
Dyrektywa @export eksportuje wartość pola (lub zestawu pól) do zmiennej dynamicznej, która ma być używana jako wejście w jakimś polu lub query z innej query.
Na przykład w tej query eksportujemy nazwę zalogowanego użytkownika i używamy tej wartości do wyszukiwania postów zawierających ten ciąg znaków (proszę zwrócić uwagę, że zmienna $loggedInUserName, ponieważ jest dynamiczna, nie musi być zdefiniowana w operacji FindPosts):
query GetLoggedInUserName {
me {
name @export(as: "loggedInUserName")
}
}
query FindPosts @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $loggedInUserName }) {
id
}
}@exportFrom
Jest podobna do @export, ale zamiast eksportować wartość pola, eksportuje wartość zmiennej dynamicznej o ograniczonym zasięgu, przekazywanej przez @passOnwards(as: "...") lub @applyField(passOnwardsAs: "...").
Na przykład w tej query używamy @applyField do modyfikowania elementów tablicy i przypisania tej nowej wartości do zmiennej dynamicznej o ograniczonym zasięgu $replaced. Następnie używamy @exportFrom, aby udostępnić tę wartość globalnie przez zmienną dynamiczną $replacedList, tak aby mogła być pobrana z kolejnej query.
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)
}Spowoduje to wyprodukowanie:
{
"data": {
"originalList": [
"Hello everyone",
"How are you?"
],
"transformedList": [
"Hello-everyone",
"How-are-you?"
]
}
}@deferredExport
Gdy funkcja Multi-Field Directives jest włączona i eksportujemy wartość wielu pól do słownika, użyj @deferredExport zamiast @export, aby zagwarantować, że wszystkie dyrektywy z każdego zaangażowanego pola zostały wykonane przed wyeksportowaniem wartości pola.
Na przykład w tej query pierwsze pole ma zastosowaną dyrektywę @strUpperCase, a drugie @strTitleCase. Podczas wykonywania @deferredExport wyeksportowana wartość będzie miała zastosowane te dyrektywy:
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)
}Produkując:
{
"data": {
"id": "ROOT",
"again": "Root",
"mirrorProps": {
"id": "ROOT",
"again": "Root"
}
}
}@skip i @include (w operacjach)
Gdy wykonywanie wielu queries jest włączone, dyrektywy @include i @skip są również dostępne jako dyrektywy operacji i mogą być używane do warunkowego wykonania operacji, jeśli spełnia pewien warunek.
Na przykład w tej query operacja CheckIfPostExists eksportuje zmienną dynamiczną $postExists i tylko wtedy, gdy jej wartość wynosi true, mutacja ExecuteOnlyIfPostExists zostanie wykonana:
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...
}Wyjścia zmiennych dynamicznych
@export może produkować 6 różnych wyjść, na podstawie kombinacji:
- Wartości argumentu
type(SINGLE,LISTlubDICTIONARY) - Czy dyrektywa jest stosowana do jednego pola, czy do wielu pól (przez moduł Multi-Field Directives)
6 możliwych wyjść to:
- Typ
SINGLE:- Pojedyncze pole
- Wiele pól
- Typ
LIST:- Pojedyncze pole
- Wiele pól
- Typ
DICTIONARY:- Pojedyncze pole
- Wiele pól
Typ SINGLE / Pojedyncze pole
Wyjście jest pojedynczą wartością po przekazaniu parametru type: SINGLE (ustawionego jako wartość domyślna).
W tej query:
query {
post(by: { id: 1 }) {
title @export(as: "postTitle", type: SINGLE)
}
}...zmienna dynamiczna $postTitle będzie mieć wartość:
"Hello world!"Proszę zwrócić uwagę, że jeśli SINGLE jest zastosowane do tablicy encji, eksportowana jest wartość ostatniej encji.
W tej query:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitle", type: SINGLE)
}
}...zmienna dynamiczna $postTitle będzie mieć wartość dla posta z ID 5:
"Everything good?"Typ SINGLE / Wiele pól
Jeśli @export jest stosowane do kilku pól (przez dodanie parametru affectAdditionalFieldsUnderPos dostarczonego przez moduł Multi-Field Directives), wartość ustawiona w zmiennej dynamicznej jest słownikiem { key: field alias, value: field value } (typu JSONObject).
Ta query:
query {
post(by: { id: 1 }) {
title
content
@export(
as: "postData",
type: SINGLE,
affectAdditionalFieldsUnderPos: [1]
)
}
}...eksportuje zmienną dynamiczną $postData z wartością:
{
"title": "Hello world!",
"content": "Lorem ipsum."
}Typ LIST / Pojedyncze pole
Zmienna dynamiczna będzie zawierać tablicę z wartością pola ze wszystkich zapytanych encji (z pola obejmującego), po przekazaniu parametru type: LIST.
Przy uruchamianiu tej query (w której zapytanymi encjami są posty z ID 1 i 5):
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitles", type: LIST)
}
}...zmienna dynamiczna $postTitles będzie mieć wartość:
[
"Hello world!",
"Everything good?"
]Typ LIST / Wiele pól
Otrzymujemy tablicę słowników (typu JSONObject), z których każdy zawiera wartości pól, do których stosowana jest dyrektywa.
Ta query:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsData",
type: LIST,
affectAdditionalFieldsUnderPos: [1]
)
}
}...eksportuje zmienną dynamiczną $postsData z wartością:
[
{
"title": "Hello world!",
"content": "Lorem ipsum."
},
{
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
]Typ DICTIONARY / Pojedyncze pole
Zmienna dynamiczna będzie zawierać słownik (typu JSONObject) z ID zapytanej encji jako kluczem i wartościami pola jako wartością, po przekazaniu parametru type: DICTIONARY.
Ta query:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postIDTitles", type: DICTIONARY)
}
}...eksportuje zmienną dynamiczną $postIDTitles z wartością:
{
"1": "Hello world!",
"5": "Everything good?"
}Typ DICTIONARY / Wiele pól
W tej kombinacji eksportujemy słownik słowników: { key: entity ID, value: { key: field alias, value: field value } } (używając typu JSONObject, który będzie zawierał wpisy typu JSONObject).
Ta query:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsIDProperties",
type: DICTIONARY,
affectAdditionalFieldsUnderPos: [1]
)
}
}...eksportuje zmienną dynamiczną $postsIDProperties z wartością:
{
"1": {
"title": "Hello world!",
"content": "Lorem ipsum."
},
"5": {
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
}Eksportowanie wartości podczas iterowania tablicy lub obiektu JSON
@export respektuje kardynalność dowolnej otaczającej meta-dyrektywy.
W szczególności, zawsze gdy @export jest zagnieżdżone poniżej meta-dyrektywy, która iteruje po elementach tablicy lub właściwościach obiektu JSON (tj. @underEachArrayItem i @underEachJSONObjectProperty), eksportowana wartość będzie tablicą.
Ta query:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underEachArrayItem
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...produkuje $contentAttributes z wartością:
[
"List Block",
"Columns Block",
"Columns inside Columns (nested inner blocks)",
"Life is so rich",
"Life is so dynamic"
]Natomiast ta sama query, która uzyskuje dostęp do konkretnego elementu w tablicy zamiast iterować po wszystkich (zastępując @underEachArrayItem przez @underArrayItem(index: 0)), wyeksportuje pojedynczą wartość.
Ta query:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underArrayItem(index: 0)
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...produkuje $contentAttributes z wartością:
"List Block"Kolejność wykonywania dyrektyw
Jeśli przed @export są inne dyrektywy, eksportowana wartość będzie odzwierciedlać modyfikacje dokonane przez te poprzednie dyrektywy.
Na przykład w tej query, w zależności od tego, czy @export następuje przed czy po @strUpperCase, wynik będzie różny:
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)
}Produkując:
{
"data": {
"id": "ROOT",
"again": "ROOT",
"mirrorID": "root",
"mirrorAgain": "ROOT"
}
}Wykonywanie w Persisted Queries
Gdy query GraphQL zawiera wiele operacji w Persisted Query, możemy wywołać odpowiedni endpoint, przekazując parametr URL ?operationName=... z nazwą operacji do wykonania; w przeciwnym razie zostanie wykonana ostatnia operacja.
Na przykład, aby wykonać operację GetPostsContainingString w Persisted Query z endpointem /graphql-query/posts-with-user-name/, musimy wywołać:
https://mysite.com/graphql-query/posts-with-user-name/?operationName=GetPostsContainingStringPrzykłady
Importowanie treści z zewnętrznego endpointu API:
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
}
}Pobieranie danych posta, przekształcanie ich i ponowne zapisywanie:
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
}
}
}Aktualizacja posta, jeśli istnieje, lub wyświetlenie komunikatu o błędzie w przeciwnym razie:
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
}Logowanie użytkownika przed wykonaniem mutacji i wylogowanie go natychmiast po:
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
}Warunkowe logowanie użytkownika przed wykonaniem mutacji, jeśli dane uwierzytelniające zostały podane:
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
}Specyfikacja GraphQL
Ta funkcjonalność nie jest obecnie częścią specyfikacji GraphQL, ale była wnioskowana: