Het schema configureren
Het schema configurerenMeerdere queries gelijktijdig uitvoeren

Meerdere queries gelijktijdig uitvoeren

Meerdere queries kunnen worden gecombineerd en uitgevoerd als één enkele operatie, waarbij hun toestand en gegevens worden gedeeld.

Dit verschilt van query batching, waarbij de GraphQL-server ook meerdere queries in één verzoek uitvoert, maar die queries gewoon één voor één worden uitgevoerd, onafhankelijk van elkaar.

Deze functie verbetert de prestaties. In plaats van queries onafhankelijk uit te voeren in afzonderlijke verzoeken (waarbij je eerst een operatie uitvoert tegen de GraphQL-server, wacht op het antwoord en dat resultaat vervolgens gebruikt om een andere operatie uit te voeren), kunnen we ze samen uitvoeren en zo de latentie van meerdere verzoeken vermijden.

Multiple Query Execution stelt ons ook in staat om onze GraphQL-queries beter te organiseren: we kunnen ze opsplitsen in logische eenheden die van elkaar afhankelijk zijn en die voorwaardelijk worden uitgevoerd op basis van het resultaat van een vorige operatie.

Hoe je multiple query execution gebruikt

Stel dat we alle posts willen zoeken die de naam van de ingelogde gebruiker vermelden. Normaal gesproken zouden we twee queries nodig hebben om dit te bereiken:

We halen eerst de name van de gebruiker op:

query GetLoggedInUserName {
  me {
    name
  }
}

...en daarna, nadat we de eerste query hebben uitgevoerd, kunnen we de opgehaalde name van de gebruiker doorgeven als variabele $search om de zoekopdracht uit te voeren in een tweede query:

query GetPostsContainingString($search: String!) {
  posts(filter: { search: $search }) {
    id
    title
  }
}

Multiple Query Execution vereenvoudigt dit proces en stelt ons in staat alle gegevens op te halen en alle vereiste logica uit te voeren in één enkel verzoek:

query GetLoggedInUserName {
  me {
    name @export(as: "search")
  }
}
 
query GetPostsContainingString @depends(on: "GetLoggedInUserName") {
  posts(filter: { search: $search }) {
    id
    title
  }
}

Multiple Query Execution wordt bereikt met behulp van deze speciale directives:

  • @depends (operatie-directive): laat een operatie (zowel query als mutation) aangeven welke andere operaties eerder moeten worden uitgevoerd
  • @export (veld-directive): exporteert de waarde van een veld uit één operatie, om die als invoer te injecteren in een veld van een andere operatie
  • @deferredExport (veld-directive): vergelijkbaar met @export, maar te gebruiken met Multi-Field Directives.

Daarnaast zijn directives @include en @skip ook beschikbaar als operatie-directives (normaal zijn het alleen veld-directives), en deze kunnen worden gebruikt om een operatie voorwaardelijk uit te voeren als aan een bepaalde voorwaarde is voldaan.

De GraphQL-server maakt de lijst van te laden en uit te voeren operaties op, haalt ze op uit elke @depends(on: ...), en exporteert de waarden van elk veld met @export als een dynamische variabele (met naam gedefinieerd onder argument as) om als invoer te dienen in een volgende operatie.

Door deze directives te combineren, kunnen we complexe functionaliteit opsplitsen in tussenstappen, afwisselend query- en mutation-operaties gebruiken, hun afhankelijkheden in de vereiste volgorde toevoegen en ze allemaal uitvoeren in één enkel verzoek door de buitenste operatie te definiëren in ?operationName=... (in het bovenstaande voorbeeld is dat ?operationName=GetPostsContainingString).

De te laden en uit te voeren operaties definiëren via @depends

Wanneer het GraphQL-document meerdere operaties bevat, geven we de server aan welke we willen uitvoeren via de URL-parameter ?operationName=...; anders wordt de laatste operatie uitgevoerd.

Vanaf deze initiële operatie verzamelt de server alle uit te voeren operaties, die worden gedefinieerd door de directive depends(on: [...]) toe te voegen, en voert ze uit in de juiste volgorde met inachtneming van de afhankelijkheden.

Het directive-argument operations ontvangt een array van operatienamen ([String]), of je kunt ook één enkele operatienaam opgeven (String).

In deze query geven we ?operationName=Four door, en de uitgevoerde operaties (zowel query als mutation) zijn ["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 ...
}

Gegevens delen tussen queries via @export

Directive @export exporteert de waarde van een veld (of een reeks velden) naar een dynamische variabele, om als invoer te worden gebruikt in een veld van een andere query.

In deze query exporteren we bijvoorbeeld de naam van de ingelogde gebruiker en gebruiken we deze waarde om te zoeken naar posts die deze string bevatten (let op: variabele $loggedInUserName hoeft, omdat het een dynamische variabele is, niet te worden gedefinieerd in operatie FindPosts):

query GetLoggedInUserName {
  me {
    name @export(as: "loggedInUserName")
  }
}
 
query FindPosts @depends(on: "GetLoggedInUserName") {
  posts(filter: { search: $loggedInUserName }) {
    id
  }
}

Uitvoer van dynamische variabelen

@export kan 6 verschillende uitvoertypen produceren, op basis van een combinatie van:

  • De waarde van het argument type (ofwel SINGLE, LIST of DICTIONARY)
  • Of de directive wordt toegepast op één veld of op meerdere velden (via de module Multi-Field Directives)

De 6 mogelijke uitvoertypen zijn:

  1. Type SINGLE:
    1. Enkel veld
    2. Meerdere velden
  2. Type LIST:
    1. Enkel veld
    2. Meerdere velden
  3. Type DICTIONARY:
    1. Enkel veld
    2. Meerdere velden

Type SINGLE / Enkel veld

De uitvoer is een enkele waarde wanneer de parameter type: SINGLE wordt doorgegeven (dit is de standaardwaarde).

In deze query:

query {
  post(by: { id: 1 }) {
    title @export(as: "postTitle", type: SINGLE)
  }
}

...heeft de dynamische variabele $postTitle de waarde:

"Hello world!"

Let op: als SINGLE wordt toegepast op een array van entiteiten, wordt de waarde van de laatste entiteit geëxporteerd.

In deze query:

query {
  posts(filter: { ids: [1, 5] }) {
    title @export(as: "postTitle", type: SINGLE)
  }
}

...heeft de dynamische variabele $postTitle de waarde voor de post met ID 5:

"Everything good?"

Type SINGLE / Meerdere velden

Als @export wordt toegepast op meerdere velden (door de parameter affectAdditionalFieldsUnderPos toe te voegen die wordt geleverd door de module Multi-Field Directives), dan is de waarde die in de dynamische variabele wordt gezet een woordenboek van { key: veldalias, value: veldwaarde } (van type JSONObject).

Deze query:

query {
  post(by: { id: 1 }) {
    title
    content
      @export(
        as: "postData",
        type: SINGLE,
        affectAdditionalFieldsUnderPos: [1]
      )
  }
}

...exporteert dynamische variabele $postData met de waarde:

{
  "title": "Hello world!",
  "content": "Lorem ipsum."
}

Type LIST / Enkel veld

De dynamische variabele bevat een array met de veldwaarde van alle bevraagde entiteiten (uit het omsluitende veld), door de parameter type: LIST door te geven.

Bij het uitvoeren van deze query (waarbij de bevraagde entiteiten posts zijn met ID 1 en 5):

query {
  posts(filter: { ids: [1, 5] }) {
    title @export(as: "postTitles", type: LIST)
  }
}

...heeft de dynamische variabele $postTitles de waarde:

[
  "Hello world!",
  "Everything good?"
]

Type LIST / Meerdere velden

We krijgen een array van woordenboeken (van type JSONObject), elk met de waarden van de velden waarop de directive wordt toegepast.

Deze query:

query {
  posts(filter: { ids: [1, 5] }) {
    title
    content
      @export(
        as: "postsData",
        type: LIST,
        affectAdditionalFieldsUnderPos: [1]
      )
  }
}

...exporteert dynamische variabele $postsData met de waarde:

[
  {
    "title": "Hello world!",
    "content": "Lorem ipsum."
  },
  {
    "title": "Everything good?",
    "content": "Quisque convallis libero in sapien pharetra tincidunt."
  }
]

Type DICTIONARY / Enkel veld

De dynamische variabele bevat een woordenboek (van type JSONObject) met het ID van de bevraagde entiteit als sleutel en de veldwaarden als waarde, door de parameter type: DICTIONARY door te geven.

Deze query:

query {
  posts(filter: { ids: [1, 5] }) {
    title @export(as: "postIDTitles", type: DICTIONARY)
  }
}

...exporteert dynamische variabele $postIDTitles met de waarde:

{
  "1": "Hello world!",
  "5": "Everything good?"
}

Type DICTIONARY / Meerdere velden

In deze combinatie exporteren we een woordenboek van woordenboeken: { key: entiteit-ID, value: { key: veldalias, value: veldwaarde } } (met een type JSONObject dat vermeldingen van het type JSONObject bevat).

Deze query:

query {
  posts(filter: { ids: [1, 5] }) {
    title
    content
      @export(
        as: "postsIDProperties",
        type: DICTIONARY,
        affectAdditionalFieldsUnderPos: [1]
      )
  }
}

...exporteert dynamische variabele $postsIDProperties met de waarde:

{
  "1": {
    "title": "Hello world!",
    "content": "Lorem ipsum."
  },
  "5": {
    "title": "Everything good?",
    "content": "Quisque convallis libero in sapien pharetra tincidunt."
  }
}

Voorwaardelijke uitvoering van operaties

Wanneer Multiple Query Execution is ingeschakeld, zijn directives @include en @skip ook beschikbaar als operatie-directives, en deze kunnen worden gebruikt om een operatie voorwaardelijk uit te voeren als aan een bepaalde voorwaarde is voldaan.

In deze query exporteert operatie CheckIfPostExists bijvoorbeeld een dynamische variabele $postExists en alleen als de waarde daarvan true is, wordt de mutatie ExecuteOnlyIfPostExists uitgevoerd:

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...
}

Waarden exporteren bij het itereren over een array of JSON-object

@export respecteert de kardinaliteit van eventuele omsluitende meta-directives.

In het bijzonder geldt dat wanneer @export is genest onder een meta-directive die itereert over array-items of JSON-objecteigenschappen (d.w.z. @underEachArrayItem en @underEachJSONObjectProperty), de geëxporteerde waarde een array is.

Deze query:

{
  post(by: { id: 19 }) {
    coreContentAttributeBlocks: blockFlattenedDataItems(
      filterBy: { include: "core/heading" }
    )
      @underEachArrayItem
        @underJSONObjectProperty(
          by: { path: "attributes.content" },
        )
          @export(
            as: "contentAttributes",
          )
  }
}

...produceert $contentAttributes met de waarde:

[
  "List Block",
  "Columns Block",
  "Columns inside Columns (nested inner blocks)",
  "Life is so rich",
  "Life is so dynamic"
]

Ter vergelijking: dezelfde query die een specifiek item in de array benadert in plaats van alle items te doorlopen (door @underEachArrayItem te vervangen door @underArrayItem(index: 0)) exporteert een enkele waarde.

Deze query:

{
  post(by: { id: 19 }) {
    coreContentAttributeBlocks: blockFlattenedDataItems(
      filterBy: { include: "core/heading" }
    )
      @underArrayItem(index: 0)
        @underJSONObjectProperty(
          by: { path: "attributes.content" },
        )
          @export(
            as: "contentAttributes",
          )
  }
}

...produceert $contentAttributes met de waarde:

"List Block"

Volgorde van directive-uitvoering

Als er andere directives voor @export staan, weerspiegelt de geëxporteerde waarde de wijzigingen die door die eerdere directives zijn aangebracht.

In deze query is het resultaat anders, afhankelijk van of @export plaatsvindt voor of na @strUpperCase:

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)
}

Dit geeft als resultaat:

{
  "data": {
    "id": "ROOT",
    "again": "ROOT",
    "mirrorID": "root",
    "mirrorAgain": "ROOT"
  }
}

Multi-Field Directives

Wanneer de functie Multi-Field Directives is ingeschakeld en je de waarde van meerdere velden exporteert naar een woordenboek, gebruik dan @deferredExport in plaats van @export om te garanderen dat alle directives van elk betrokken veld zijn uitgevoerd voordat de veldwaarde wordt geëxporteerd.

In deze query heeft het eerste veld de directive @strUpperCase en het tweede @titleCase. Bij het uitvoeren van @deferredExport zal de geëxporteerde waarde deze directives toegepast hebben:

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)
}

Dit geeft als resultaat:

{
  "data": {
    "id": "ROOT",
    "again": "Root",
    "mirrorProps": {
      "id": "ROOT",
      "again": "Root"
    }
  }
}

GraphQL-specificatie

Deze functionaliteit maakt momenteel geen deel uit van de GraphQL-specificatie, maar er zijn wel verzoeken ingediend: