GraphQL: イントロスペクション(introspection)

本稿は公式サイト「[Introspection](https://graphql.org/learn/introspection/)」にもとづく、GraphQLのイントロスペクションの仕組みについての解説です。ドキュメントの邦訳ではなく、日本語で説明し直しました。原文から省いた部分もあり、逆にわかりにくいところは補っています。なお、GraphQL公式サイトのコード例は、インタラクティブな環境です。コードを書き替えて結果が確かめられますので、ぜひ試してみてください。

GraphQLスキーマがどのようなクエリをサポートしているのか、情報を求めると役立つことが少なくありません。GraphQLでは、イントロスペクションの仕組みを用いて行うことができます。

スターウォーズの例では、ファイル[`starWarsIntrospection-test.ts`](https://github.com/graphql/graphql-js/blob/main/src/__tests__/starWarsIntrospection-test.ts)にイントロスペクションシステムを示す数多くのクエリが含まれています。参照が実装されたイントロスペクションシステムを試すために実行するテストファイルです。

型システムの設計者は、どのような型が使えるのか知っています。そうでない場合にも、ルートのクエリ型からいつでも得られる`__schema`フィールドを照会して、GraphQLに問い合わせられるのです。では、どのような型が使えるのか、尋ねてみましょう。

```graphql:GraphQL
{
	__schema {
		types {
			name
		}
	}
}
```

```json:結果
{
	"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` - 先頭のふたつのアンダースコア(`__`)は、イントロスペクションシステムの一部であることを表す。

では、どのようなクエリがあるのか、調べることから始めましょう。型システムを設計するとき、すべてのクエリの始まる型が指定されました。イントロスペクションシステムに尋ねてみます。

```graphql:GraphQL
{
	__schema {
		queryType {
			name
		}
	}
}
```

```json:結果
{
	"data": {
		"__schema": {
			"queryType": {
				"name": "Query"
			}
		}
	}
}
```

この結果は記事「[GraphQL: スキーマと型](https://qiita.com/FumioNonaka/items/ff32ce5182ba328efbe7)」で型システムについてご説明したとき、`Query`型がエントリポイントを定めると述べたことに一致します(「[Query型とMutation型](https://qiita.com/FumioNonaka/items/ff32ce5182ba328efbe7#query%E5%9E%8B%E3%81%A8mutation%E5%9E%8B)」参照)。`Query`型というのは、あくまで慣習上の名前です。他の名前をつけても構いません。クエリのはじまりの型として指定したので返されました。

特定の型について確かめたいこともあるでしょう。たとえば、`Droid`型の場合です。

```graphql:GraphQL
{
	__type(name: "Droid") {
		name
	}
}
```

```json:結果
{
	"data": {
		"__type": {
			"name": "Droid"
		}
	}
}
```

もっとも、クエリの引数に`name`を渡したのですから、すでに値はわかっています。ほかに知りたいことがある場合、たとえばインタフェースかオブジェクトかを確かめたいときです。

```graphql:GraphQL
{
	__type(name: "Droid") {
		name
		kind
	}
}
```

```json:結果
{
	"data": {
		"__type": {
			"name": "Droid",
			"kind": "OBJECT"
		}
	}
}
```

`kind`は`__TypeKind`列挙型を返します。`OBJECT`は、その値のひとつです。今度は`Character`について尋ねると、インタフェース(`INTERFACE`)だとわかります。

```graphql:GraphQL
{
	__type(name: "Character") {
		name
		kind
	}
}
```

```json:結果
{
	"data": {
		"__type": {
			"name": "Character",
			"kind": "INTERFACE"
		}
	}
}
```

オブジェクトにどのようなフィールドが備わっているかを知っておくことは役に立つでしょう。イントロスペクションシステムに、`Droid`について尋ねてみます。

```graphql:GraphQL
{
	__type(name: "Droid") {
		name
		fields {
			name
			type {
				name
				kind
			}
		}
	}
}
```

```json:結果
{
	"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`を問い合わせれば、それぞれ何のリストかがわかります。

```graphql:GraphQL
{
	__type(name: "Droid") {
		name
		fields {
			name
			type {
				name
				kind
				ofType {
					name
					kind
				}
			}
		}
	}
}
```

```json:結果
{
	"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
					}
				}
			]
		}
	}
}
```

最後に、ツールとして特に役立つイントロスペクションの機能をご紹介しましょう。システムにドキュメントを要求することです。

```graphql:GraphQL
{
	__type(name: "Droid") {
		name
		description
	}
}
```

```json:結果
{
	"data": {
		"__type": {
			"name": "Droid",
			"description": null
		}
	}
}
```

イントロスペクションを使って型システムについてのドキュメントが参照できます。ドキュメントブラウザや、リッチなIDEエクスペリエンスもつくれるでしょう。

公式サイトはこのように説明していますが、肝心のコード例の結果は`description`が`null`です。これは、`description`が定められていないからだと考えられます。「GraphQL」仕様の「[Schema Introspection](https://spec.graphql.org/October2021/#sec-Schema-Introspection)」によると、クエリ操作のルートの型からスキーマイントロスペクションシステムを参照できるメタフィールドはつぎのふたつです。ともに`description`という`String`のフィールドをもちます。ドキュメントの書き方については、「[Descriptions](https://spec.graphql.org/October2021/#sec-Descriptions)」をお読みください(なお、Apollo GraphQL Docs「[Descriptions (docstrings)](https://www.apollographql.com/docs/apollo-server/schema/schema/#descriptions-docstrings)」参照)。

```graphql:GraphQL
__schema: __Schema!
__type(name: String!): __Type
```

イントロスペクションシステムについて簡単にご紹介しました。システムを用いることにより、列挙型の値や、ある型がどういうインタフェースを実装しているか問い合わせられるのです。さらに、イントロスペクションシステム自体もイントロスペクトできます。また、[`graphql-js/src/type/introspection.ts/`](https://github.com/graphql/graphql-js/blob/main/src/type/introspection.ts)は、仕様に準拠したGraphQLクエリイントロスペクションシステムを実装するコードです。


### シリーズGraphQLの基本
「[GraphQL: クエリ(queries)と変更(mutations)](https://qiita.com/FumioNonaka/items/f22bfc2928d56469bf93)」
「[GraphQL: スキーマと型](https://qiita.com/FumioNonaka/items/ff32ce5182ba328efbe7)」
「[GraphQL: 検証(validation)](https://qiita.com/FumioNonaka/items/fe1e5c8b51cfa94e18c3)」
「[GraphQL: 実行](https://qiita.com/FumioNonaka/items/611fe2d9698f36f268db)」
「GraphQL: イントロスペクション(introspection)」