検証された後、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は非同期値を操作するために使用されますが、同じ概念は多くの言語に存在し、多くの場合、Future、Task、または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
フィールドが解決されている間、appearsIn
とstarships
フィールドは同時に解決できます。appearsIn
フィールドにも単純なレゾルバーを使用できますが、詳しく見てみましょう。
Human: { appearsIn(obj) { return obj.appearsIn // returns [ 4, 5, 6 ] }}
型システムはappearsIn
が既知の値を持つEnum値を返すことを主張していますが、この関数は数値を返しています。!実際、結果を見ると、適切なEnum値が返されていることがわかります。何が起こっているのでしょうか?
これは、スカラーの強制変換の例です。型システムは期待するものを知っており、レゾルバー関数が返す値をAPI契約を維持する何かに変換します。この場合、サーバーには4
、5
、6
などの数値を内部的に使用しますが、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として)送信できます。
これらのすべての解決関数がどのように結果を生成するかを確認するために、元のクエリをもう一度見てみましょう。