Wykonywanie wielu queries jednocześnie
Wiele queries można łączyć i wykonywać jako jedną operację, współdzieląc ich stan i dane.
Różni się to od query batching, gdzie serwer GraphQL również wykonuje wiele queries w jednym żądaniu, ale te queries są po prostu wykonywane jedna po drugiej, niezależnie od siebie.
Ta funkcja poprawia wydajność. Zamiast wykonywać queries niezależnie w różnych żądaniach (najpierw wykonując operację przeciwko serwerowi GraphQL, czekając na odpowiedź, a następnie używając tego wyniku do wykonania kolejnej operacji), możemy wykonać je razem, unikając w ten sposób opóźnień wynikających z wielu żądań.
Multiple Query Execution pozwala nam również lepiej organizować nasze queries GraphQL, dzieląc je na logiczne jednostki, które od siebie zależą i są warunkowo wykonywane na podstawie wyniku poprzedniej operacji.
Jak używać wykonywania wielu queries
Załóżmy, że chcemy wyszukać wszystkie posty, które zawierają imię zalogowanego użytkownika. Normalnie potrzebowalibyśmy dwóch queries, aby to osiągnąć:
Najpierw pobieramy name użytkownika:
query GetLoggedInUserName {
me {
name
}
}...a następnie, po wykonaniu pierwszej query, możemy przekazać pobrane name użytkownika jako zmienną $search, aby wykonać wyszukiwanie w drugiej query:
query GetPostsContainingString($search: String!) {
posts(filter: { search: $search }) {
id
title
}
}Multiple Query Execution upraszcza ten proces, umożliwiając pobranie wszystkich danych i wykonanie całej wymaganej logiki w jednym żądaniu:
query GetLoggedInUserName {
me {
name @export(as: "search")
}
}
query GetPostsContainingString @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $search }) {
id
title
}
}Multiple Query Execution jest osiągane za pomocą tych specjalnych dyrektyw:
@depends(dyrektywa operacji): sprawia, że operacja (czy toquery, czymutation) wskazuje, jakie inne operacje muszą zostać wykonane wcześniej@export(dyrektywa pola): eksportuje wartość pola z jednej operacji, aby wstrzyknąć ją jako dane wejściowe do pola w innej operacji@deferredExport(dyrektywa pola): podobna do@export, ale do użycia z Multi-Field Directives.
Ponadto dyrektywy @include i @skip są również dostępne jako dyrektywy operacji (normalnie są tylko dyrektywami pola), i mogą być używane do warunkowego wykonania operacji, jeśli spełnia ona pewien warunek.
Serwer GraphQL utworzy listę operacji do załadowania i wykonania, pobierając je z każdego @depends(on: ...), i wyeksportuje wartości z dowolnego pola zawierającego @export jako zmienną dynamiczną (o nazwie zdefiniowanej w argumencie as), aby użyć jej jako danych wejściowych w kolejnych operacjach.
Łącząc te dyrektywy, jesteśmy w stanie podzielić dowolną złożoną funkcjonalność na etapy pośrednie, naprzemiennie używając operacji query i mutation, dodając ich zależności w wymaganej kolejności i wykonując je wszystkie w jednym żądaniu, definiując najbardziej zewnętrzną operację w ?operationName=... (w powyższym przykładzie będzie to ?operationName=GetPostsContainingString).
Definiowanie operacji do załadowania i wykonania via @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 w odpowiedniej kolejności, respektując zależności.
Argument operations dyrektywy przyjmuje tablicę nazw operacji ([String]), lub możemy również 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 ...
}Współdzielenie danych między queries via @export
Dyrektywa @export eksportuje wartość pola (lub zestawu pól) do zmiennej dynamicznej, która ma być użyta jako dane wejściowe w polu innej query.
Na przykład, w tej query eksportujemy imię zalogowanego użytkownika i używamy tej wartości do wyszukiwania postów zawierających ten ciąg znaków (należy zauważyć, ż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
}
}Wyniki zmiennych dynamicznych
@export może produkować 6 różnych wyników, opartych na kombinacji:
- Wartości argumentu
type(alboSINGLE,LISTlubDICTIONARY) - Czy dyrektywa jest stosowana do jednego pola, czy do wielu pól (via moduł Multi-Field Directives)
6 możliwych wyników 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
Wynik jest pojedynczą wartością przy przekazaniu parametru type: SINGLE (który jest ustawiony jako wartość domyślna).
W tej query:
query {
post(by: { id: 1 }) {
title @export(as: "postTitle", type: SINGLE)
}
}...zmienna dynamiczna $postTitle będzie miała wartość:
"Hello world!"Należy zauważyć, że jeśli SINGLE jest zastosowany 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 miała wartość dla posta z ID 5:
"Everything good?"Typ SINGLE / Wiele pól
Jeśli @export jest stosowany do kilku pól (przez dodanie parametru affectAdditionalFieldsUnderPos dostarczonego przez moduł Multi-Field Directives), wartość ustawiana w zmiennej dynamicznej jest słownikiem { key: alias pola, value: wartość pola } (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 otaczającego pola), przez przekazanie parametru type: LIST.
Podczas wykonywania 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 miała 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 dyrektywa jest stosowana.
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ą, przez przekazanie 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: ID encji, value: { key: alias pola, value: wartość pola } } (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."
}
}Warunkowe wykonywanie operacji
Gdy Multiple Query Execution 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 ona pewien warunek.
Na przykład, w tej query operacja CheckIfPostExists eksportuje zmienną dynamiczną $postExists i tylko jeśli 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...
}Eksportowanie wartości podczas iterowania tablicy lub obiektu JSON
@export respektuje kardynalność z dowolnej otaczającej meta-dyrektywy.
W szczególności, gdy @export jest zagnieżdżony 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"
]W przeciwieństwie do tego, ta sama query, która uzyskuje dostęp do konkretnego elementu 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 znajdują się 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 inny:
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"
}
}Multi-Field Directives
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 eksportowaniem wartości pola.
Na przykład, w tej query, pierwsze pole ma zastosowaną dyrektywę @strUpperCase, a drugie @titleCase. Podczas wykonywania @deferredExport eksportowana wartość będzie miała zastosowane te dyrektywy:
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)
}Produkując:
{
"data": {
"id": "ROOT",
"again": "Root",
"mirrorProps": {
"id": "ROOT",
"again": "Root"
}
}
}Specyfikacja GraphQL
Ta funkcjonalność nie jest obecnie częścią specyfikacji GraphQL, ale została zgłoszona jako propozycja: