GraphQLロゴGraphQL

実行

検証された後、GraphQLクエリはGraphQLサーバーによって実行され、要求されたクエリの形状を反映した結果(通常はJSON)が返されます。

GraphQLは、型システムなしではクエリを実行できません。クエリの例を説明するために、型システムの例を使用しましょう。これは、これらの記事の例全体で使用されているのと同じ型システムの一部です。

type Query {
human(id: ID!): Human
}
type Human {
name: String
appearsIn: [Episode]
starships: [Starship]
}
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
type Starship {
name: String
}

クエリの実行時に何が起こるかを説明するために、例を通して見ていきましょう。

GraphQLクエリ内の各フィールドは、前の型の関数またはメソッドと考えることができ、次の型を返します。実際、これがGraphQLの動作です。各型の各フィールドは、レゾルバーと呼ばれる関数によって裏付けられており、これはGraphQLサーバー開発者によって提供されます。フィールドが実行されると、対応するレゾルバーが呼び出され、次の値が生成されます。

フィールドが文字列や数値などのスカラー値を生成する場合、実行は完了します。ただし、フィールドがオブジェクト値を生成する場合、クエリにはそのオブジェクトに適用されるフィールドの別の選択が含まれます。これは、スカラー値に達するまで続きます。GraphQLクエリは常にスカラー値で終了します。

ルートフィールドとレゾルバー#

すべてのGraphQLサーバーの最上位には、GraphQL APIへのすべての可能なエントリポイントを表す型があり、これは多くの場合、ルート型またはクエリ型と呼ばれます。

この例では、クエリ型はid引数を受け入れるhumanというフィールドを提供します。このフィールドのレゾルバー関数は、おそらくデータベースにアクセスし、Humanオブジェクトを作成して返します。

Query: {
human(obj, args, context, info) {
return context.db.loadHumanByID(args.id).then(
userData => new Human(userData)
)
}
}

この例はJavaScriptで記述されていますが、GraphQLサーバーは多くの異なる言語で構築できます。レゾルバー関数は4つの引数を受け取ります。

  • obj 前のオブジェクト。ルートクエリ型のフィールドの場合、多くの場合使用されません。
  • args GraphQLクエリでフィールドに提供された引数。
  • context すべてのレゾルバーに提供され、現在ログインしているユーザーやデータベースへのアクセスなど、重要なコンテキスト情報を保持する値。
  • info 現在のクエリに関するフィールド固有の情報とスキーマの詳細を保持する値。詳細はGraphQLResolveInfo型も参照してください。

非同期レゾルバー#

このレゾルバー関数で何が起こっているかを詳しく見てみましょう。

human(obj, args, context, info) {
return context.db.loadHumanByID(args.id).then(
userData => new Human(userData)
)
}

contextは、GraphQLクエリで引数として提供されたidによってユーザーのデータを読み込むために使用されるデータベースへのアクセスを提供するために使用されます。データベースからの読み込みは非同期操作であるため、Promiseを返します。JavaScriptでは、Promiseは非同期値を操作するために使用されますが、同じ概念は多くの言語に存在し、多くの場合、FutureTask、またはDeferredと呼ばれます。データベースが返されると、新しいHumanオブジェクトを作成して返すことができます。

レゾルバー関数はPromiseを認識する必要がある一方で、GraphQLクエリは認識する必要がないことに注意してください。単にhumanフィールドが何かを返し、その後nameを要求することを期待します。実行中、GraphQLはPromise、Future、Taskが完了するのを待つまで継続し、最適な並行性で実行します。

単純なレゾルバー#

これでHumanオブジェクトが利用可能になったため、GraphQLの実行は、そのオブジェクトで要求されたフィールドを続行できます。

Human: {
name(obj, args, context, info) {
return obj.name
}
}

GraphQLサーバーは、次に何をするかを決定するために使用される型システムによって駆動されます。humanフィールドが何かを返す前でも、型システムは、humanフィールドがHumanを返すため、次のステップはHuman型でフィールドを解決することであることをGraphQLに伝えます。

この場合、名前の解決は非常に簡単です。名前レゾルバー関数が呼び出され、obj引数は前のフィールドから返されたnew Humanオブジェクトです。この場合、Humanオブジェクトにnameプロパティがあり、それを直接読み取って返すことを期待します。

実際、多くのGraphQLライブラリでは、このような単純なレゾルバーを省略し、フィールドにレゾルバーが提供されていない場合、同じ名前のプロパティを読み取って返すことを想定しています。

スカラーの強制変換#

nameフィールドが解決されている間、appearsInstarshipsフィールドは同時に解決できます。appearsInフィールドにも単純なレゾルバーを使用できますが、詳しく見てみましょう。

Human: {
appearsIn(obj) {
return obj.appearsIn // returns [ 4, 5, 6 ]
}
}

型システムはappearsInが既知の値を持つEnum値を返すことを主張していますが、この関数は数値を返しています。!実際、結果を見ると、適切なEnum値が返されていることがわかります。何が起こっているのでしょうか?

これは、スカラーの強制変換の例です。型システムは期待するものを知っており、レゾルバー関数が返す値をAPI契約を維持する何かに変換します。この場合、サーバーには456などの数値を内部的に使用しますが、GraphQL型システムではEnum値として表すEnumが定義されている場合があります。

リストレゾルバー#

上記のappearsInフィールドで、フィールドがもののリストを返す場合に何が起こるかを既に少し見てきました。Enum値のリストを返し、型システムがそれを期待していたため、リスト内の各アイテムは適切なEnum値に強制変換されました。starshipsフィールドが解決された場合はどうなるでしょうか?

Human: {
starships(obj, args, context, info) {
return obj.starshipIDs.map(
id => context.db.loadStarshipByID(id).then(
shipData => new Starship(shipData)
)
)
}
}

このフィールドのレゾルバーは、Promiseを返すだけでなく、Promiseのリストを返します。Humanオブジェクトには、操縦したStarshipsのIDのリストが含まれていましたが、実際のStarshipオブジェクトを取得するには、それらのすべてのIDを読み込む必要があります。

GraphQLは、これらのPromiseをすべて同時に待つまで続行せず、オブジェクトのリストが残ると、これらのアイテムごとにnameフィールドを読み込むために再び同時に続行します。

結果の生成#

各フィールドが解決されると、結果の値は、フィールド名(またはエイリアス)をキーとして、解決された値を値として持つキーバリューマップに配置されます。これは、クエリの最下位のリーフフィールドから、ルートクエリ型の元のフィールドまで続きます。これらはまとめて、元のクエリを反映した構造を生成し、それをリクエストしたクライアントに(通常はJSONとして)送信できます。

これらのすべての解決関数がどのように結果を生成するかを確認するために、元のクエリをもう一度見てみましょう。

読み続ける →イントロスペクション