@@ -43,6 +43,15 @@ for t in teams:
43
43
44
44
过程式的数据处理对调整和阅读都不友好, 循环和拼接容易产生不通用和不易维护的代码. 添加和修改字段也很麻烦.
45
45
46
+ 另外这种处理放在不同的分层, 影响也不同. 比如常用的 controller - service - model 三层:
47
+
48
+ - 如果由 model 来处理, 那么 model 层需要做修改, controller 和 service 都要跟着调整
49
+ - 如果由 service 处理, 那么 service 的查询就会充满视图细节, 不容易复用, service 就变得僵化了
50
+ - 如果由 controller 处理, 影响面较小, 但这种命令式的拼接本身不直观, 不好维护
51
+ - 如果让 client 处理, 那会是个灾难.
52
+
53
+ > 面向组合模式将会在 controller 层解决这个拼接问题.
54
+
46
55
GraphQL 带来的通过声明描述数据结构是一个好的方向, Graph Query Language, 作为一种声明式的查询语言, 服务于一系列关系型资源的获取和组合. 其查询语句能展示期望的数据结构.
47
56
48
57
``` gql
@@ -70,7 +79,7 @@ var rootValue = {
70
79
}
71
80
```
72
81
73
- graphene 的会更加形象一些.
82
+ 借用 code first 的 graphene 的会更加形象一些.
74
83
75
84
``` python
76
85
# code first
@@ -83,7 +92,7 @@ class Query(ObjectType):
83
92
return f ' Hello { first_name} ! '
84
93
```
85
94
86
- 一段复杂的GraphQL query 查询结果和一段复杂的 ORM 查询是类似的. 只是GraphQL 借助dataloader更擅长关联数据. 可以更加直观地构造多层的视图数据.
95
+ 一段复杂的GraphQL query 查询结果和一段复杂的 ORM 查询是类似的. 只是GraphQL 借助dataloader更擅长关联数据. 可以更加直观地构造多层的视图数据. (代价是额外的查询开销)
87
96
88
97
GraphQL 虽好, 但完整引入 GraphQL 对架构的影响不小, schema 定义之类的都要跟随调整. 而且其自身也存在一系列的问题, 例如:
89
98
- 无法描述尺寸不确定的递归结构
@@ -100,15 +109,15 @@ GraphQL 虽好, 但完整引入 GraphQL 对架构的影响不小, schema 定义
100
109
101
110
总体来说, GraphQL 在提供视图数据方面, 有查询灵活度高的优点, 但存在获取的数据后期调整比较麻烦, 以及架构侵入较大等缺点. 比如 GraphQL 获取到多层数据后要做层级聚合统计, 就需要重新遍历一遍树状数据来处理. 框架本身没有设计合适的下层数据处理完之后触发回调的钩子. (这恰恰是对视图调整很有用的)
102
111
103
- 在这里我们总结一下, 从数据获取到生成视图需要哪几个步骤 .
112
+ 在这里我们暂停总结一下, 看看从数据获取到生成视图需要哪几个步骤 .
104
113
105
- 1 . 查询, 可能是多组数据 , 也可能已经是嵌套数据
106
- 2 . 数据拼接 (optinal), 根据 1 来决定是否需要 (GraphQL可以很大程度跳过这一步.)
107
- 3 . 业务转换: 对查到的数据做调整, 比如计算数组长度, 额外过滤, 业务转换等等, 构造出前端直接可以使用的数据. (这种具体业务逻辑不适合在GraphQL中操作, 因此需要单独处理. )
114
+ 1 . 查询: 来源可能是多组数据 , 也可能已经是嵌套数据
115
+ 2 . 数据拼接: (optinal), 根据 1 来决定是否需要 (GraphQL可以很大程度跳过这一步.)
116
+ 3 . 业务转换: 对查到的数据做调整, 比如计算数组长度, 额外过滤, 业务转换等等, 构造出前端直接可以使用的数据. (GraphQL不擅长这块 )
108
117
109
- 思考后会发现, 比起 GraphQL 那个灵活的查询组合功能 , 在处理视图数据的时候, GraphQL 最大的优势是他申明式的数据描述方式 .
118
+ 思考后会发现, 相较 GraphQL 灵活的查询 , 在处理视图数据的时候, GraphQL 最大的优势恰恰是他申明式的数据描述方式 .
110
119
111
- 以 graphene-python 为例, Query对象可以支持灵活的 GraphQL 查询, 比如挑选字段, 或者重命名等等.
120
+ 以 graphene-python 为例, Query可以灵活选择所需的字段
112
121
113
122
``` python
114
123
from graphene import ObjectType, String, Schema
@@ -129,9 +138,9 @@ result = schema.execute(query_with_argument)
129
138
print (result.data[' hello' ])
130
139
```
131
140
132
- 那如果我不去编写查询, 而是直接把 Query 直接变成一个服务于固定业务的视图数据描述:
141
+ 那如果我省去查询, 而是直接将 Query 变成一个固定的业务的视图数据描述?
133
142
134
- 借助 pydantic 强大的类型转换和检查的功能, 来实现申明式的数据结构描述 .
143
+ 借助 pydantic 强大的类型转换和检查的功能, 我们可以这样来实现申明式的数据结构描述 .
135
144
136
145
``` python
137
146
from pydantic import BaseModel
@@ -154,25 +163,29 @@ async def main():
154
163
return await Resolver(context = {' first_name' : ' tangkikodo' }).resolve(hgv)
155
164
```
156
165
157
- ** 优点是把大而全的单一查询入口, 替换成了一个个小巧灵活的定制化 schema 描述.**
166
+ 这样, 我们就获得了一个专用的视图数据描述, 并且获得了期望的数据.
167
+
168
+ ** 把大而全的单一查询入口, 替换成了一个个小巧灵活的定制化 schema 描述.**
158
169
159
170
结合那三个步骤, ` pydantic-resolve ` 可以做到:
160
171
161
172
1 . 查询. 可以层层查询 (dataloader), 也可以一次性从树状数据加载 (GraphQL or ORM 查询结果).
162
173
2 . ~~ 数据拼接~~
163
- 3 . 业务转换: 利用post_method 和 exclude 方法 , 可以在每一层灵活处理数据
174
+ 3 . 业务转换: 利用post_method, expose, exclude 等方法 , 可以在每一层灵活处理数据
164
175
165
176
## 什么是面向组合的模式?
166
177
167
- 面向组合的开发模式就是在声明式描述期望视图结构的基础上 , 逐步扩展出来的一套开发模式.
178
+ 面向组合的开发模式就是在 ** 声明式描述期望视图结构 ** 的基础上 , 逐步扩展出来的一套开发模式.
168
179
169
- 它在架构上的优点是 , 通过申明式的 schema 定义, 让数据拼接过程变得更加直接和容易调整.
180
+ 它在架构上的优势是 , 通过申明式的 schema 定义, 让数据拼接过程变得更加直接和容易调整.
170
181
171
- 它让 service 层保持简洁, 大多数情况下只需要提供简单的查询就能满足拼接的需要 , 避免了拼接逻辑侵入到 service 的情况.
182
+ 它让 service 层的核心数据查询保持简洁, 使用独立的数据loader来组合数据 , 避免了拼接逻辑侵入到 service 或者 controller 的情况.
172
183
173
184
它的核心概念是:
174
185
175
- 先定义好视图结构, 然后获取` 根数据 ` (树干), 再让` Resolver ` 从树干解析出来所有的数据 (树枝,树叶).
186
+ 1 . 定义好视图结构schema(从根数据的结构向下扩展)
187
+ 2 . 获取` 根数据 ` (树干), 转换成schema
188
+ 3 . 让` Resolver ` 遍历解析出来所有的数据 (树枝,树叶).
176
189
177
190
![ ] ( ./static/resolve_data.png )
178
191
@@ -182,25 +195,26 @@ async def main():
182
195
183
196
- 查询
184
197
- 用申明式的方式描述数据和查询, 直观且容易修改
198
+ - 简化` 根数据 ` 的查询, 避免复杂sql 语句对可读性的影响.
185
199
- 可以读取全局参数, 可以跨层级向下传递数据
186
200
- 任意层级, 任意类型.
187
201
- 架构简单, 各个 service 仅需提供通用的 loader, 用于数据拼装
188
- - 修改
202
+ - 调整
189
203
- 每一层都有后处理数据的能力
190
204
- 可以挑选所需的字段
191
- - 调整视图数据很容易, 让前端真正做到开箱即用 .
205
+ - 让前端开箱即用 .
192
206
- 性能
193
- - 解决 N+1 查询相关的性能问题
194
- - 对优化友好, 只要保证输出一致, 内部重构对API使用者无感 .
207
+ - 避免 N+1 查询相关的性能问题
208
+ - 对优化友好, 重构的依赖阻碍小 .
195
209
- 其他
196
210
- 借助OpenAPI, 前端对后端操作简化为sdk 方法调用.
211
+ - 借助typescript 让前后端调整变得易如反掌
197
212
198
-
199
- > 可以很容易联想到, 我们获得了一个 API 提供一个page 所需数据的能力, 这会让前后端接口关系变得更简单.
213
+ > 可以很容易联想到, 我们获得了单个 API 提供一个page 所需数据的能力, 这会让前后端接口关系变得更简单.
200
214
>
201
- > 就像前端通过 GraphQL 实现的那样, 而且更简单, 不用写额外查询, 直接一个简单请求就行.
215
+ > 就像前端通过 GraphQL 实现的那样, 而且更简单, 数据直接可用, 不用写额外查询, 直接一个简单请求就行.
202
216
203
- 下图简单的展示了组合模式的关系, 分为 service 和 router 两个部分.
217
+ 下图简单的展示了组合模式的关系, 分为 service 和 router(controller) 两个部分.
204
218
205
219
- service 负责一个个具体业务对象, 对外提供业务` query ` 以及通用的数据 ` loader ` .
206
220
- router 负责声明 ` schema `
0 commit comments