【开源自荐】pydantic-resolve,基于graphql 的理念做取舍后的产物,服务于精准构建满足client 需求的视图数据
pydantic-resolve: https://github.com/allmonday/pydantic-resolve
graphql 作为构造前端视图数据的主流方案之一,在查询关联数据和挑选等方面提供了巨大的便利。
但同时,其也存在一些弊端: (以下简称gql)
- 如果是服务于client的具体页面/功能,通常到最后会写出一系列固化的schema来搭配不同的页面, client使用固化的query。 这种情况下就没有 restful 或者 rpc 来得方便 (基于openapi 我们可以在前端直接生成sdk)
- 后端开发难度加大,graph 的结构,让节点调整特别困难, 无法判断是否存在查询的依赖。 另外项目迭代较频繁的时候,前端持有的过时的query 是迭代中的负资产,存在维护成本等 (难度问题有很多文章描述, 不一一列举)
- gql 本身层层往下获取数据的行为,存在局限性, 无法完美构造出需要的视图数据, 比如做下层数据的向上聚合计算, 或者根据业务需求做额外调整
被以上问题折磨了一阵子之后, 痛定思痛, 开始反思使用gql 的目的究竟是什么,构建视图数据的本质是什么? 于是得出了一个结论:
构造面向client 使用的视图数据,这个场景下,graphql并不完美。
从职责分割的角度来看, 前后端的分工分界线应该是数据, 后端专注在数据构造, 前端专注在展示和交互。 因此前端维护gql 的query 是个累赘 (重新强调一下,仅仅是针对client视图数据这个具体场景,gql query用于维护良好的公共接口查询有巨大优势),需要使用直接请求来代替(搭配ts sdk)
改由BFF或者后端来提供固定接口。
此时, gql 中申明式描述schema 就闪耀出来了它的价值。让我们先忘记gql 的条条框框,回归本源思考一下怎么才能灵活地构建视图数据?
简单来说, 从核心数据到视图数据,需要有这几个步骤:
- 定义核心数据和关联数据 (通过schema 来描述)
- 通过dataloader 获取关联数据(层层下推, 这是gql 所作的)
- 通过post-process, 在子孙数据获取完毕之后,可以再次进行处理 (数据修改,这个gql 无法做到, 也是核心痛点)
- 把不需要暴露的字段做隐藏
第三步骤正是gql 最大的痛点, 没法通过遍历的回溯阶段优雅的调整数据(做聚合, 做转换,等等),因为子孙数据还没获取到, 没法提前做计算。
为了满足上述四点要求, 开发了 pydantic-resolve,满足了上述所有要求, 成了构造视图数据的利器。
项目受到 graphene-python 启发, 使用了 resolve_field 的形式来定义获取数据的过程, 故取名pydantic-resolve. 在此之外:
- 使用了 post_field 在回溯阶段执行调整
- 提供了exclude 方案来过滤中间计算的字段
- 大幅强化了dataloader 的使用便利性
- schema 互相独立,减轻了后端维护成本
- 结构简单,只要使用schema 描述就能实现gql 相同的输出。
另外,pydantic-resolve 的功能虽然简单, 本质是个广度优先,带有回溯的walker,但结合不同的用法, 可以玩出许多的花样。
因此特意整理了一份使用指南以供参考: https://github.com/allmonday/composition-oriented-development-pattern/blob/master/readme-cn.md