クエリ > クエリのベストプラクティス

クエリのベストプラクティス

Reading time: 16 min

The Graphは、ブロックチェーンのデータをクエリするための分散化された方法を提供します。

The GraphのネットワークのデータはGraphQL APIで公開され、GraphQL言語によるデータクエリーが容易になります。

このページでは、GraphQLの言語ルールとGraphQLクエリのベストプラクティスに必要不可欠な情報をご案内しています。


GraphQL APIのクエリ

このセクションへのリンク

GraphQLクエリの構造

このセクションへのリンク

REST APIとは異なり、GraphQL APIは実行可能なクエリを定義するSchemaをベースに構築されています。

例えば、tokenクエリを使ってトークンを取得するクエリは次のようになります。

query GetToken($id: ID!) {
token(id: $id) {
id
owner
}
}

以下のような予測可能なJSONレスポンスが返ってきます(適切な$id変数値を渡す場合)。

{
"token": {
"id": "...",
"owner": "..."
}
}

GraphQLクエリは、仕様で定義されているGraphQL言語を使用します。

上記の GetToken クエリは、複数の言語部分で構成されています (以下では [...] プレースホルダーに置き換えられています)。

query [operationName]([variableName]: [variableType]) {
[queryName]([argumentName]: [variableName]) {
# "{ ... }" express a Selection-Set, we are querying fields from `queryName`.
[field]
[field]
}
}

構文の「やるべきこと」「やってはいけないこと」を挙げればきりがありませんが、ここではGraphQLクエリを書く際に覚えておきたい基本的なルールを紹介します:

  • queryNameは、1回の操作で1回だけ使用しなければなりません。
  • フィールドは、選択の中で一度だけ使用しなければなりません(トークンの下にidを二度照会することはできません)。
  • いくつかの field やクエリ (tokens など) は、サブフィールドの選択を必要とする複雑な型を返します。期待されたときに選択を提供しない(あるいは期待されていないときに提供する-たとえばidの場合)ことは、エラーを発生させます。フィールドタイプを知るには、The Graph Explorerを参照してください。
  • 引数に代入される変数は、その型と一致しなければなりません。
  • 与えられた変数のリストにおいて、各変数は一意でなければなりません。
  • 定義された変数はすべて使用する必要があります。

上記のルールに従わない場合、Graph APIからエラーが発生します。

ルールの完全なリストとコード例については、GraphQL Validationsガイドをご覧ください。

GraphQL APIへのクエリの送信

このセクションへのリンク

GraphQLは、HTTPを介して転送される言語と一連の規約です。

これは、標準のfetch(ネイティブであれば、@whatwg-node/fetchisomorphic-fetchを介しても)を使用して、GraphQL APIにクエリを送信できることを意味します。

ただし、「アプリケーションからのクエリ」で述べたように、以下のような固有の機能をサポートするgraph-clientを使用することをおすすめします。

graph-client を使用してグラフをクエリする方法は次のとおりです。

import { execute } from '../.graphclient'
const query = `
query GetToken($id: ID!) {
token(id: $id) {
id
owner
}
}
`
const variables = { id: '1' }
async function main() {
const result = await execute(query, variables)
// `result` is fully typed!
console.log(result)
}
main()

その他の GraphQL クライアントの代替手段については、「アプリケーションからのクエリ」 で説明します。

GraphQL クエリ構文の基本ルールを説明したので、今度は GraphQL クエリ記述のベスト プラクティスを見てみましょう。


GraphQLクエリの作成

このセクションへのリンク

常に静的なクエリを記述

このセクションへのリンク

一般的な (悪い) プラクティスは、次のようにクエリ文字列を動的に構築することです。

const id = params.id
const fields = ['id', 'owner']
const query = `
query GetToken {
token(id: ${id}) {
${fields.join('\n')}
}
}
`
// Execute query...

上記のスニペットは有効な GraphQL クエリを生成しますが、多くの欠点があります。

  • クエリ全体を理解するのが難しくなります
  • 開発者は、文字列補間を安全にサニタイズする責任があるということです。
  • リクエストパラメータの一部として変数の値を送信しないでください。サーバー側でのキャッシュの可能性を防止
  • それは ** ツールがクエリを静的に分析するのを防ぐ** (例: Linter、またはタイプ生成ツール) です。

このため、クエリは常に静的文字列として記述することをお勧めします:

import { execute } from 'your-favorite-graphql-client'
const id = params.id
const query = `
query GetToken($id: ID!) {
token(id: $id) {
id
owner
}
}
`
const result = await execute(query, {
variables: {
id,
},
})

そうすることで多くのメリットがもたらされます:

  • 読みやすく、メンテナンスしやすいクエリ
  • GraphQLのサーバーは、変数のサニタイズを処理します
  • サーバーレベルで変数がキャッシュできます
  • ツールでクエリを静的に分析できる(これについては、次のセクションで詳しく説明します。)

注: 静的クエリに条件付きでフィールドを含める方法

特定の条件でのみ owner フィールドを含めることができます。

このために、次のように @include(if:...) ディレクティブを利用できます:

import { execute } from 'your-favorite-graphql-client'
const id = params.id
const query = `
query GetToken($id: ID!, $includeOwner: Boolean) {
token(id: $id) {
id
owner @include(if: $includeOwner)
}
}
`
const result = await execute(query, {
variables: {
id,
includeOwner: true,
},
})

注: 反対のディレクティブは @skip(if: ...) です。

パフォーマンスに関するヒント

このセクションへのリンク

"欲しいものを聞いてください"

GraphQL は、「欲しいものを聞いてください」というキャッチフレーズで有名になりました。

このため、GraphQLでは、個々にリストすることなくすべての利用可能なフィールドを取得する方法はありません。

GraphQL APIをクエリする際には、実際に使用するフィールドのみをクエリするように常に考えてください。

過剰なデータ取得の一般的な原因は、エンティティのコレクションです。デフォルトでは、クエリはコレクション内のエンティティを100個取得しますが、通常、実際に使用される量(たとえば、ユーザーに表示される量)よりもはるかに多いです。そのため、クエリはほぼ常にfirstを明示的に設定し、実際に必要なだけのエンティティを取得するようにする必要があります。これは、クエリ内のトップレベルのコレクションだけでなく、さらにエンティティのネストされたコレクションにも当てはまります。

たとえば、次のクエリでは:

query listTokens {
tokens {
# will fetch up to 100 tokens
id
transactions {
# will fetch up to 100 transactions
id
}
}
}

応答には、100 個のトークンごとに 100 個のトランザクションが含まれる可能性があります。

アプリケーションが 10 トランザクションのみを必要とする場合、クエリではトランザクション フィールドに first: 10 を明示的に設定する必要があります。

複数のクエリを組み合わせる

アプリケーションでは、次のように複数の種類のデータをクエリする必要がある場合があります:

import { execute } from "your-favorite-graphql-client"
const tokensQuery = `
query GetTokens {
tokens(first: 50) {
id
owner
}
}
`
const countersQuery = `
query GetCounters {
counters {
id
value
}
}
`
const [tokens, counters] = Promise.all(
[
tokensQuery,
countersQuery,
].map(execute)
)

この実装は完全に有効ですが、GraphQL API を使用した 2 つの往復が必要になります。

幸いなことに、次のように同じ GraphQL リクエストで複数のクエリを送信することも有効です:

import { execute } from "your-favorite-graphql-client"
const query = `
query GetTokensandCounters {
tokens(first: 50) {
id
owner
}
counters {
id
value
}
}
`

このアプローチは、ネットワークに費やす時間を減少させる(APIへの往復を省略する)ため、全体的なパフォーマンスを向上させます。また、より簡潔な実装を提供します。

GraphQLフラグメントの活用

このセクションへのリンク

GraphQL クエリを作成するのに役立つ機能は、GraphQL Fragment です。

次のクエリを見ると、いくつかのフィールドが複数のSelection-Sets({ ... })で繰り返されていることがわかります:

query {
bondEvents {
id
newDelegate {
id
active
status
}
oldDelegate {
id
active
status
}
}
}

このような繰り返しフィールド (idactivestatus) は、多くの問題を引き起こします。

  • より広範囲なクエリに対応するために読みにくくなります
  • クエリに基づいて TypeScript 型を生成するツールを使用する場合 (前のセクションで詳しく説明します)、newDelegate および oldDelegate は、2 つの異なるインライン インターフェイスになります。

クエリのリファクタリングされたバージョンは次のようになります:

query {
bondEvents {
id
newDelegate {
...DelegateItem
}
oldDelegate {
...DelegateItem
}
}
}
# we define a fragment (subtype) on Transcoder
# to factorize repeated fields in the query
fragment DelegateItem on Transcoder {
id
active
status
}

GraphQLのfragmentを使用すると、可読性が向上します(特に大規模な場合)し、さらにはより良いTypeScriptの型生成にも結びつきます。

型生成ツールを使用すると、上記のクエリは適切なDelegateItemFragment型を生成します(最後の「ツール」セクションを参照)。

GraphQLフラグメントの注意点

このセクションへのリンク

フラグメントベースは型である必要があります

フラグメントは、適用できない型、つまりフィールドを持たない型に基づくことはできません。

fragment MyFragment on BigInt {
# ...
}

BigIntスカラー (ネイティブの「プレーン」タイプ) であり、フラグメントのベースとして使用できません。

フラグメントを拡散する方法

フラグメントは特定のタイプに定義されているため、クエリではそれに応じて使用する必要があります。

例:

query {
bondEvents {
id
newDelegate {
...VoteItem # Error! `VoteItem` cannot be spread on `Transcoder` type
}
oldDelegate {
...VoteItem
}
}
}
fragment VoteItem on Vote {
id
voter
}

newDelegateoldDelegate のタイプは Transcoder です。

ここでタイプ Vote のフラグメントを拡散することはできません。

フラグメントをデータのアトミックなビジネス単位として定義する

GraphQL フラグメントは、その使用法に基づいて定義する必要があります。

ほとんどのユースケースでは、1つのタイプに対して1つのフラグメントを定義すること(繰り返しフィールドの使用または型生成の場合)で十分です。

Fragment を使用する場合の経験則は次のとおりです:

  • 同じ型のフィールドがクエリ内で繰り返される場合、それらをFragmentでグループ化します。
  • 同じフィールドが繰り返される場合、複数のフラグメントを作成します。
# base fragment (mostly used in listing)
fragment Voter on Vote {
id
voter
}
# extended fragment (when querying a detailed view of a vote)
fragment VoteWithPoll on Vote {
id
voter
choiceID
poll {
id
proposal
}
}

GraphQL ウェブベースのエクスプローラ

このセクションへのリンク

クエリをアプリケーション内で実行して繰り返しテストするのは手間がかかる場合があります。そのため、クエリをアプリケーションに追加する前に、The Graph Explorerを使用してクエリをテストすることを躊躇しないでください。The Graph Explorerは、クエリをテストするための事前に設定されたGraphQLプレイグラウンドを提供します。

クエリをデバッグやテストするより柔軟な方法を探している場合、AltairGraphiQLなどの類似のWebベースのツールも利用できます。

上記で述べたベストプラクティスと構文ルールに従うためには、以下のワークフローとIDEツールを使用することを強くお勧めします。

GraphQL ESLint

GraphQL ESLint を使用すると、手間をかけずに GraphQL のベスト プラクティスを常に把握できるようになります。

「operations-recommended」 構成をセットアップすると、次のような重要なルールが適用されます。

  • @graphql-eslint/fields-on-correct-type: フィールドは適切なタイプで使用されているか?
  • @graphql-eslint/no-unused variables: 与えられた変数は未使用のままであるべきか?
  • ともっと

これにより、 プレイグラウンドでクエリをテストしたり、本番環境で実行したりせずにエラーをキャッチできる ようになります。

VSCodeとGraphQL

The GraphQL VSCode Extension is a great addition to your development workflow, allowing you to:

  • 構文の強調表示
  • オートコンプリートの提案
  • スキーマに対する検証
  • snippets
  • フラグメントと入力タイプの定義に移動

graphql-eslintを使用している場合、ESLint VSCode拡張機能はエラーや警告を正しくコード内に表示するために必須です。

WebStorm/Intellij および GraphQL

JS GraphQLプラグインは、以下を提供することで、GraphQLを使用する際のエクスペリエンスを大幅に向上させます。

  • 構文の強調表示
  • オートコンプリートの提案
  • スキーマに対する検証
  • snippets

詳細は、このWebStorm の記事で、プラグインの主な機能をすべて紹介しています。

ページを編集

API キーの管理
アプリケーションからのクエリ
ページを編集