GraphQL の主要ライブラリと resolver、schema、rootValue についてざらっと眺めてみる
(=˘ ꒳ ˘=) resolver
と rootValue
って何が違うんだろう... makeExecutableSchema
って何なんだろう...
...な記憶も新しく、むしろ現在進行形で混乱中ですが、めげずに公式サイト等眺めながら理解を進めていきたいと思います。
GraphQL 系パッケージまとめ
まず、前提知識の整理として主な GraphQL 系のパッケージをまとめていきたいと思います。
GraphQLのHTTPサーバ用ミドルウェアについては http://lightbulbcat.hatenablog.com/entry/2018/01/26/014846 でまとめております。
- graphql
- Facebook 製の参照実装かつデファクトスタンダードな GraphQL のコアライブラリです
GraphQLSchema
,GraphQLObjectType
などの基本的なデータ構造を提供します- buildSchema などのユーティリティが含まれます
- 以下、小文字の graphql はこのパッケージを指すものとします
- graphql-tools
- Apollo 製のユーティリティです
makeExecutableSchema
が含まれます- テスト用のモックを生成できたりもします
このあたり、コードを書いていると、どのメソッドがどちらのパッケージに属しているかで混乱したりします。また graphql-tools を使わない実装例も存在するので、実装の方法が複数あるというところでも混乱した記憶があります。
GraphQLSchema を作成する主な方法
schema としてサーバのミドルウェアに渡すようなオブジェクトは graphql で定義された GraphQLSchema
というクラスのオブジェクトらしいのですが、それを生成する手段として、現状よく見かけるのは
- graphql の
buildSchema
を使う - graphql-tools の
makeExecutableSchema
を使う new GraphQLSchema
でゴリゴリ書く
の3通りですね。それぞれ軽く見ていきましょう。
apollo-tools のお作法にのっとり、以下の例では GraphQL Schema Language で書かれたスキーマ定義文字列が typeDefs
としてスコープ内に定義されているものとしています。
graphql: buildSchema
const schema = buildSchema(typeDefs)
graphql-tools: makeExecutableSchema
const schema = makeExecutableSchema({ typeDefs, resolvers });
graphql: GraphQLObjectType
const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', fields: { hello: { type: GraphQLString, resolve() { return 'world'; } } } }) });
こうしてみると対応箇所が見えてきて、気付きや疑問点が湧いてきます。
GraphQLSchema
に含まれるGraphQLObjectType
の各フィールドはそれぞれresolver
を持つmakeExecutableSchema
についての疑問(主にソースコードが追いきれず...)- 生成される schema は
GraphQLSchema
と互換性のあるものなのか resolvers
は各対応するGraphQLObjectType
に分配されるようなイメージであっているか- 上の仮定が正しければ
makeExecutableSchema
の立ち位置は、生成されるGraphQLSchema
以下のGraphQLObjectType
にresolver
を注入することのできるbuildSchema
と理解できますね
- 生成される schema は
graphql の値解決の仕組みと rootValue
http://graphql.org/graphql-js/#writing-code にある通り graphql はクエリに対する値を解決するために root
と呼ばれる値を利用できます。
graphql(schema, '{ hello }', root).then((response) => { console.log(response); });
ここで出てくる root
というのがミドルウェアに渡している rootValue
相当のものだと思います。
graphql の resolver 定義
graphql-tools の makeExecutableSchema
を利用したことのある方なら見覚えがあると思いますが、各フィールドに対するリゾルバは graphql のドキュメント にある通り、以下のような形をしています。
type GraphQLFieldResolveFn = ( source?: any, args?: {[argName: string]: any}, context?: any, info?: GraphQLResolveInfo ) => any
この source
という引数には自身の直上の階層のオブジェクトが与えられ、例えば User 型のオブジェクトのフィールドの resolver
として定義されていれば親の User オブジェクトとなります。
graphql の defaultFieldResolver
ソースコードを追いかけてみるとデフォルトで使用される resolver
では source
に対して自身のフィールド名で参照している雰囲気でした。
export const defaultFieldResolver: GraphQLFieldResolver<any, *> = function( source, args, context, info, ) { // ensure source is a value for which property access is acceptable. if (typeof source === 'object' || typeof source === 'function') { const property = source[info.fieldName]; if (typeof property === 'function') { return source[info.fieldName](args, context, info); } return property; } };
graphql/graphql-js/src/execution/execute.js
まとめ
ここまで出てきた材料から推測するに、各クエリ実行時の graphql の値解決は...
- 実行時に与えられた
rootValue
に対して、クエリに従って各フィールドを求めていく - 各フィールドは
source
(自身の直上のオブジェクト)を参照しながらresolver
により自身の値を決定する- デフォルトの
resolver
の定義はsource[自身のフィールド名]
- これは
resolver
の定義により自由に振舞を変えられる
- デフォルトの
- そしてその
resolver
が定義されたGraphQLSchema
を作るには...- graphql-tools の
makeExecutableSchema
を利用すると便利 - graphql の
GraphQLObjectType
を頑張って使う方法もある - graphql の
buildSchema
では各フィールドへのresolver
の定義は不可能
- graphql-tools の
推測の域を出ない箇所もありますが、調べれば調べるほど宿題が増えていくので、今回はここまでの仮定を立てられただけでも良しとします。
間違いなどありましたら、ご指摘いただけるとありがたいです。