Configurar o schema
Configurar o schemaExecutando múltiplas queries simultaneamente

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 uma query ou mutation) 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 (entre SINGLE, LIST ou DICTIONARY)
  • 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:

  1. Tipo SINGLE:
    1. Campo único
    2. Multi-campo
  2. Tipo LIST:
    1. Campo único
    2. Multi-campo
  3. Tipo DICTIONARY:
    1. Campo único
    2. 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: