きみはねこみたいなにゃんにゃんなまほう

ねこもスクリプトをかくなり

GraphQL の Introspection について - schema.json って何だろう

(=˘ ꒳ ˘=) GraphQL を使っているとよく schema.json などと名付けられた JSON 形式のファイルを利用している例に突き当たる...

この schema.json ってなんだろうというお話。

schema.json - Apollo の場合

例えば apollo-codegen の使い方を見てみると

// schema.json 生成
apollo-codegen introspect-schema http://localhost:8080/graphql --output schema.json

// schema.json から型定義を生成
apollo-codegen generate **/*.graphql --schema schema.json --output API.swift

のように introspection で得られる値が schema.json ということらしい。

GraphQL の Introspection

Introspection について Working Draft を見ると

__schema: __Schema!
__type(name: String!): __Type

で root query から得られるものとある。__Schema 定義は以下の通り。

type __Schema {
  types: [__Type!]!
  queryType: __Type!
  mutationType: __Type
  directives: [__Directive!]!
}

__Type の中身は GraphQL.js の TypeScript 等の型情報を眺めたことのある人なら「なるほど」と読めるような内容で定義されている。

Standard Introspection Query

apollo-codegen は introspection に

import { introspectionQuery } from 'graphql/utilities'

を利用している。このutilities/introspectionQuery.js には __schema で得られる introspection 情報を完全に得るためのフィールド定義が延々と書かれている。初めて見た時は目を疑った。

Mocking | GraphQL Tools ではこのクエリは Standard Introspection Query と呼ばれている。しかし Working Draft には見つからなかった。

試しに graphqlintrospectionQuery を実行してみる。

import { graphql, buildSchema, introspectionQuery } from 'graphql'

const schema = buildSchema(`
  type Query {
    hello: String!
  }
`)

graphql(schema, introspectionQuery).then(console.log)

とすると schema.json でよく見る形式のデータが帰ってくる。

{ data:
   { __schema:
      { queryType: [Object],
        mutationType: null,
        subscriptionType: null,
        types: [Array],
        directives: [Array] } } }

これをさらに buildClientSchemaGraphQLSchema オブジェクトに戻すことができる。

graphql(schema, introspectionQuery).then(res => {
  const clientSchema = buildClientSchema(res.data)
  console.log(clientSchema)
})

ドキュメントによると

Build a GraphQLSchema for use by client tools. Given the result of a client running the introspection query, creates and returns a GraphQLSchema instance which can be then used with all GraphQL.js tools, but cannot be used to execute a query, as introspection does not represent the "resolver", "parse" or "serialize" functions or any other server-internal mechanisms.

つまり introspection query の結果を渡すことでクライアントやツール類が利用するための GraphQLSchema オブジェクトを構成するユーティリティメソッドらしい。Introspection に resolver などの関数の情報が含まれていないのでこのインスタンスはサーバ側で値を返すために利用することはできない。

GraphQL スキーマの表現形式の比較

ここまで GraphQL 周辺を触ってきて、いろいろなスキーマの表現形式を見かけてきた。

GraphQL Schema Language の DSL を利用したスキーマ表現。JSON形式のスキーマ表現。これは introspection result とも dump と呼ばれることも多い気がする。それから GraphQLSchema オブジェクトで表される JavaScript コード中の表現。折角なので比較してみよう。

GraphQL Schema Language (.graphql)

  • description やカスタムディレクティブ定義など一部の表現をサポートしていない
  • 人が読み書きしやすい

GraphQL.js の GraphQLSchema

  • 参照実装だけあっておそらく仕様の全てをカバーしている形式だろう
  • シリアライズ・デシリアライズ処理など実行に関わる定義も含まれている

Introspection Result (.json)

  • Introspection の結果をそのまま JSON として吐き出したもの
    • 大体は Standard Introspection Query の結果
    • Apollo の場合は Unio 中の Fragment の型解決のために部分的な定義を利用していたこともあった(参考)
  • 雰囲気としてはGraphQLSchema から JSON として表現できない処理を除いたあらゆる情報が含まれている形式
  • 情報としては完全なものに近いが、しかし人が読み書きするものではない

どれも一長一短だが、ワークフロー中のスキーマ取得手法として「APIサーバからの型情報取得」という手を採用するのなら現状 GraphQL Schema Language では表現しきれない部分も多いため Introspection Result を型情報のソースとしておいておくのは妥当に思える。

Swagger や同様、スキーマというものはそれ単体では機能しない。必ず「ワークフロー」と呼ばれるスキーマ共有・更新の共有を行うための仕組みが必要となる。GraphQL の場合は Introspection の仕組みがそれに該当するが、Custom Scalar をサーバ・フロントでどう共有するかなど、まだ掴みきれていない部分も多い。ライブラリとしてリポジトリ管理し両サイドで共有するのがいいか、それとも他に何か良い方法があるのか...