Executando múltiplas queries simultaneamente
Múltiplas queries podem ser combinadas e executadas como uma única operação, reutilizando seu estado e seus dados.
Isso é diferente do query batching, em que o servidor GraphQL também executa múltiplas queries em uma única requisição, mas essas queries são simplesmente executadas uma após a outra, de forma independente entre si.
Essa funcionalidade melhora o desempenho. Em vez de executar queries de forma independente em requisições diferentes (executando primeiro uma operação contra o servidor GraphQL, aguardando sua resposta e, em seguida, usando esse resultado para realizar outra operação), podemos executá-las juntas, evitando assim a latência das múltiplas requisições.
A Multiple Query Execution também nos permite organizar melhor nossas queries GraphQL, dividindo-as em unidades lógicas que dependem umas das outras, e que são executadas condicionalmente com base no resultado de uma operação anterior.
Como usar a execução de múltiplas queries
Suponha que queremos buscar todos os posts que mencionam o nome do usuário logado. Normalmente, precisaríamos de duas queries para isso:
Primeiro recuperamos o name do usuário:
query GetLoggedInUserName {
me {
name
}
}...e então, tendo executado a primeira query, podemos passar o name do usuário recuperado como variável $search para realizar a busca em uma segunda query:
query GetPostsContainingString($search: String!) {
posts(filter: { search: $search }) {
id
title
}
}Multiple Query Execution simplifica esse processo, permitindo recuperar todos os dados e executar toda a lógica necessária em uma única requisição:
query GetLoggedInUserName {
me {
name @export(as: "search")
}
}
query GetPostsContainingString @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $search }) {
id
title
}
}A Multiple Query Execution é obtida com o uso destas diretivas especiais:
@depends(diretiva de operação): faz com que uma operação (seja umaqueryoumutation) indique quais outras operações devem ser executadas antes@export(diretiva de campo): exporta o valor de um campo de uma operação, para injetá-lo como entrada em um campo de outra operação@deferredExport(diretiva de campo): semelhante a@export, mas para ser usada com Multi-Field Directives.
Além disso, as diretivas @include e @skip também estão disponíveis como diretivas de operação (normalmente são apenas diretivas de campo), e podem ser usadas para executar condicionalmente uma operação se ela satisfizer alguma condição.
O servidor GraphQL criará a lista de operações a carregar e executar, recuperando-as de cada @depends(on: ...), e exportará os valores de qualquer campo contendo @export como uma variável dinâmica (com o nome definido no argumento as) para ser usada como entrada em qualquer operação subsequente.
Combinando essas diretivas, somos capazes de dividir qualquer funcionalidade complexa em etapas intermediárias, alternando operações query e mutation, adicionando suas dependências na ordem necessária, e executando todas em uma única requisição definindo a operação mais externa em ?operationName=... (no exemplo acima, será ?operationName=GetPostsContainingString).
Definindo as operações a carregar e executar via @depends
Quando o documento GraphQL contém múltiplas operações, indicamos ao servidor qual delas executar via parâmetro de URL ?operationName=...; caso contrário, a última operação será executada.
A partir dessa operação inicial, o servidor coletará todas as operações a executar, definidas adicionando a diretiva depends(on: [...]), e as executará na ordem correspondente respeitando as dependências.
O argumento operations da diretiva recebe um array de nomes de operações ([String]), ou também podemos fornecer um único nome de operação (String).
Nesta query, passamos ?operationName=Four, e as operações executadas (sejam query ou mutation) serão ["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 ...
}Compartilhando dados entre queries via @export
A diretiva @export exporta o valor de um campo (ou conjunto de campos) para uma variável dinâmica, a ser usada como entrada em um campo de outra query.
Por exemplo, nesta query exportamos o nome do usuário logado e usamos esse valor para buscar posts que contêm essa string (observe que a variável $loggedInUserName, por ser dinâmica, não precisa ser definida na operação FindPosts):
query GetLoggedInUserName {
me {
name @export(as: "loggedInUserName")
}
}
query FindPosts @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $loggedInUserName }) {
id
}
}Saídas de variáveis dinâmicas
@export pode produzir 6 saídas diferentes, baseadas em uma combinação de:
- O valor do argumento
type(entreSINGLE,LISTouDICTIONARY) - Se a diretiva é aplicada a um único campo, ou a múltiplos campos (via o módulo Multi-Field Directives)
As 6 saídas possíveis são:
- Tipo
SINGLE:- Campo único
- Multi-campo
- Tipo
LIST:- Campo único
- Multi-campo
- Tipo
DICTIONARY:- Campo único
- Multi-campo
Tipo SINGLE / Campo único
A saída é um valor único ao passar o parâmetro type: SINGLE (que é definido como valor padrão).
Nesta query:
query {
post(by: { id: 1 }) {
title @export(as: "postTitle", type: SINGLE)
}
}...a variável dinâmica $postTitle terá o valor:
"Hello world!"Observe que se SINGLE é aplicado sobre um array de entidades, o valor exportado é o da última entidade.
Nesta query:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitle", type: SINGLE)
}
}...a variável dinâmica $postTitle terá o valor do post com ID 5:
"Everything good?"Tipo SINGLE / Multi-campo
Se @export é aplicado em vários campos (adicionando o parâmetro affectAdditionalFieldsUnderPos fornecido pelo módulo Multi-Field Directives), o valor definido na variável dinâmica é um dicionário de { key: alias do campo, value: valor do campo } (do tipo JSONObject).
Esta query:
query {
post(by: { id: 1 }) {
title
content
@export(
as: "postData",
type: SINGLE,
affectAdditionalFieldsUnderPos: [1]
)
}
}...exporta a variável dinâmica $postData com o valor:
{
"title": "Hello world!",
"content": "Lorem ipsum."
}Tipo LIST / Campo único
A variável dinâmica conterá um array com o valor do campo de todas as entidades consultadas (do campo envolvente), ao passar o parâmetro type: LIST.
Ao executar esta query (em que as entidades consultadas são posts com ID 1 e 5):
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitles", type: LIST)
}
}...a variável dinâmica $postTitles terá o valor:
[
"Hello world!",
"Everything good?"
]Tipo LIST / Multi-campo
Obtemos um array de dicionários (do tipo JSONObject), cada um contendo os valores dos campos nos quais a diretiva é aplicada.
Esta query:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsData",
type: LIST,
affectAdditionalFieldsUnderPos: [1]
)
}
}...exporta a variável dinâmica $postsData com o valor:
[
{
"title": "Hello world!",
"content": "Lorem ipsum."
},
{
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
]Tipo DICTIONARY / Campo único
A variável dinâmica conterá um dicionário (do tipo JSONObject) com o ID da entidade consultada como chave e os valores do campo como valor, ao passar o parâmetro type: DICTIONARY.
Esta query:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postIDTitles", type: DICTIONARY)
}
}...exporta a variável dinâmica $postIDTitles com o valor:
{
"1": "Hello world!",
"5": "Everything good?"
}Tipo DICTIONARY / Multi-campo
Nesta combinação, exportamos um dicionário de dicionários: { key: ID da entidade, value: { key: alias do campo, value: valor do campo } } (usando um tipo JSONObject que conterá entradas do tipo JSONObject).
Esta query:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsIDProperties",
type: DICTIONARY,
affectAdditionalFieldsUnderPos: [1]
)
}
}...exporta a variável dinâmica $postsIDProperties com o valor:
{
"1": {
"title": "Hello world!",
"content": "Lorem ipsum."
},
"5": {
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
}Execução condicional de operações
Quando Multiple Query Execution está habilitado, as diretivas @include e @skip também ficam disponíveis como diretivas de operação, e podem ser usadas para executar condicionalmente uma operação se ela satisfizer alguma condição.
Por exemplo, nesta query, a operação CheckIfPostExists exporta uma variável dinâmica $postExists e, somente se seu valor for true, a mutation ExecuteOnlyIfPostExists será executada:
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...
}Exportando valores ao iterar um array ou objeto JSON
@export respeita a cardinalidade de qualquer meta-diretiva envolvente.
Em particular, sempre que @export está aninhado abaixo de uma meta-diretiva que itera sobre itens de array ou propriedades de objeto JSON (ou seja, @underEachArrayItem e @underEachJSONObjectProperty), o valor exportado será um array.
Esta query:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underEachArrayItem
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...produz $contentAttributes com o valor:
[
"List Block",
"Columns Block",
"Columns inside Columns (nested inner blocks)",
"Life is so rich",
"Life is so dynamic"
]Em contraste, a mesma query que acessa um item específico do array em vez de iterar sobre todos eles (substituindo @underEachArrayItem por @underArrayItem(index: 0)) exportará um valor único.
Esta query:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underArrayItem(index: 0)
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...produz $contentAttributes com o valor:
"List Block"Ordem de execução das diretivas
Se houver outras diretivas antes de @export, o valor exportado refletirá as modificações feitas por essas diretivas anteriores.
Por exemplo, nesta query, dependendo de @export ocorrer antes ou depois de @strUpperCase, o resultado será diferente:
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)
}Produzindo:
{
"data": {
"id": "ROOT",
"again": "ROOT",
"mirrorID": "root",
"mirrorAgain": "ROOT"
}
}Multi-Field Directives
Quando a funcionalidade Multi-Field Directives está habilitada e exportamos o valor de múltiplos campos para um dicionário, use @deferredExport em vez de @export para garantir que todas as diretivas de cada campo envolvido tenham sido executadas antes de exportar o valor do campo.
Por exemplo, nesta query, o primeiro campo tem a diretiva @strUpperCase aplicada e o segundo tem @titleCase. Ao executar @deferredExport, o valor exportado terá essas diretivas aplicadas:
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)
}Produzindo:
{
"data": {
"id": "ROOT",
"again": "Root",
"mirrorProps": {
"id": "ROOT",
"again": "Root"
}
}
}Spec do GraphQL
Esta funcionalidade atualmente não faz parte da spec do GraphQL, mas foi solicitada: