GraphQL.js を直接使って Subscription を定義してみる
(=˘ ꒳ ˘=) Apollo が目につきやすい GraphQL 界隈だけどやっぱり GraphQL の生の鼓動を感じたい...
の続きです。GraphQL.js のコードの中に Subscription についての処理が書かれているのを見つけたので、今回は subscribe
メソッドの振る舞いを簡単に確認していきたいと思います。
TypeScript の型定義を覗いてみる
先にフィールド定義から見てみます。Query ではフィールドの値を定義するために各フィールドの resolve
メソッドを定義しましたが Subscription では subscribe
メソッドを定義するようです。GraphQLFieldConfig
オブジェクトによると resolve
と subscribe
が同じ型であることがわかりますね。
// @types/graphql/type/definition.d.ts export interface GraphQLFieldConfig< TSource, TContext, TArgs = { [argName: string]: any } > { type: GraphQLOutputType; args?: GraphQLFieldConfigArgumentMap; resolve?: GraphQLFieldResolver<TSource, TContext, TArgs>; subscribe?: GraphQLFieldResolver<TSource, TContext, TArgs>; deprecationReason?: string; description?: string; astNode?: FieldDefinitionNode; }
そう言えば query
と mutation
もデータ変更の有無で使い分けているだけですし subscription
もそれと同様、ということなのかもしれませんね。
次に実行メソッドです。Query は graphql
で実行しましたが Subscription は subscribe
メソッドで実行するようです。こちらも型定義から引数を見てみます。
// @types/graphql/subscription/subscribe.d.ts export function subscribe( schema: GraphQLSchema, document: DocumentNode, rootValue?: any, contextValue?: any, variableValues?: { [key: string]: any; }, operationName?: string, fieldResolver?: GraphQLFieldResolver<any, any>, subscribeFieldResolver?: GraphQLFieldResolver<any, any>, ): Promise<AsyncIterator<ExecutionResult> | ExecutionResult>;
graphql
メソッドと似ていますが source
だったところが document
になっていますね。型定義を追いかけたとろこ DocumentNode
は GraphQL Schema Language をパースしたAST(抽象構文木)を表しているようで、parse
メソッドを利用することで GraphQL Schema Language の文字列から生成することができます。
graphql
メソッドは文字列の queryString
をそのまま実行できたのですが微妙なところに違いがありますね。
実行コードを書いてみる
とりあえず subscribe
を実行することでフィールド定義の subscribe
が実行されるところまでを確認したいので以下のコードを書きました。
import { subscribe, parse, GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql' const schema = new GraphQLSchema({ // query は省略不可みたいなので適当に定義 query: new GraphQLObjectType({ name: 'RootQueryType', fields: { dummy: { type: GraphQLString }, }, }), // とりあえず subscribe の呼ばれ具合を確認するだけの定義 subscription: new GraphQLObjectType({ name: 'RootSubscriptionType', fields: { somethingUpdated: { type: GraphQLString, args: { value: { type: GraphQLString }, }, subscribe(src, args, ctx) { console.log('Given args:', src, args, ctx) // 実行時に渡る引数を確認 }, }, }, }), }) const requestString = ` subscription SomethingUpdated($value: String) { somethingUpdated(value: $value) } ` // 怒涛の引数たち (*˘꒳˘*) めげずにがんばる... const document = parse(requestString) const rootValue = { value: 'in rootValue'} const context = { value: 'in context' } const variables = { value: 'in variables' } const operationName = 'SomethingUpdated' subscribe(schema, document, rootValue, context, variables, operationName).then(console.log, console.error)
これを実行したところ、フィールド定義の subscribe
に rootValue
variables
context
の値が渡っていることが確認できました。
Given args: { value: 'in rootValue' } { value: 'in variables' } { value: 'in context' } Error: Subscription field must return Async Iterable. Received: undefined
そしてエラーメッセージによると subscribe
は Async Iterable を返す必要があるみたいです。AsyncIterator については以前 graphql-subscription を使ってみる - まず PubSub って何 - きみはねこみたいなにゃんにゃんなまほう
で確認済みで、for..await..of
構文を使うことで非同期に延々とエントリを処理することができるイテレータでしたね。Async Iterable とは for..await..of
の of..
の部分に渡せるオブジェクト、ということになりそうです。
つまり GraphQL.js が提供するsubscribe
メソッドは非同期なデータパッシングを行うためのインタフェースとしてAsync Iterable を返している、ということですね。
なのでここを見る限り GraphQL.js のスタンスとしては、インタフェースは定義したのであとは利用者が subscribe
中に Async Iterable なオブジェクトを用意するなりして適当に非同期処理を実装しなよ、と言っているように見えます。
ということで... Async Iterable についての理解をまだ深めていない私にはこれ以上の理解はすぐにはできなさそうなので、今回は一旦ここまでとします。
兎にも角にも鍵は Async Iterator ですね。
雑感: node_modules 下を見ていて
最近 GitHub のソースをブラウジングするよりエディタの定義ジャンプを利用した方が断然早いということに遅まきながら気がつきまして、node_modules 内をふらつく頻度が格段に増えました。
Apollo は TypeScript で GraphQL.org は Flow
Apollo は TypeScript で作られているのに対して、GraphQL.js は Flow で書かれているんですね。TypeScript を使いたければ @types/graphql を使うという。 結局 TypeScript/Flow どちらも知らなければ生きづらい世の中になってしまっていますね。
そしてこれもどうでもいいことですが、たまにエディタが Flow で型情報をもってきているのか TypeScript でもってきているのかわからなくなります。
しかしドキュメントを読まなくてもある程度コードが書き進められるので型情報とは良いものです。
Flow の型定義ファイルが .js.flow になっている
node_modules/graphql を見ていて型定義が .js.flow
ちょっと感動しました。これでもうあの「あれ、コピペしたけど動かない、何これ、JavaScript の新しい構文なの...?」みたいな悲劇は起こらない...
この毎年構文が追加される時代にあって .js
に拡張構文をしれっと紛れ込ませてくる facebook のやり方はちょっと辛いところがあります。
いえ、でも、やり方はどうあれ技術は好きであります。GraphQL とか React とか。