GraphQL 概览

GraphQL

GraphQL 是一个用于 API 的查询语言,使用基于类型系统来执行查询的服务端运行时,并且它不和任何特定数据库或存储引擎绑定,依靠你现有的代码和数据支撑。

一个 GraphQL 服务通过定义类型和类型上的字段创建,并为每个类型上的字段提供解析函数。

为什么使用 GraphQL

传统的 REST 标准在使用中有一些问题:

  • 获取比较复杂的对象需要向服务器进行多次请求
  • REST 通常在服务端就决定了数据的结构,这导致你获得的数据很多是你不要的,即冗余数据颇多
  • 服务器返回的数据不可预测,你需要付出额外的工作来准确的理解你从服务器获得数据

而通过 GraphQL 你可以从服务器获取你期望的数据,并且清楚的知道你获得的数据是什么样子的。

查询和变更

字段 Fields

字段可以是标量或对象,当子段位对象类型时,你可以对其进行次级选择。GraphQL 查询能够遍历相关对象及其字段,是的客户端可以一次请求查询大量相关数据。

参数 Arguments

在 GraphQL 中,每一个字段和嵌套对象都能有自己的一组参数,从而使得 GraphQL 可以完美替代多次 API 请求。

参数可以是多种不同的类型。GraphQL 自带一套默认类型,但是你可以声明一套自己的定制类型,你要能序列化成的你需要的格式即可。

1
2
3
4
5
6
{
queryFunction(arg: arg) {
field,
...
}
}

别名 Aliases

为字段设置别名,可以使你能够通过不同参数来查询相同的字段。

片段 Fragments

片段是 GraphQL 中的可复用单元,它能够组织一组字段,在需要的地方被引入。

片段也可以访问查询或变更中声明的变量。

操作名称 Operation name

1
query/mutation/subscription operation_name {}

操作类型可以是 query/mutation/subscription,描述你打算做什么类型的操作。操作类型是必须的,除非使用查询简写语法 - 这种情况下,你无法为操作提供名称或变量定义。

操作名称是你的操作的有意义和明确的名称,它仅在有多个操作的文档中是必须的。但是操作名称在调试和服务端日志记录中非常有用,可以帮助快速定位到问题发生的位置。

变量 Variables

1
2
3
4
5
query query_function($variable_name: variable_type) {
obj(variable: $variable_name) {
...
}
}

所有声明的变量都必须是标量、枚举类型或输入对象类型。所以如果想要传递一个复杂对象到一个字段上,你必须知道服务器上其匹配的类型。

变量定义可以是可选的或者必要的。但是如果你传递变量的字段要求非空参数,那变量一定是必要的。

可以通过在查询中对类型定义后面附带默认值的方式,将默认值赋给变量。

1
2
3
4
5
query query_function($variable_name: variable_type = "default_value") {
obj(variable: $variable_name) {
...
}
}

指令 Directives

指令可以动态的改变我们的查询结构。指令可以附着在字段或者字段包含的字段上,然后以任何服务端期待的方式来改变查询的执行。

  • @include(if: Boolean) 仅在参数为 true 时,包含此字段
  • @skip(if: Boolean) 如果参数为 true ,跳过此字段

变更 Mutations

技术上而言,任何查询都能够被实现为导致数据写入的操作,然而,一个规范任何导致写入的操作都应该显式通过变更来发送也是必要的。

一个变更可能包含多个字段,查询是并行执行,变更是线性执行。

内联片段 Inline Fragments

如果你查询的字段返回的是接口或者联合类型,那么你可能需要使用内联片段来取出下层具体类型的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
query numberOFAnimals($sp:Species!) {
animals(species: $sp) {
number,
// 仅在 animals 返回为 Mammal 时执行
... on Mammal {
isRare
}
// 仅在 animals 返回为 Human 时执行
... on Human {
sex
}
}
}

具名片段也可以用于同样的情况,因为具名片段总是附带了一个类型。

原字段

1
2
// 获得那个位置的对象类型名称
__typename

Schema 和类型

类型系统

GraphQL 查询语言基本上就是关于选择对象上的字段。因此一个 GraphQL 查询的结构和结果非常相似,这使我们能预测服务器会返回什么样的结果。

每一个 GraphQL 服务都会定义一套类型,用来描述你能从服务器能够查询到的数据。每当查询到来,服务器就会根据 schema 验证并执行查询。

类型语言

GraphQL 可以用任何语言编写,因为它并不依赖于任何特定语言的句法来与 GrapahQL schema 沟通,它定义了称之为 GraphQL schema language 的简单语言。

对象类型和字段 Object Types and Fields

GraphQL schema 中最基本的组件是对象类型,它就表示你可以从服务上获取到什么类型的对象,以及这个对象有什么字段。

1
2
3
4
type Object_Name {
field,
...
}

参数 Arguments

GraphQL 对象类型上的每个字段都可能有零个或多个参数,并且所有参数都必须是具名的。参数可能是必选的或可选的,如果参数可选的,可以给它定义默认值。

查询和变更类型 The Query and Mutation Types

每一个 GraphQL 服务中都有一个 query 类型,可能有一个 mutation 类型,它们定义了每一个 GraphQL 查询的入口。

1
2
3
4
schema {
query: Query,
mutation: Mutation
}

标量类型 Scalar Types

一个对象类型有自己的名字和字段,而某些时候,这些字段必然会解析到具体数据,这就是标量,表示对应 GraphQL 查询的叶子节点。

它们包括 :

  • Int : 有符号 32 位证书
  • Float :有符号双精度浮点值
  • String : UTF-8 字符序列
  • Booleantruefalse
  • ID : ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键

你可以自定义标量类型,但需要保证其能够序列化、反序列化和验证。

枚举类型 Enumeration Types

枚举类型是一种特殊的标量,它限制在一个特殊的可选值集合内。

列表和非空 List and Non-Null

类型修饰符 ! 可将字段标注为非空,即表示这个字段总是会返回一个非空值。亦可用于修饰定义字段上的参数。

1
2
3
type Object_Name {
field_1: Type!
}

使用 [] 标记一个类型为 List ,表示这个字段会返回这个类型的数组。

1
2
3
query query_name {
field_list: [String],
}

接口 Interface

GraphQL 支持接口,接口是一个抽象类型,它包含某些字段,而想要实现这个接口的对象必须包含这些子段。

1
2
3
4
5
6
7
8
9
10
11
interface Human {
name: String!
age: Int!
}

type Man implements Human {
name: String!
age: Int!
height: Float!

}

联合类型 Union Type

联合类型和接口非常相似,但是不指定类型之间的任何共同字段。

1
union union_type = type_1 | type_2 | ...

联合类型的成员需要是具体对象类型。

输入类型 Input Types

使用输入类型可以将复杂的对象传递给字段,这在变更中特别有用。

1
2
3
4
input input_type {
field,
....
}

输入对象类型上的字段本身也可以只带输入对象类型,但是注意不能混淆输入和输出类型,输入对象类型上的字段不能拥有参数。

验证

通过使用类型系统,可以预判一个查询是否有效。这使得服务器和客户端可以在无效查询创建时就有效的通知开发者,而不用依赖运行时检查。

以下是指的注意的基本原则

  • 片段不能引用自身或创造回环
  • 只能查询给定类型上的字段
  • 如果查询返回的不是标量或枚举类型,就需要指明想要从字段中获取的数据

执行

一个 GraphQL 查询在被验证之后,GraphQL 服务器将会将其执行,并返回与请求的结构相对应的结果,该结果通常会是 JSON 格式。

每个类型的每个字段都由一个 resolver 函数支持,该函数由 GraphQL 服务器开发人员提供 - 当一个字段被执行时,相应的 resolver 函数被调用以产生下一个值。如果字段产生标量值,则执行完成。如果一个字段产生一个对象,则该查询将继续执行该对象对应字段的解析器,直到生成标量值。GraphQL 查询始终以标量值结束。

根字段 & 解析器

每一个 GraphQL 服务端应用的顶层,必有一个代表着所有进入 GraphQL API 可能的入口点,称之为 Root 类型或 Query 类型。

1
2
3
4
5
6
7
8
9
10
11
// `obj` 代表上一级对象,如果字段是根节点查询类型通常不会被使用
// `args` 提供在 GraphQL 查询中传入的参数
// `context` 会提供给所有解析器,持有重要的上下文信息
// `info` 保存与当前查询相关的字段的特定信息以及 `schema` 详细信息的值
Query {
query_name(obj, args, context, info) {
return context.db.query(...).then((return_data) => {
return return_data;
});
}
}

当每个字段被解析后,结果被放到键值映射中,字段名称作为键值映射的键,解析器的值作为键值映射的值,这个过程从查询字段的底部叶子节点开始返回,直到根 Query 类型的起始节点。最后合并成为能够镜像到原始查询结果的结果,让后将其发送到请求的客户端。

异步解析器

context 提供了一个数据库访问对象,用来通过查询中传递的参数查询数据,因为从数据库拉取数据是一个异步操作,它会返回一个 Promise 对象,当数据库查询返回结果,就能构造并返回查询结果。

只有解析器能够感知 Promise 的进度,GraphQL 查询只关注一个结果是否返回,在执行期间如果异步操作没有完成,则 GraphQL 会一直等待下去。

内省

GraphQL 通过内省系统使得我们能够直到 GraphQL Schema 支持哪些查询。

我们可以通过 __schema 字段来向 GraphQL 询问哪些类型是可用的。一个查询的根类型总是有 __schema 这个字段。

1
2
3
4
5
{
__schema {
...
}
}

检验特定类型

1
2
3
{
__type() {}
}
作者

Y2hlbmdsZWk=

发布于

2018-12-22

更新于

2021-09-01

许可协议