Skip to content

Commit 42ff1f8

Browse files
committed
update doc
1 parent 09b82d4 commit 42ff1f8

File tree

1 file changed

+38
-24
lines changed

1 file changed

+38
-24
lines changed

readme-cn.md

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ for t in teams:
4343
4444
过程式的数据处理对调整和阅读都不友好, 循环和拼接容易产生不通用和不易维护的代码. 添加和修改字段也很麻烦.
4545

46+
另外这种处理放在不同的分层, 影响也不同. 比如常用的 controller - service - model 三层:
47+
48+
- 如果由 model 来处理, 那么 model 层需要做修改, controller 和 service 都要跟着调整
49+
- 如果由 service 处理, 那么 service 的查询就会充满视图细节, 不容易复用, service 就变得僵化了
50+
- 如果由 controller 处理, 影响面较小, 但这种命令式的拼接本身不直观, 不好维护
51+
- 如果让 client 处理, 那会是个灾难.
52+
53+
> 面向组合模式将会在 controller 层解决这个拼接问题.
54+
4655
GraphQL 带来的通过声明描述数据结构是一个好的方向, Graph Query Language, 作为一种声明式的查询语言, 服务于一系列关系型资源的获取和组合. 其查询语句能展示期望的数据结构.
4756

4857
```gql
@@ -70,7 +79,7 @@ var rootValue = {
7079
}
7180
```
7281

73-
graphene 的会更加形象一些.
82+
借用 code first 的 graphene 的会更加形象一些.
7483

7584
```python
7685
# code first
@@ -83,7 +92,7 @@ class Query(ObjectType):
8392
return f'Hello {first_name}!'
8493
```
8594

86-
一段复杂的GraphQL query 查询结果和一段复杂的 ORM 查询是类似的. 只是GraphQL 借助dataloader更擅长关联数据. 可以更加直观地构造多层的视图数据.
95+
一段复杂的GraphQL query 查询结果和一段复杂的 ORM 查询是类似的. 只是GraphQL 借助dataloader更擅长关联数据. 可以更加直观地构造多层的视图数据. (代价是额外的查询开销)
8796

8897
GraphQL 虽好, 但完整引入 GraphQL 对架构的影响不小, schema 定义之类的都要跟随调整. 而且其自身也存在一系列的问题, 例如:
8998
- 无法描述尺寸不确定的递归结构
@@ -100,15 +109,15 @@ GraphQL 虽好, 但完整引入 GraphQL 对架构的影响不小, schema 定义
100109

101110
总体来说, GraphQL 在提供视图数据方面, 有查询灵活度高的优点, 但存在获取的数据后期调整比较麻烦, 以及架构侵入较大等缺点. 比如 GraphQL 获取到多层数据后要做层级聚合统计, 就需要重新遍历一遍树状数据来处理. 框架本身没有设计合适的下层数据处理完之后触发回调的钩子. (这恰恰是对视图调整很有用的)
102111

103-
在这里我们总结一下, 从数据获取到生成视图需要哪几个步骤.
112+
在这里我们暂停总结一下, 看看从数据获取到生成视图需要哪几个步骤.
104113

105-
1. 查询, 可能是多组数据, 也可能已经是嵌套数据
106-
2. 数据拼接 (optinal), 根据 1 来决定是否需要 (GraphQL可以很大程度跳过这一步.)
107-
3. 业务转换: 对查到的数据做调整, 比如计算数组长度, 额外过滤, 业务转换等等, 构造出前端直接可以使用的数据. (这种具体业务逻辑不适合在GraphQL中操作, 因此需要单独处理.)
114+
1. 查询: 来源可能是多组数据, 也可能已经是嵌套数据
115+
2. 数据拼接: (optinal), 根据 1 来决定是否需要 (GraphQL可以很大程度跳过这一步.)
116+
3. 业务转换: 对查到的数据做调整, 比如计算数组长度, 额外过滤, 业务转换等等, 构造出前端直接可以使用的数据. (GraphQL不擅长这块)
108117

109-
思考后会发现, 比起 GraphQL 那个灵活的查询组合功能, 在处理视图数据的时候, GraphQL 最大的优势是他申明式的数据描述方式.
118+
思考后会发现, 相较 GraphQL 灵活的查询, 在处理视图数据的时候, GraphQL 最大的优势恰恰是他申明式的数据描述方式.
110119

111-
以 graphene-python 为例, Query对象可以支持灵活的 GraphQL 查询, 比如挑选字段, 或者重命名等等.
120+
以 graphene-python 为例, Query可以灵活选择所需的字段
112121

113122
```python
114123
from graphene import ObjectType, String, Schema
@@ -129,9 +138,9 @@ result = schema.execute(query_with_argument)
129138
print(result.data['hello'])
130139
```
131140

132-
那如果我不去编写查询, 而是直接把 Query 直接变成一个服务于固定业务的视图数据描述:
141+
那如果我省去查询, 而是直接将 Query 变成一个固定的业务的视图数据描述?
133142

134-
借助 pydantic 强大的类型转换和检查的功能, 来实现申明式的数据结构描述.
143+
借助 pydantic 强大的类型转换和检查的功能, 我们可以这样来实现申明式的数据结构描述.
135144

136145
```python
137146
from pydantic import BaseModel
@@ -154,25 +163,29 @@ async def main():
154163
return await Resolver(context={'first_name': 'tangkikodo'}).resolve(hgv)
155164
```
156165

157-
**优点是把大而全的单一查询入口, 替换成了一个个小巧灵活的定制化 schema 描述.**
166+
这样, 我们就获得了一个专用的视图数据描述, 并且获得了期望的数据.
167+
168+
**把大而全的单一查询入口, 替换成了一个个小巧灵活的定制化 schema 描述.**
158169

159170
结合那三个步骤, `pydantic-resolve` 可以做到:
160171

161172
1. 查询. 可以层层查询 (dataloader), 也可以一次性从树状数据加载 (GraphQL or ORM 查询结果).
162173
2. ~~数据拼接~~
163-
3. 业务转换: 利用post_method exclude 方法, 可以在每一层灵活处理数据
174+
3. 业务转换: 利用post_method, expose, exclude 等方法, 可以在每一层灵活处理数据
164175

165176
## 什么是面向组合的模式?
166177

167-
面向组合的开发模式就是在声明式描述期望视图结构的基础上, 逐步扩展出来的一套开发模式.
178+
面向组合的开发模式就是在**声明式描述期望视图结构**的基础上, 逐步扩展出来的一套开发模式.
168179

169-
它在架构上的优点是, 通过申明式的 schema 定义, 让数据拼接过程变得更加直接和容易调整.
180+
它在架构上的优势是, 通过申明式的 schema 定义, 让数据拼接过程变得更加直接和容易调整.
170181

171-
它让 service 层保持简洁, 大多数情况下只需要提供简单的查询就能满足拼接的需要, 避免了拼接逻辑侵入到 service 的情况.
182+
它让 service 层的核心数据查询保持简洁, 使用独立的数据loader来组合数据, 避免了拼接逻辑侵入到 service 或者 controller 的情况.
172183

173184
它的核心概念是:
174185

175-
先定义好视图结构, 然后获取`根数据`(树干), 再让`Resolver`从树干解析出来所有的数据 (树枝,树叶).
186+
1. 定义好视图结构schema(从根数据的结构向下扩展)
187+
2. 获取`根数据`(树干), 转换成schema
188+
3.`Resolver`遍历解析出来所有的数据 (树枝,树叶).
176189

177190
![](./static/resolve_data.png)
178191

@@ -182,25 +195,26 @@ async def main():
182195

183196
- 查询
184197
- 用申明式的方式描述数据和查询, 直观且容易修改
198+
- 简化`根数据`的查询, 避免复杂sql 语句对可读性的影响.
185199
- 可以读取全局参数, 可以跨层级向下传递数据
186200
- 任意层级, 任意类型.
187201
- 架构简单, 各个 service 仅需提供通用的 loader, 用于数据拼装
188-
- 修改
202+
- 调整
189203
- 每一层都有后处理数据的能力
190204
- 可以挑选所需的字段
191-
- 调整视图数据很容易, 让前端真正做到开箱即用.
205+
- 让前端开箱即用.
192206
- 性能
193-
- 解决 N+1 查询相关的性能问题
194-
- 对优化友好, 只要保证输出一致, 内部重构对API使用者无感.
207+
- 避免 N+1 查询相关的性能问题
208+
- 对优化友好, 重构的依赖阻碍小.
195209
- 其他
196210
- 借助OpenAPI, 前端对后端操作简化为sdk 方法调用.
211+
- 借助typescript 让前后端调整变得易如反掌
197212

198-
199-
> 可以很容易联想到, 我们获得了一个 API 提供一个page 所需数据的能力, 这会让前后端接口关系变得更简单.
213+
> 可以很容易联想到, 我们获得了单个 API 提供一个page 所需数据的能力, 这会让前后端接口关系变得更简单.
200214
>
201-
> 就像前端通过 GraphQL 实现的那样, 而且更简单, 不用写额外查询, 直接一个简单请求就行.
215+
> 就像前端通过 GraphQL 实现的那样, 而且更简单, 数据直接可用, 不用写额外查询, 直接一个简单请求就行.
202216
203-
下图简单的展示了组合模式的关系, 分为 service 和 router 两个部分.
217+
下图简单的展示了组合模式的关系, 分为 service 和 router(controller) 两个部分.
204218

205219
- service 负责一个个具体业务对象, 对外提供业务`query` 以及通用的数据 `loader`.
206220
- router 负责声明 `schema`

0 commit comments

Comments
 (0)