スキーマの設定
スキーマの設定複数クエリの同時実行

複数クエリの同時実行

複数のクエリを組み合わせて単一の操作として実行し、それぞれの状態とデータを再利用できます。

これはクエリバッチング(query batching)とは異なります。クエリバッチングでもGraphQLサーバーは単一リクエストで複数のクエリを実行しますが、それらのクエリは互いに独立して順番に実行されるだけです。

この機能はパフォーマンスを向上させます。クエリを複数のリクエストで個別に実行する(最初にGraphQLサーバーに対して操作を実行し、レスポンスを待ち、その結果を使って別の操作を行う)のではなく、まとめて実行できるため、複数リクエストによるレイテンシーを回避できます。

Multiple Query Executionにより、GraphQLクエリをより適切に整理することもできます。互いに依存し、前の操作の結果に基づいて条件付きで実行される論理的な単位にクエリを分割できます。

複数クエリ実行の使い方

ログイン中のユーザー名を含む投稿をすべて検索したいとします。通常、これを実現するには2つのクエリが必要です。

まずユーザーの name を取得します:

query GetLoggedInUserName {
  me {
    name
  }
}

...そして最初のクエリを実行した後、取得したユーザーの name を変数 $search として渡し、2番目のクエリで検索を実行します:

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

Multiple Query Execution はこのプロセスを簡略化し、すべてのデータを取得して必要なロジックをすべて単一のリクエストで実行できるようにします:

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

Multiple Query Executionは、次の特殊なディレクティブを使用することで実現されます:

  • @depends(オペレーションディレクティブ):操作(query または mutation)が、先に実行される必要がある他の操作を示します
  • @export(フィールドディレクティブ):ある操作のフィールド値をエクスポートし、別の操作のフィールドへの入力として注入します
  • @deferredExport(フィールドディレクティブ):Multi-Field Directives と一緒に使用する場合の @export に似た動作をします

さらに、@include@skip もオペレーションディレクティブとして利用可能になります(通常はフィールドディレクティブのみ)。これらを使用して、ある条件を満たす場合に操作を条件付きで実行できます。

GraphQLサーバーは、各 @depends(on: ...) から取得して実行すべき操作のリストを作成し、@export を含むフィールドの値を動的変数(as 引数で定義された名前)としてエクスポートして、後続の操作への入力として使用します。

これらのディレクティブを組み合わせることで、複雑な機能を中間ステップに分割し、querymutation の操作を交互に行い、必要な順序で依存関係を追加し、最も外側の操作を ?operationName=... で定義することですべてを単一のリクエストで実行できます(上記の例では ?operationName=GetPostsContainingString となります)。

@depends による実行する操作の定義

GraphQLドキュメントに複数の操作が含まれる場合、URLパラメーター ?operationName=... でサーバーに実行する操作を指定します。指定しない場合は、最後の操作が実行されます。

この初期操作から始めて、サーバーはディレクティブ depends(on: [...]) を追加することで定義された実行すべきすべての操作を収集し、依存関係を考慮した対応する順序で実行します。

ディレクティブ引数 operations は操作名の配列([String])を受け取ります。単一の操作名(String)を指定することもできます。

このクエリでは ?operationName=Four を渡し、実行される操作(query または mutation)は ["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 ...
}

@export によるクエリ間のデータ共有

ディレクティブ @export は、フィールド(またはフィールドのセット)の値を動的変数にエクスポートし、別のクエリのフィールドへの入力として使用します。

例えば、このクエリではログイン中のユーザー名をエクスポートし、その値を使ってその文字列を含む投稿を検索します(変数 $loggedInUserName は動的変数のため、操作 FindPosts で定義する必要がないことに注目してください):

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

動的変数の出力

@export は、以下の組み合わせに基づいて6種類の異なる出力を生成できます:

  • type 引数の値(SINGLELIST、または DICTIONARY
  • ディレクティブが単一フィールドに適用されるか、複数フィールドに適用されるか(Multi-Field Directives モジュール経由)

6種類の可能な出力は次のとおりです:

  1. SINGLE タイプ:
    1. 単一フィールド
    2. マルチフィールド
  2. LIST タイプ:
    1. 単一フィールド
    2. マルチフィールド
  3. DICTIONARY タイプ:
    1. 単一フィールド
    2. マルチフィールド

SINGLE タイプ / 単一フィールド

パラメーター type: SINGLE(デフォルト値として設定)を渡すと、出力は単一の値になります。

このクエリでは:

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

...動的変数 $postTitle の値は次のようになります:

"Hello world!"

SINGLE がエンティティの配列に対して適用された場合、最後のエンティティの値がエクスポートされることに注意してください。

このクエリでは:

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

...動的変数 $postTitle はID 5 の投稿の値を持ちます:

"Everything good?"

SINGLE タイプ / マルチフィールド

@export が複数のフィールドに適用された場合(Multi-Field Directives モジュールが提供するパラメーター affectAdditionalFieldsUnderPos を追加することで)、動的変数に設定される値は { key: フィールドエイリアス, value: フィールド値 } の辞書(JSONObject 型)になります。

このクエリ:

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

...は動的変数 $postData を次の値でエクスポートします:

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

LIST タイプ / 単一フィールド

パラメーター type: LIST を渡すと、動的変数にはクエリされたすべてのエンティティ(内包フィールドから)のフィールド値を持つ配列が格納されます。

このクエリを実行すると(クエリされるエンティティはID 15 の投稿):

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

...動的変数 $postTitles の値は次のようになります:

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

LIST タイプ / マルチフィールド

ディレクティブが適用されたフィールドの値を含む辞書(JSONObject 型)の配列が得られます。

このクエリ:

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

...は動的変数 $postsData を次の値でエクスポートします:

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

DICTIONARY タイプ / 単一フィールド

パラメーター type: DICTIONARY を渡すと、動的変数にはクエリされたエンティティのIDをキー、フィールド値を値とする辞書(JSONObject 型)が格納されます。

このクエリ:

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

...は動的変数 $postIDTitles を次の値でエクスポートします:

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

DICTIONARY タイプ / マルチフィールド

この組み合わせでは、辞書の辞書をエクスポートします:{ key: エンティティID, value: { key: フィールドエイリアス, value: フィールド値 } }JSONObject 型のエントリを含む JSONObject 型を使用)。

このクエリ:

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

...は動的変数 $postsIDProperties を次の値でエクスポートします:

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

操作の条件付き実行

Multiple Query Executionが有効な場合、ディレクティブ @include@skip もオペレーションディレクティブとして利用可能になり、ある条件を満たす場合に操作を条件付きで実行するために使用できます。

例えば、このクエリでは操作 CheckIfPostExists が動的変数 $postExists をエクスポートし、その値が true の場合にのみ、ミューテーション 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...
}

配列またはJSONオブジェクトを反復処理する際の値のエクスポート

@export は、それを囲むメタディレクティブのカーディナリティを尊重します。

特に、@export が配列要素またはJSONオブジェクトプロパティを反復処理するメタディレクティブ(すなわち @underEachArrayItem@underEachJSONObjectProperty)の下にネストされている場合、エクスポートされる値は配列になります。

このクエリ:

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

...は $contentAttributes を次の値で生成します:

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

対照的に、すべての要素を反復処理する代わりに配列内の特定のアイテムにアクセスする同じクエリ(@underEachArrayItem@underArrayItem(index: 0) に置き換えることで)は、単一の値をエクスポートします。

このクエリ:

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

...は $contentAttributes を次の値で生成します:

"List Block"

ディレクティブの実行順序

@export の前に他のディレクティブがある場合、エクスポートされる値はそれらの前のディレクティブによる変更を反映します。

例えば、このクエリでは @export@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)
}

生成される結果:

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

Multi-Field Directives

Multi-Field Directives 機能が有効で、複数フィールドの値を辞書にエクスポートする場合、フィールドの値をエクスポートする前に関係するすべてのフィールドのすべてのディレクティブが実行されたことを保証するために、@export の代わりに @deferredExport を使用してください。

例えば、このクエリでは最初のフィールドにディレクティブ @strUpperCase が適用され、2番目には @titleCase が適用されています。@deferredExport を実行すると、エクスポートされる値にはこれらのディレクティブが適用されます:

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

生成される結果:

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

GraphQL仕様

この機能は現在GraphQL仕様の一部ではありませんが、要望が寄せられています: