Eseguire più queries contemporaneamente
Più queries possono essere combinate ed eseguite come una singola operazione, riutilizzando il loro stato e i loro dati.
Questo è diverso dal query batching, in cui il server GraphQL esegue anch'esso più queries in una singola richiesta, ma tali queries vengono semplicemente eseguite una dopo l'altra, in modo indipendente l'una dall'altra.
Questa funzionalità migliora le prestazioni. Invece di eseguire queries in modo indipendente in richieste separate (così da eseguire prima un'operazione contro il server GraphQL, attendere la sua risposta, e poi usare quel risultato per effettuare un'altra operazione), possiamo eseguirle insieme, evitando così la latenza derivante dalle multiple richieste.
La Multiple Query Execution ci consente inoltre di organizzare meglio le nostre queries GraphQL, suddividendole in unità logiche che dipendono l'una dall'altra, e che vengono eseguite condizionalmente in base al risultato di un'operazione precedente.
Come usare l'esecuzione di più queries
Supponiamo di voler cercare tutti i post che menzionano il nome dell'utente connesso. Normalmente, avremmo bisogno di due queries per ottenere questo risultato:
Prima recuperiamo il name dell'utente:
query GetLoggedInUserName {
me {
name
}
}...e poi, dopo aver eseguito la prima query, possiamo passare il name dell'utente recuperato come variabile $search per effettuare la ricerca in una seconda query:
query GetPostsContainingString($search: String!) {
posts(filter: { search: $search }) {
id
title
}
}Multiple Query Execution semplifica questo processo, consentendoci di recuperare tutti i dati ed eseguire tutta la logica necessaria in una singola richiesta:
query GetLoggedInUserName {
me {
name @export(as: "search")
}
}
query GetPostsContainingString @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $search }) {
id
title
}
}La Multiple Query Execution si ottiene mediante l'uso di queste direttive speciali:
@depends(direttiva di operazione): fa sì che un'operazione (che sia unaqueryo unamutation) indichi quali altre operazioni devono essere eseguite prima@export(direttiva di campo): esporta il valore di un campo da un'operazione, per iniettarlo come input in un campo di un'altra operazione@deferredExport(direttiva di campo): simile a@exportma da usare con Multi-Field Directives.
Inoltre, le direttive @include e @skip sono disponibili anche come direttive di operazione (normalmente sono solo direttive di campo), e possono essere utilizzate per eseguire condizionalmente un'operazione se soddisfa una determinata condizione.
Il server GraphQL creerà l'elenco delle operazioni da caricare ed eseguire, recuperandole da ogni @depends(on: ...), ed esporterà i valori di qualsiasi campo contenente @export come variabile dinamica (con il nome definito nell'argomento as) da utilizzare come input in qualsiasi operazione successiva.
Combinando queste direttive, siamo in grado di suddividere qualsiasi funzionalità complessa in passaggi intermedi, alternando operazioni query e mutation, aggiungendo le loro dipendenze nell'ordine richiesto, ed eseguendole tutte in una singola richiesta definendo l'operazione più esterna in ?operationName=... (nell'esempio sopra, sarà ?operationName=GetPostsContainingString).
Definire le operazioni da caricare ed eseguire tramite @depends
Quando il documento GraphQL contiene più operazioni, indichiamo al server quale eseguire tramite il parametro URL ?operationName=...; altrimenti, verrà eseguita l'ultima operazione.
A partire da questa operazione iniziale, il server raccoglierà tutte le operazioni da eseguire, definite aggiungendo la direttiva depends(on: [...]), e le eseguirà nell'ordine corrispondente rispettando le dipendenze.
L'argomento operations della direttiva riceve un array di nomi di operazioni ([String]), oppure possiamo fornire anche un singolo nome di operazione (String).
In questa query, passiamo ?operationName=Four, e le operazioni eseguite (che siano query o mutation) saranno ["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 ...
}Condividere dati tra queries tramite @export
La direttiva @export esporta il valore di un campo (o di un insieme di campi) in una variabile dinamica, da utilizzare come input in un campo di un'altra query.
Ad esempio, in questa query esportiamo il nome dell'utente connesso, e utilizziamo questo valore per cercare post che contengono questa stringa (si noti che la variabile $loggedInUserName, essendo dinamica, non deve essere definita nell'operazione FindPosts):
query GetLoggedInUserName {
me {
name @export(as: "loggedInUserName")
}
}
query FindPosts @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $loggedInUserName }) {
id
}
}Output delle variabili dinamiche
@export può produrre 6 output diversi, basati su una combinazione di:
- Il valore dell'argomento
type(traSINGLE,LISToDICTIONARY) - Se la direttiva è applicata a un singolo campo, o a più campi (tramite il modulo Multi-Field Directives)
I 6 output possibili sono quindi:
- Tipo
SINGLE:- Campo singolo
- Multi-campo
- Tipo
LIST:- Campo singolo
- Multi-campo
- Tipo
DICTIONARY:- Campo singolo
- Multi-campo
Tipo SINGLE / Campo singolo
L'output è un valore singolo passando il parametro type: SINGLE (che è impostato come valore predefinito).
In questa query:
query {
post(by: { id: 1 }) {
title @export(as: "postTitle", type: SINGLE)
}
}...la variabile dinamica $postTitle avrà il valore:
"Hello world!"Si noti che se SINGLE è applicato su un array di entità, viene esportato il valore dell'ultima entità.
In questa query:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitle", type: SINGLE)
}
}...la variabile dinamica $postTitle avrà il valore del post con ID 5:
"Everything good?"Tipo SINGLE / Multi-campo
Se @export è applicato su più campi (aggiungendo il parametro affectAdditionalFieldsUnderPos fornito dal modulo Multi-Field Directives), il valore impostato nella variabile dinamica è un dizionario di { key: alias del campo, value: valore del campo } (di tipo JSONObject).
Questa query:
query {
post(by: { id: 1 }) {
title
content
@export(
as: "postData",
type: SINGLE,
affectAdditionalFieldsUnderPos: [1]
)
}
}...esporta la variabile dinamica $postData con il valore:
{
"title": "Hello world!",
"content": "Lorem ipsum."
}Tipo LIST / Campo singolo
La variabile dinamica conterrà un array con il valore del campo di tutte le entità interrogate (dal campo contenitore), passando il parametro type: LIST.
Eseguendo questa query (in cui le entità interrogate sono post con ID 1 e 5):
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitles", type: LIST)
}
}...la variabile dinamica $postTitles avrà il valore:
[
"Hello world!",
"Everything good?"
]Tipo LIST / Multi-campo
Otteniamo un array di dizionari (di tipo JSONObject), ciascuno contenente i valori dei campi su cui è applicata la direttiva.
Questa query:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsData",
type: LIST,
affectAdditionalFieldsUnderPos: [1]
)
}
}...esporta la variabile dinamica $postsData con il valore:
[
{
"title": "Hello world!",
"content": "Lorem ipsum."
},
{
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
]Tipo DICTIONARY / Campo singolo
La variabile dinamica conterrà un dizionario (di tipo JSONObject) con l'ID dell'entità interrogata come chiave e i valori del campo come valore, passando il parametro type: DICTIONARY.
Questa query:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postIDTitles", type: DICTIONARY)
}
}...esporta la variabile dinamica $postIDTitles con il valore:
{
"1": "Hello world!",
"5": "Everything good?"
}Tipo DICTIONARY / Multi-campo
In questa combinazione, esportiamo un dizionario di dizionari: { key: ID dell'entità, value: { key: alias del campo, value: valore del campo } } (usando un tipo JSONObject che conterrà voci di tipo JSONObject).
Questa query:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsIDProperties",
type: DICTIONARY,
affectAdditionalFieldsUnderPos: [1]
)
}
}...esporta la variabile dinamica $postsIDProperties con il valore:
{
"1": {
"title": "Hello world!",
"content": "Lorem ipsum."
},
"5": {
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
}Esecuzione condizionale delle operazioni
Quando Multiple Query Execution è abilitato, le direttive @include e @skip sono disponibili anche come direttive di operazione, e possono essere utilizzate per eseguire condizionalmente un'operazione se soddisfa una determinata condizione.
Ad esempio, in questa query, l'operazione CheckIfPostExists esporta una variabile dinamica $postExists e, solo se il suo valore è true, verrà eseguita la mutation ExecuteOnlyIfPostExists:
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...
}Esportare valori durante l'iterazione di un array o di un oggetto JSON
@export rispetta la cardinalità di qualsiasi meta-direttiva contenitore.
In particolare, ogni volta che @export è annidato sotto una meta-direttiva che itera sugli elementi di un array o sulle proprietà di un oggetto JSON (ovvero @underEachArrayItem e @underEachJSONObjectProperty), il valore esportato sarà un array.
Questa query:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underEachArrayItem
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...produce $contentAttributes con il valore:
[
"List Block",
"Columns Block",
"Columns inside Columns (nested inner blocks)",
"Life is so rich",
"Life is so dynamic"
]Al contrario, la stessa query che accede a un elemento specifico dell'array invece di iterare su tutti (sostituendo @underEachArrayItem con @underArrayItem(index: 0)) esporterà un valore singolo.
Questa query:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underArrayItem(index: 0)
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...produce $contentAttributes con il valore:
"List Block"Ordine di esecuzione delle direttive
Se ci sono altre direttive prima di @export, il valore esportato rifletterà le modifiche apportate da quelle direttive precedenti.
Ad esempio, in questa query, a seconda che @export avvenga prima o dopo @strUpperCase, il risultato sarà diverso:
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)
}Producendo:
{
"data": {
"id": "ROOT",
"again": "ROOT",
"mirrorID": "root",
"mirrorAgain": "ROOT"
}
}Multi-Field Directives
Quando la funzionalità Multi-Field Directives è abilitata e si esporta il valore di più campi in un dizionario, usare @deferredExport al posto di @export per garantire che tutte le direttive di ogni campo coinvolto siano state eseguite prima di esportare il valore del campo.
Ad esempio, in questa query, il primo campo ha la direttiva @strUpperCase applicata e il secondo ha @titleCase. Quando si esegue @deferredExport, il valore esportato avrà queste direttive applicate:
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)
}Producendo:
{
"data": {
"id": "ROOT",
"again": "Root",
"mirrorProps": {
"id": "ROOT",
"again": "Root"
}
}
}Spec GraphQL
Questa funzionalità non fa attualmente parte della spec GraphQL, ma è stata richiesta: