1
1
# 教程: GraphQL API 的准则
2
2
3
- 本教程最初由 [ Shopify] ( https://www.shopify.ca/ ) 基于内部需求而撰写, 但考虑到它对于任何
4
- 准备编写 GraphQL API 的人有帮助因此我们创建了这一对外的公开版本 。
3
+ 本教程最初由 [ Shopify] ( https://www.shopify.ca/ ) 创建,仅供内部使用。 但考虑到它对于任何
4
+ 准备基于 GraphQL API 开发的人有帮助因此我们创建了这个公开版本 。
5
5
6
- 它参考了最近3年来 Shopify 在生产环境中开发和迭代 GraphQL API 所获得的经验和教训,并将持续
7
- 更新 。
6
+ 基于最近三年来 Shopify 在生产环境中开发和迭代 GraphQL API 所获得的经验和教训,并将持续
7
+ 更新此文档,并且将会不断修正,不会一成不变 。
8
8
9
- 我们相信这些设计准则在绝大多数情况下都是适用的。但它们可能未必完全适用于你的需求 ,即使在 Shopify 内部
9
+ 我们认为这些设计准则在绝大多数情况下都是适用的。但它们可能未必完全适用于您的需求 ,即使在 Shopify 内部
10
10
这些规则也未必 100% 的适用于所有场景。因此请不要盲目的照搬和实施下述的所有设计准则。
11
11
12
12
目录
13
13
=================
14
- * [ 简介] ( #简介 )
15
- * [ 步骤0: 假定背景] ( #步骤0-假定背景 )
16
- * [ 步骤1: 全局概览] ( #步骤1:-全局概览 )
17
- * [ 步骤2: 少既是多] ( #步骤2-少既是多 )
18
- * [ 避免暴露 ` CollectionMembership ` 表] ( #避免暴露-collectionmembership-表 )
19
- * [ 重新思考合集] ( #重新思考合集 )
20
- * [ 结论] ( #结论 )
21
- * [ 步骤3: 增加细节] ( #步骤3-增加细节 )
22
- * [ 最初实现] ( #最初实现 )
23
- * [ ID 和 ` Node ` 接口] ( #id-和-node-接口 )
24
- * [ ` Rules ` 字段和子对象] ( #rules-字段和子对象 )
25
- * [ 列表和分页] ( #列表和分页 )
26
- * [ 字符串] ( #字符串 )
27
- * [ 关联对象的 ID] ( #关联对象的-id )
28
- * [ 命名与标量(Scalar)] ( #命名与标量scalar )
29
- * [ 分页的再思考] ( #分页的再思考 )
30
- * [ 枚举(Enum)] ( #枚举enum )
31
- * [ 步骤4: 商业逻辑] ( #步骤4-商业逻辑 )
32
- * [ 步骤5: 变更(Mutation)] ( #步骤5-变更mutation )
33
- * [ 根据具体业务来划分支持的操作种类] ( #根据具体业务来划分支持的操作种类 )
34
- * [ 思考对象与对象间的关系] ( #思考对象与对象间的关系 )
35
- * [ Input: 结构 - 第一部分] ( #input-结构---第一部分 )
36
- * [ Input: 标量] ( #input-标量 )
37
- * [ Input: 结构 - 第二部分] ( #input-结构---第二部分 )
38
- * [ Mutation 的返回值] ( #mutation-的返回值 )
39
- * [ TLDR: 设计准则总结] ( #tldr-设计准则总结 )
40
- * [ 结尾] ( #结尾 )
14
+ - [ 教程: GraphQL API 的准则] ( #教程-graphql-api-的准则 )
15
+ - [ 目录] ( #目录 )
16
+ - [ 简介] ( #简介 )
17
+ - [ 步骤0: 假设背景] ( #步骤0-假设背景 )
18
+ - [ 步骤1: 全局概览] ( #步骤1-全局概览 )
19
+ - [ 步骤2: 少既是多] ( #步骤2-少既是多 )
20
+ - [ 避免暴露 ` CollectionMembership ` 表] ( #避免暴露-collectionmembership-表 )
21
+ - [ 重新思考合集] ( #重新思考合集 )
22
+ - [ 结论] ( #结论 )
23
+ - [ 步骤3: 增加细节] ( #步骤3-增加细节 )
24
+ - [ 最初实现] ( #最初实现 )
25
+ - [ ID 和 ` Node ` 接口] ( #id-和-node-接口 )
26
+ - [ ` Rules ` 字段和子对象] ( #rules-字段和子对象 )
27
+ - [ 列表和分页] ( #列表和分页 )
28
+ - [ 字符串] ( #字符串 )
29
+ - [ 关联对象的 ID] ( #关联对象的-id )
30
+ - [ 命名与标量(Scalar)] ( #命名与标量scalar )
31
+ - [ 分页的再思考] ( #分页的再思考 )
32
+ - [ 枚举(Enum)] ( #枚举enum )
33
+ - [ 步骤4: 商业逻辑] ( #步骤4-商业逻辑 )
34
+ - [ 步骤5: 变更(Mutation)] ( #步骤5-变更mutation )
35
+ - [ 根据具体业务来划分支持的操作种类] ( #根据具体业务来划分支持的操作种类 )
36
+ - [ 思考对象与对象间的关系] ( #思考对象与对象间的关系 )
37
+ - [ Input: 结构 - 第一部分] ( #input-结构---第一部分 )
38
+ - [ Input: 标量] ( #input-标量 )
39
+ - [ Input: 结构 - 第二部分] ( #input-结构---第二部分 )
40
+ - [ Mutation 的返回值] ( #mutation-的返回值 )
41
+ - [ TLDR: 设计准则总结] ( #tldr-设计准则总结 )
42
+ - [ 结尾] ( #结尾 )
41
43
42
44
## 简介
43
45
44
- 欢迎!本文档将指导您如何设计新的 GraphQL API。API 设计是一项极具挑战性的任务,它需要你对于
45
- 业务场景拥有深刻地理解并且不断的进行迭代和实验。
46
+ 欢迎您!本文档将引导设计新的 GraphQL API(或现有 GraphQL API 的扩展和迭代)。 API 设计是一项极具挑战性的任务,它需要你对于业务场景拥有深刻地理解并且不断的进行迭代和验证。
46
47
47
- ## 步骤0: 假定背景
48
+ ## 步骤0: 假设背景
48
49
49
- 为了本教程之目的,你需要想象自己正为一家电商公司工作。目前这一电商平台已经拥有1个可以查询商品信息的 GraphQL API。在最近的产品迭代中 ,你的团队刚刚完成了「商品合集」功能的后端开发,你被指派负责开发该功能的 GraphQL 接口。
50
+ 就本教程而言,假设的想象您在一家电子商务公司工作。目前这一电商平台已经拥有一个可以查询商品信息的 GraphQL API。在最近的产品开发迭代中 ,你的团队刚刚完成了「商品合集」功能的后端开发,你被指派负责开发该功能对应的 API 接口。
50
51
51
- 商品合集本质上是一个类似收藏夹的功能, 允许商家对于商品进行分组—— 从而实现例如在某个专题促销页中仅展示某个特定合集中的商品或「某个特定合集中的商品打85折 」之类的程序化任务。
52
+ 商品合集本质上是一个类似于商品收藏夹的功能;例如,可能拥有所有 t-shirts 的合集分组。 允许商家对于商品进行分组 —— 从而实现例如在某个专题促销页中仅展示某个特定合集中的商品或「某个特定合集中的商品打折 」之类的程序化任务。
52
53
53
- 你的同事已经完成了这一需求的业务逻辑设计 ,具体如下:
54
- * 所有的合集都包含诸如 :标题、详情描述(可能包括 HTML 片段)、缩略图 等基础字段。
55
- * 合集有 2 种类型 ,分别是 需要人工添加商品的手动合集 (ManualCollections) 和 能够按照规则生成的自动合集 (AutomaticCollections)——例如可以创建1个自动合集 ,该合集会将店铺内的存在 XL 码库存的所有男装都加入进去。
56
- * 商品和合集之间是多对多(many-to-many)关系,在数据库层面存在一个叫做 ` CollectionMembership ` 的中间关联表。
54
+ 你的后端同事已经完成了这一需求的业务逻辑设计 ,具体如下:
55
+ * 所有的合集都包含一些简单属性诸如 :标题、详情描述(可能包括 HTML 片段)、缩略图 等基础字段。
56
+ * 合集有两种类型 ,分别是 需要人工添加商品的「手动合集」 (ManualCollections) 和 能够按照规则生成的「自动合集」 (AutomaticCollections)—— 例如可以创建一个自动合集 ,该合集会将店铺内的存在 XL 码库存的所有男装都加入进去。
57
+ * 商品和合集之间是多对多(many-to-many)关系,在数据库层面存在一个叫做 ` CollectionMembership ` 的中间关联表。
57
58
* 就像商品可以设置上下架一样,合集也有一个字段可以设置其是否生效。
58
59
59
- 我们将基于上述的假定需求来进行 API 设计。
60
+ 我们将基于上述的假定需求来思考如何进行 API 设计。
60
61
61
62
## 步骤1: 全局概览
62
63
63
- 不考虑架构的话, 这一功能最为简单粗暴的 GraphQL Schema 设计可能是这样 :
64
+ 这一功能最为简单粗暴的 GraphQL Schema 设计可能看起来是这样( 忽略所有已存在的类型,比如 Product ) :
64
65
65
66
``` graphql
66
67
interface Collection {
@@ -101,12 +102,10 @@ type CollectionMembership {
101
102
}
102
103
```
103
104
104
- 即便只有4个对象和1个接口,但这样设计依然看起来有点过于复杂 。而且,它似乎并没有完全地满足业务需求,
105
- 例如我们需要使用这样的 API 来完成手机 App 上的合集功能似乎就不太够用了。
105
+ 尽管只有四个对象和一个接口,但乍看之下已经相当复杂 。而且,它似乎并没有完全地满足业务需求,
106
+ 例如我们需要使用这样的 API 来完成移动端 App 上的合集功能似乎就不太够用了。
106
107
107
- 基于我们的经验,用一个由多个对象、数十个字段揉杂在一起所构成的 Graphql API 来实现某一业务需求往往是混乱和错误的开端。你应该从更高的抽象层级进行思考,着眼于类型(GraphQL Type )及各个类型之间的关系,而非数据库层面的具体字段或对 CRUD 接口进行简单罗列。从[实体关系模型(Entity -relationship model )](https ://zh .wikipedia .org /zh -hans /ER %E6 %A8 %A1 %E5 %9E %8B )开始思考会是一个好的选择。
108
-
109
- 在开始正式重构前,我们先把当前设计中所有不需要关注的细节隐藏起来以利于我们接下来的思考:
108
+ 让我们先缓缓好好的思考一下,用一个由多个对象和数十个字段揉杂在一起所构成的 Graphql API 来实现某一业务需求往往是混乱和错误的开端。你应该首先从更高的抽象层级进行思考,并着眼于类型(GraphQL Type )及各个类型之间的关系,而非数据库层面的具体字段或对 CRUD 接口进行简单罗列。从[实体关系模型(Entity -Relationship model )](https ://zh .wikipedia .org /zh -hans /ER %E6 %A8 %A1 %E5 %9E %8B )开始思考会是一个好的选择。如果我们像这样收敛和简化一下范围,我们将得到以下结果:
110
109
111
110
```graphql
112
111
interface Collection {
@@ -133,19 +132,22 @@ type CollectionMembership {
133
132
}
134
133
```
135
134
135
+ 为了获得这种简化的表示形式,当前设计中所有不需要关注的细节隐藏起来。移除了所有字段类型,所有字段名称和所有可能为空的信息。剩下的东西看起来仍然有点像 GraphQL ,但是可以让您关注更高级别的类型及其关系。
136
+
136
137
*规则一:永远先从更高的抽象层级进行设计,先考虑类型与类型之间的关系,再去考虑具体的字段。*
137
138
138
139
## 步骤2: 少既是多
139
140
140
141
接下来,让我们着力于解决简单粗暴版本中的核心问题。
141
142
142
- 如前所述,在数据库中本需求由通过 自动合集表、手动合集表以及用于实现商品与合集间多对多关系的中间表 这3张表来实现 。我们现有的 GraphQL API 设计完全照搬了这一关系模型,但这实际上是错误的。
143
+ 如前所述,在数据库中本需求由通过 「 自动合集表」、「手动合集表」以及用于「实现商品与合集间多对多关系的中间表」这三张表来实现 。我们现有的 GraphQL API 设计完全照搬了这一关系模型,但这实际上是错误的。
143
144
144
- 这一错误的核心问题在于,API 设计和数据库设计有着不同的抽象层级和使用目的 。在 API 设计中不加思考的照搬数据库表结构,往往会把我们引入歧途。
145
+ 这一错误的核心问题在于,API 设计和数据库设计有着不同的抽象层级和使用目的 。在 API 设计中不加思考的照搬数据库表结构,往往会把我们引入歧途。
145
146
146
147
### 避免暴露 `CollectionMembership` 表
147
148
148
149
你可能已经注意到 `CollectionMembership ` 表实质上是一种技术细节,对于业务场景而言它实质上应该是一种黑盒实现。
150
+ 现在再读一遍最后一句话:产品与合集之间的关系;从业务域的维度来看,它们并不需要明确暴露出来它们之间的数据关系。这是一个需要实现的技术细节。
149
151
150
152
也就是说我们不应该在 API 设计中将它暴露出来,因为我们的 API 是对于业务模型而非技术细节的抽象。因此我们可以进一步将 Schema 重构成下述版本:
151
153
@@ -169,15 +171,15 @@ type ManualCollection implements Collection {
169
171
type AutomaticCollectionRule { }
170
172
```
171
173
172
- This is much better .
174
+ 看起来好多了。
173
175
174
176
*规则二: 永远不要在 API 中暴露不必要的实现细节。*
175
177
176
178
### 重新思考合集
177
179
178
- 现有的 API Schema 设计依然着一个因为我们没有深入理解业务知识而产生的缺陷。 我们把自动合集和手动合集设计成两种不同的 GraphQL 类型,他们都继承于同一个公共的集合接口。从技术上说,这一的设计似乎是符合逻辑的—— 他们有许多相同的字段,但本质上它们的功能由存在着明显差异 。
180
+ 现有的 API Schema 设计依然着一个因为我们没有深入理解业务知识而产生的缺陷。 我们把「自动合集」和「手动合集」设计成两种不同的 GraphQL 类型,他们都继承于同一个公共的集合接口。从直觉上讲,这设计似乎是符合逻辑的 —— 他们有许多相同的字段,但本质上它们的功能和行为存在着明显差异(自动合集有规则) 。
179
181
180
- 但从业务场景角度思考,这种差异实现是也只是一种实现细节上的差异 。它们在业务场景中所实现的功能是完全一致的—— 将若干个商品聚合在一起。而且未来,可能会有新的需求导致第三种甚至第四种合集类型的出现,例如可能会新增一种主要由规则生成同时允许手工加入商品的合集类型。但无论需求怎么变更,有一点是不会变的——它们从逻辑上永远是一种用于将若干个商品聚合在一起的功能 。因此或许我们可以进一步的这样重构:
182
+ 但从业务场景角度来看,这些差异基本上也是实现细节 。它们在业务场景中所实现的功能是完全一致的 —— 将若干个商品聚合在一起,至于怎么样的方式选择产放入合集都是次要的 。而且未来,可能会有新的需求导致第三种甚至第四种合集类型的出现,例如可能会新增一种主要由规则生成同时允许手工加入商品的合集类型。但无论需求怎么变更,有一点是不会变的 —— 它们从逻辑上永远是一种用于将若干个商品聚合在一起的合集 。因此或许我们可以进一步的这样重构 API :
181
183
182
184
```graphql
183
185
type Collection {
@@ -188,27 +190,27 @@ type Collection {
188
190
189
191
type CollectionRule { }
190
192
```
191
- 这样就看起来简洁多了,也许你会担心对于手动合集而言 ,聚合规则(CollectionRule )字段是不存在的。但实际上只要返回一个空数组 ,那么这一设计完全是符合逻辑和满足需求的。
193
+ 这样就看起来简洁多了,也许你会担心对于「手动合集」而言 ,聚合规则(CollectionRule )字段是不存在的。但实际不用担心对于「手动合集」只要返回一个空数组 ,那么这一设计完全是符合逻辑和满足需求的。
192
194
193
195
### 结论
194
196
195
- 在较高的抽象层级下进行 API 设计,要求你必须对于你所建模的业务场景有着非常深刻的认知。不要急于进行细节实现,花费足够时间理解业务场景及其上下文对于你而言至关重要。
197
+ 在较高的抽象层级下进行 API 设计,要求你必须对于你所建模的业务场景有着非常深刻的认知。本教程提供的范例很难有你真实工作环境中的需求那么具体和复杂。但是,也足够用来阐述和讲解这样设计的意义。对于你实际工作中业务领域建模,也是一样的原则 —— 不要急于进行细节实现,花费足够时间理解业务场景及其上下文对于你而言至关重要。
196
198
197
- 与此同时,一个好的 API 设计也不应该是对于某个 UI 设计稿的建模—— 你不应该在设计 API 时只考虑设计稿中要求体现哪些字段。即便 UI 设计稿和数据库表结构对于 API 设计而言非常有参考价值,但你务必记得你的核心关注点应该是更为抽象的业务场景 。
199
+ 与此同时,一个好的 API 设计也不应该是对于某个 UI 设计稿的建模 —— 你不应该在设计 API 时只考虑设计稿中要求体现哪些字段。即便 UI 设计稿和数据库表结构对于 API 设计而言非常有参考价值,但你务必记得你的核心关注点应该是更为抽象的业务领域场景 。
198
200
199
- 更重要的是,务必不要照搬现有的 REST API 设计(如果有的话)。 REST 和 GraphQL 背后是不同的思考逻辑,你在 REST API 领域的设计经验对于 GraphQL API 而言未必是能够适用的 。
201
+ 更重要的是,务必不要照搬现有的 REST API 设计(如果有的话)。 REST 和 GraphQL 背后是不同的思考逻辑,你在 REST API 领域的设计经验对于 GraphQL API 而言未必是能够适用的 。
200
202
201
203
既往不恋,纵情向前,当下不杂,未来不迎。
202
204
203
- *规则三: 围绕着业务背景重新思考你的 GraphQL API ,切忌直接照搬数据库表结构、视觉稿或已有的 REST API 。*
205
+ *规则三: 围绕着业务领域背景重新思考你的 GraphQL API ,切忌直接照搬数据库表结构、视觉稿或已有遗留的 API 。*
204
206
205
207
## 步骤3: 增加细节
206
208
207
- 现在我们已经有了一个大体合适的抽象模型,我们可以逐步开始考虑细节—— 把隐去的具体字段加回来。
209
+ 现在我们已经有了一个大体合适的抽象模型,我们可以逐步开始考虑细节 —— 把隐去的具体字段加回来。
208
210
209
211
在我们开始增加每一个字段时,我们都需要仔细思考这个字段是否真的有存在的必要。我们的 GraphQL 类型中存在某个字段是因为我们的业务场景需要用到,而不应该是因为这个字段在数据库中存在或在过去的 REST API 中存在。
210
212
211
- 在 GraphQL 中暴露一个字段、参数或类型,非常简单。但一旦发布上线后,你想要将之去除和改名将变得异常苦难 ,GraphQL 的灵活意味着你很难预测哪些地方会用到它们。
213
+ 在 GraphQL 中暴露一个字段、参数或类型,非常简单。但一旦发布上线后,你想要将之和改名将变得异常苦难 ,GraphQL 的灵活意味着你很难预测哪些地方会用到它们。
212
214
213
215
*规则四:永远记得在 GraphQL 中去掉一个字段要比新增一个字段困难的多。*
214
216
@@ -234,21 +236,23 @@ type CollectionRule {
234
236
}
235
237
```
236
238
237
- 接下来,我们来从上到下具体思考每一个字段是否有其存在的必要 。
239
+ 接下来,我们来从上到下的顺序具体思考每一个字段是否有其存在的必要。并且逐步解决许多新的问题 。
238
240
239
241
### ID 和 `Node` 接口
240
242
在我们的合集类型中,第一个字段是 ID 字段。非常合理,而且确实是必须的——我们在做增删该查时都会用的到这一字段。
241
243
不过,在 GraphQL API 中往往会存在一个`Node ` 接口,它的具体结构如下:
244
+
242
245
```graphql
243
246
interface Node {
244
247
id : ID !
245
248
}
246
249
```
247
- 在 GraphQL 中,它用来告示客户端基于其实现的对象是可以基于唯一 ID 进行持久化和搜索的 ,这有助与客户端更高效的实现本地缓存和其他功能。
250
+ 在 GraphQL 中,它用来告示客户端基于其实现的对象是可以基于唯一 ID 进行持久保存和检索 ,这有助与客户端更高效的实现本地缓存和其他功能。
248
251
249
252
> 译者注:`Node ` 接口实际上 Relay 规范的一部分,可以在 [GraphQL Sever Specification ](https ://relay .dev /docs /en /graphql -server -specification .html ) 中了解具体信息。
250
253
251
254
因此我们的业务对象应该继承于 `Node `接口:
255
+
252
256
```graphql
253
257
type Collection implements Node {
254
258
id : ID !
0 commit comments