GraphQL: 自省(introspection)

本篇文章是根据官方网站”Introspection”提供的信息,对GraphQL的introspection机制进行的解释。这篇文章不是文档的翻译,而是用日语重新解释了原文。有一些省略的部分,同时也补充了一些原文中不太容易理解的内容。需要注意的是,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"
				}
			]
		}
	}
}

有相当数量的型。我们将其分为三类,进行说明。

Query/Character/Human/Episode/Droid – 型システムで定義された型。

String/Boolean – 型システムに備わる組み込みのスカラー。

__Schema/__Type/__TypeKind/__Field/__InputValue/__EnumValue/__Directive – 先頭のふたつのアンダースコア(__)は、イントロスペクションシステムの一部であることを表す。

让我们从调查有哪些查询开始吧。在设计类型系统时,已指定了所有查询的起始类型。我们可以尝试询问一下内省系统。

{
	__schema {
		queryType {
			name
		}
	}
}
{
	"data": {
		"__schema": {
			"queryType": {
				"name": "Query"
			}
		}
	}
}

这个结果与之前在文章「GraphQL: Schema and Types」中关于类型系统的讲解一致,我们提到了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。

同样地,”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
		}
	}
}

使用内省技术,可以参考有关类型系统的文件。还可以创建文档浏览器和丰富的集成开发环境。

公式网站这样解释,但关键的代码示例结果是空的。这可能是因为未定义了描述。根据“GraphQL”规范的“Schema Introspection”,可以通过查询操作的根类型引用模式内省系统,其中包含了两个元字段,都具有名为描述(description)的字符串字段。关于文档编写方式,请阅读“Descriptions”(请参考Apollo GraphQL Docs的“Descriptions(文档字符串)”)。

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

我简要介绍了自省系统。通过使用此系统,可以查询枚举类型的值以及某个类型实现了哪些接口。此外,自省系统本身也可以被自省。同时,graphql-js/src/type/introspection.ts是实现符合规范的GraphQL查询自省系统的代码。

GraphQL系列的基础

「GraphQL: 查询和变更」
「GraphQL: 架构和类型」
「GraphQL: 验证」
「GraphQL: 执行」
「GraphQL: 内省」

广告
将在 10 秒后关闭
bannerAds