本稿は公式サイト「Introspection」にもとづく、GraphQLのイントロスペクションの仕組みについての解説です。ドキュメントの邦訳ではなく、日本語で説明し直しました。原文から省いた部分もあり、逆にわかりにくいところは補っています。なお、GraphQL公式サイトのコード例は、インタラクティブな環境です。コードを書き替えて結果が確かめられますので、ぜひ試してみてください。
GraphQLスキーマがどのようなクエリをサポートしているのか、情報を求めると役立つことが少なくありません。GraphQLでは、イントロスペクションの仕組みを用いて行うことができます。
スターウォーズの例では、ファイルstarWarsIntrospection-test.ts
にイントロスペクションシステムを示す数多くのクエリが含まれています。参照が実装されたイントロスペクションシステムを試すために実行するテストファイルです。
型システムの設計者は、どのような型が使えるのか知っています。そうでない場合にも、ルートのクエリ型からいつでも得られる__schema
フィールドを照会して、GraphQLに問い合わせられるのです。では、どのような型が使えるのか、尋ねてみましょう。
{
__schema {
types {
name
}
}
}
{
"data": {
"__schema": {
"types": [
{
"name": "Query"
},
{
"name": "String"
},
{
"name": "ID"
},
{
"name": "Mutation"
},
{
"name": "Episode"
},
{
"name": "Character"
},
{
"name": "Int"
},
{
"name": "LengthUnit"
},
{
"name": "Human"
},
{
"name": "Float"
},
{
"name": "Droid"
},
{
"name": "FriendsConnection"
},
{
"name": "FriendsEdge"
},
{
"name": "PageInfo"
},
{
"name": "Boolean"
},
{
"name": "Review"
},
{
"name": "ReviewInput"
},
{
"name": "Starship"
},
{
"name": "SearchResult"
},
{
"name": "__Schema"
},
{
"name": "__Type"
},
{
"name": "__TypeKind"
},
{
"name": "__Field"
},
{
"name": "__InputValue"
},
{
"name": "__EnumValue"
},
{
"name": "__Directive"
},
{
"name": "__DirectiveLocation"
}
]
}
}
}
かなりの数の型がありました。3つに分類して、ご説明しましょう。
-
Query
/Character
/Human
/Episode
/Droid
- 型システムで定義された型。 -
String
/Boolean
- 型システムに備わる組み込みのスカラー。 -
__Schema
/__Type
/__TypeKind
/__Field
/__InputValue
/__EnumValue
/__Directive
- 先頭のふたつのアンダースコア(__
)は、イントロスペクションシステムの一部であることを表す。
では、どのようなクエリがあるのか、調べることから始めましょう。型システムを設計するとき、すべてのクエリの始まる型が指定されました。イントロスペクションシステムに尋ねてみます。
{
__schema {
queryType {
name
}
}
}
{
"data": {
"__schema": {
"queryType": {
"name": "Query"
}
}
}
}
この結果は記事「GraphQL: スキーマと型」で型システムについてご説明したとき、Query
型がエントリポイントを定めると述べたことに一致します(「Query型とMutation型」参照)。Query
型というのは、あくまで慣習上の名前です。他の名前をつけても構いません。クエリのはじまりの型として指定したので返されました。
特定の型について確かめたいこともあるでしょう。たとえば、Droid
型の場合です。
{
__type(name: "Droid") {
name
}
}
{
"data": {
"__type": {
"name": "Droid"
}
}
}
もっとも、クエリの引数にname
を渡したのですから、すでに値はわかっています。ほかに知りたいことがある場合、たとえばインタフェースかオブジェクトかを確かめたいときです。
{
__type(name: "Droid") {
name
kind
}
}
{
"data": {
"__type": {
"name": "Droid",
"kind": "OBJECT"
}
}
}
kind
は__TypeKind
列挙型を返します。OBJECT
は、その値のひとつです。今度はCharacter
について尋ねると、インタフェース(INTERFACE
)だとわかります。
{
__type(name: "Character") {
name
kind
}
}
{
"data": {
"__type": {
"name": "Character",
"kind": "INTERFACE"
}
}
}
オブジェクトにどのようなフィールドが備わっているかを知っておくことは役に立つでしょう。イントロスペクションシステムに、Droid
について尋ねてみます。
{
__type(name: "Droid") {
name
fields {
name
type {
name
kind
}
}
}
}
{
"data": {
"__type": {
"name": "Droid",
"fields": [
{
"name": "id",
"type": {
"name": null,
"kind": "NON_NULL"
}
},
{
"name": "name",
"type": {
"name": null,
"kind": "NON_NULL"
}
},
{
"name": "friends",
"type": {
"name": null,
"kind": "LIST"
}
},
{
"name": "friendsConnection",
"type": {
"name": null,
"kind": "NON_NULL"
}
},
{
"name": "appearsIn",
"type": {
"name": null,
"kind": "NON_NULL"
}
},
{
"name": "primaryFunction",
"type": {
"name": "String",
"kind": "SCALAR"
}
}
]
}
}
}
これらがDroid
に定められたフィールドです。
id
に型名がないのは、不思議に思えるかもしれません。これは、NON_NULL
という種類の「ラッパー」型だからです。このフィールドの型にofType
を問い合わせると、ID
型が見つかり、null
でないDIだとわかります。
同じように、friends
とappearsIn
にも型名がありません。これらはLIST
ラッパー型だからです。これらの型にofType
を問い合わせれば、それぞれ何のリストかがわかります。
{
__type(name: "Droid") {
name
fields {
name
type {
name
kind
ofType {
name
kind
}
}
}
}
}
{
"data": {
"__type": {
"name": "Droid",
"fields": [
{
"name": "id",
"type": {
"name": null,
"kind": "NON_NULL",
"ofType": {
"name": "ID",
"kind": "SCALAR"
}
}
},
{
"name": "name",
"type": {
"name": null,
"kind": "NON_NULL",
"ofType": {
"name": "String",
"kind": "SCALAR"
}
}
},
{
"name": "friends",
"type": {
"name": null,
"kind": "LIST",
"ofType": {
"name": "Character",
"kind": "INTERFACE"
}
}
},
{
"name": "friendsConnection",
"type": {
"name": null,
"kind": "NON_NULL",
"ofType": {
"name": "FriendsConnection",
"kind": "OBJECT"
}
}
},
{
"name": "appearsIn",
"type": {
"name": null,
"kind": "NON_NULL",
"ofType": {
"name": null,
"kind": "LIST"
}
}
},
{
"name": "primaryFunction",
"type": {
"name": "String",
"kind": "SCALAR",
"ofType": null
}
}
]
}
}
}
最後に、ツールとして特に役立つイントロスペクションの機能をご紹介しましょう。システムにドキュメントを要求することです。
{
__type(name: "Droid") {
name
description
}
}
{
"data": {
"__type": {
"name": "Droid",
"description": null
}
}
}
イントロスペクションを使って型システムについてのドキュメントが参照できます。ドキュメントブラウザや、リッチなIDEエクスペリエンスもつくれるでしょう。
公式サイトはこのように説明していますが、肝心のコード例の結果はdescription
がnull
です。これは、description
が定められていないからだと考えられます。「GraphQL」仕様の「Schema Introspection」によると、クエリ操作のルートの型からスキーマイントロスペクションシステムを参照できるメタフィールドはつぎのふたつです。ともにdescription
というString
のフィールドをもちます。ドキュメントの書き方については、「Descriptions」をお読みください(なお、Apollo GraphQL Docs「Descriptions (docstrings)」参照)。
__schema: __Schema!
__type(name: String!): __Type
イントロスペクションシステムについて簡単にご紹介しました。システムを用いることにより、列挙型の値や、ある型がどういうインタフェースを実装しているか問い合わせられるのです。さらに、イントロスペクションシステム自体もイントロスペクトできます。また、graphql-js/src/type/introspection.ts/
は、仕様に準拠したGraphQLクエリイントロスペクションシステムを実装するコードです。
シリーズGraphQLの基本
「GraphQL: クエリ(queries)と変更(mutations)」
「GraphQL: スキーマと型」
「GraphQL: 検証(validation)」
「GraphQL: 実行」
「GraphQL: イントロスペクション(introspection)」