LoginSignup
0

posted at

updated at

GraphQL: 実行

本稿は公式サイト「Execution」にもとづく、GraphQLの構文検証の考え方についての解説です。ドキュメントの邦訳ではなく、日本語で説明し直しました。原文から省いた部分もあり、逆にわかりにくいところは補っています。なお、GraphQL公式サイトのコード例は、インタラクティブな環境です。コードを書き替えて結果が確かめられますので、ぜひ試してみてください。

GraphQLクエリは検証されたあと、GraphQLサーバーにより実行されます。返されるのはクエリの形状(shape)にしたがった結果で、通常はJSONです。

GraphQLは型システムなしではクエリを実行できません。クエリの実行を説明するために、つぎのような型システムの例を使いましょう。

GraphQL
type Query {
	human(id: ID!): Human
}

type Human {
	name: String
	appearsIn: [Episode]
	starships: [Starship]
}

enum Episode {
	NEWHOPE
	EMPIRE
	JEDI
}

type Starship {
	name: String
}

ルートフィールドとリゾルバ(resolver)

GraphQLサーバーの最上位にあるのは、GraphQL APIへのすべてのエントリーポイントを示す型です。ルート型あるいはクエリ型と呼ばれます。

前掲の例では、クエリ型はhumanというフィールドを提供し、idという引数を受け取ります。このフィールドに対して、データベースにアクセスし、Humanオブジェクトを構築して返すのがリゾルバ(resolver)関数です。

GraphQL
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 - スキーマの詳細と同じく、現在のクエリにかかわるフィールド固有の情報をもつ値。

非同期リゾルバ

リゾルバ関数で何が行われているのかをご説明します。

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

データベースへのアクセスを得るために用いられるのがcontextです。GraphQLクエリの引数として渡されたidにより、データベースからユーザーのデータがロードされます。データベースからの読み込みは非同期処理です。そのため、JavaScriptでは非同期の値を扱うPromiseが返ります。同じ考えは多くの言語にあり、FuturesTasksDeferredなどとも呼ばれる機能です。データベースから戻ってきたら、新しいHumanオブジェクトを構築して返すことができます。

リゾルバ関数はPromiseを認識していなければなりません。GraphQLクエリはそうではないことにご注意ください。ただ、humanフィールドが何かを返すとみなし、ただnameを問うだけです。実行中、GraphQLはPromiseFuturesTasksが完了するのを待ちます。そのあと、最適な並行性で実行されるのです。

単純なリゾルバ

Humanオブジェクトが使えるようになると、GraphQLは要求されたフィールドで実行を続けられます。

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

GraphQLサーバーは、型システムの支えにより、つぎに何をすべきか決めるのです。Humanフィールドがまだ何も返さなくても、GraphQLはつぎにやることはHuman型のフィールドの解決だとわかります。型システムから、humanフィールドが返すのはHumanだと知らされているからです。

ここでは、nameの解決はとても簡単です。nameリゾルバ関数が呼び出されると、引数objnew Humanオブジェクトを受け取ります。これは、前のフィールドから返されたオブジェクトです。そして、Humanオブジェクトはnameプロパティをもち、直接読み込んで返すことができるとわかります。

実際、多くのGraphQLライブラリでは、このような単純なリゾルバは省いてしまえます。フィールドにリゾルバが与えられていない場合、同じ名前のプロパティが読み込まれて返されると仮定すればよいからです。

スカラー強制(coercion)

nameフィールドが解決される間に、appearsInstarshipsのふたつのフィールドも同時に解決できます。ただ、appearsInフィールドは簡単なリゾルバをもっているかもしれません。

GraphQL
Human: {
	appearsIn(obj) {
		return obj.appearsIn // 戻り値: [ 4, 5, 6 ]
	}
}

はじめに掲げた型システムによれば、appearsInはあらかじめ決められた値を返す列挙型のリストです。ところが、リゾルバ関数の戻り値のリストは数値を要素としています。たしかに結果を見ると、返されるのは正しい列挙型の値です。

これはスカラー強制(coercion)と呼ばれる機能の例です。型システムは何が期待されているかを知っています。そこで、リゾルバ関数から返された値を APIの規約にしたがって変換するのです。サーバーで列挙型が定められていれば、内部的には4、5、6などの数字を用いていても、GraphQLの型システムは列挙型の値として表します。

リストリゾルバ

リストを返すフィールドの扱いは、前項のappearsInで垣間見ました。返されるのは列挙型のリストだと型システムが認識したので、各項目は強制的に適切な列挙型値に変換されたのです。今度は、やはりリストが返されるstarshipsフィールドを採り上げましょう。

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

このフィールドのリゾルバが返すのはPromise単体ではなく、Promiseのリストです。Humanオブジェクトは、操縦する宇宙船のidのリストをもちます。けれど、それらは実際のStarshipオブジェクトを得るために必要なのです。

GraphQLはこれらのPromiseをすべて同時に待ち、そのあと処理を続けます。そして、リストに残ったオブジェクトがあればふたたび同時に処理を続け、各項目のnameフィールドを読み込むのです。

結果の生成

各フィールドが解決されると、結果の値はキー-値のマップに配置されます。キーはフィールド名(またはエイリアス)、解決された値が値です。クエリの最下層の末端フィールドから、ルートのクエリ型の大もとのフィールドまでずっと続きます。これらをまとめたのが、もとのクエリを反映した構造です。要求したクライアントに(通常はJSONとして)送信できるようになります。

クエリからリゾルバ関数が、どのように結果を生成するか確かめましょう。

GraphQL
{
	human(id: 1002) {
		name
		appearsIn
		starships {
			name
		}
	}
}
結果
{
	"data": {
		"human": {
			"name": "Han Solo",
			"appearsIn": [
				"NEWHOPE",
				"EMPIRE",
				"JEDI"
			],
			"starships": [
				{
					"name": "Millenium Falcon"
				},
				{
					"name": "Imperial shuttle"
				}
			]
		}
	}
}

シリーズGraphQLの基本

GraphQL: クエリ(queries)と変更(mutations)
GraphQL: スキーマと型
GraphQL: 検証(validation)
「GraphQL: 実行」
GraphQL: イントロスペクション(introspection)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0