V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  tangkikodo  ›  全部回复第 1 页 / 共 2 页
回复总数  31
1  2  
@AloneHero

这段我没描述清晰, 这两个 comment count 是为了比喻某种前端视图需求, 拿到了数据之后需要做二次统计聚合, 或者干脆要自己转换一把。 “解决 gql 不擅长的后处理环节” 是 gql 不适合前后端紧耦合这个大问题下面的子问题。


------ “那是不是某个字段的逻辑有可能分散在很多字段里面?这样可维护性会不会很容易遭到破坏?”

你对 gql 很熟悉, 会想到这个问题,我猜是从 qgl 中 schema 是复用的情况下产生的疑问。在 resolve 的模式下,schema 是通过继承**核心数据**的结构, 来保证每个结构都有一套自己的描述。( pick 数据也能支持)
比如一个页面用到的 schema , 是在同级目录下按需申明出来的, 所以后处理的依赖关系挺清晰的。

比较真实的例子可以看

- https://github.com/allmonday/composition-oriented-development-pattern/blob/master/src/router/sample_1/schema.py 介绍了使用继承来“clone" schema 的做法。


我在“优势” 中说,gql 和 restuful 适合提供公共接口, 是因为公共接口往往设计合理,也不用考虑太多视图层数据的特殊展示需求。gql 根据查询来驱动的特性决定了这种后处理字段的依赖关系会造成混乱(单独查询 count 总不能暗搓搓去触发 blogs 吧),gql 构造业务向的数据,可能的后果是, 提供了 90%前端所要的结构, 但剩下 10% (不准确,比喻)涉及到后处理相关的,gql 做和前端做, 都挺麻烦的。 再加上前端需要随时跟着后端调整 query ,形成了迭代中的负担。

pydantic-resolve 的例子里面, 可以理解为, 前端描述的 query 直接固化在了后端 schema 上。

这时前端只要向 rpc 那样直接`getSiteData` 就能拿到视图数据, 如果迭代中出现需求, 需要在这个视图数据上添加额外数据,比如后处理按照层级统计 count , 或者把结构做转换, 后端都很容易,并且改完之后前端同步一下就能感知。

里面比较极端的例子就是文末彩蛋处理 tree 的层层聚合。

pydantic-resolve 的角色是扮演好防腐层, 提供各种功能来做好“数据获取和转换” 这个目的, 然后尽可能避免 service 做调整。
@qwerasdf123 这份执着也真的只能佩服
@StarkWhite 复杂度不能消灭, 只能控制。

从关注点分离的角度, 以及自己的体会来看, 这块复杂度我希望放在后端来管控
而“前端自己要做数据转换”, 这件事在前后端分离的项目中, 就是容易积累“技术债” 和 “遗留代码” 的地方。
@StarkWhite gql 本身没问题, 问题出在 gql 查询到的字段要转换成前端直接可用的结构, 往往是会有落差的。
因为后端结构相对固定, 但是前端视图数据却是各种天马行空。

如果不是具体专供的 gql 接口的话, 前端做转换的工作量一般都是存在的。
如果是专供接口的话, 那前端重写一遍 query 就有点多余。

这是我使用的一些感受~
@qwerasdf123 在项目中体验过 gql 之后,得出了这个工具,要用也是应该放在后端代理查询, 用最简洁的形式和前端做沟通。 这样如果请求有性能问题, 后端也有足够的方案来优化, 代替。

以一个个功能明确的 API 的方式, 类似 gql 查询, 但是前端不同提供描述, 如果有 ts sdk 传递类型信息会更好。

如果是 python 后端的话, 推荐一把 pydantic-resolve ,面向前后端一同迭代的场景,通过构建前端恰好可用的视图数据, 让前端专注在展现和交互功能上。
@qwerasdf123 是, 打着“不用对接” 的旗号, 其实挖了个深坑
@StarkWhite

fix

```graphql
query {
MyBlogSite {
name
blogs {
id
title
comments {
id
content
}
comment_count # comments count for blog
}
comment_count # total comments
}
}
```
@StarkWhite 两个都挺重要,sql join 负责 db 的查询,dataloader 负责 db 或者 其他远程调用。

这个 join-monster 看起来和 https://postgraphile.org/ 之类的概念很像, 从 gql 查询调用 db 查询

可是。。这不太适合直接给 client 用的, 太灵活了, 自己的项目用用倒是可以简化一些步骤。

graphql 对前端有个小麻烦,就是遇到数据层级聚合的场景, 前端要么依赖 gql-lodash, 要么自己手动拆了算。

感觉问题的本质还是,gql 或者 apijson 或者 orm 这些都是负责一层层找到数据, 不负责数据后续转换。

比如获取完数据之后做点后处理, 计算全部每个 blog comment 数量, 或者 site 所有 comment 数量

这种 gql 内部做的话, 相当于整个 node 要全部预计算完了。

query {
MyBlogSite {
name
blogs {
id
title
comments {
id
content
}
comment_count # comments count for blog
}
comment_count # total comments
}
}

当然,这些是属于某种深水区的使用困扰 :<
@StarkWhite 说到 n+1 和 dataloader 感觉现有的 在 context 里面集中初始化的写法,维护起来稍嫌麻烦, 不能放开手脚随便定义
@StarkWhite 确实,追写生成器帮助下,这个还是好解决的。
我感觉疼的还是 client 整的查询比较魔幻的时候的性能问题。 约等于丢失了慢查询的调优空间。
@StarkWhite 看迭代的变化程度, 如果是增减字段还是 ok 的, 如果接口要做大变化的话, 我感觉维护 query 也是额外成本。

给 client 一种这么灵活的工具, 有时搞不好会玩出花最后不好维护。 比如本来可以通过一个新接口+过滤来获取的数据,client 为了图方便避免沟通, 走了一个绕路的查询, 而路径长的查询又可能引起性能问题。
@Mithril 找到看法相同的了~ 握手

作为一个查询语言,gql 是个后端请求数据聚合的好帮手 (但也有问题, 比如做层级聚合)

但把这查询语言暴露给 client 就离大谱了 (除非做 github, jira 这种固定需求的 client )

这约等于后端把业务处理的控制权分散了出去,client 拼个夸张的 query 之后, 过来说功能交付了, 但是性能不行, 你帮我优化优化。 这可就刺激了。

gql 也好,orm 也好, 都只做了层层向下关联数据的事情, 并不负责从查询到的数据, 转换为前端所真正需要的视图这一块。

如果使用 python 的话, 可以尝试一下 pydantic-resolve. https://allmonday.github.io/pydantic-resolve/dataloader/

解决的就是这种,即负责关联数据的获取, 同时还能处理数据转换,然后再使用最朴素的传统接口返回。
@StarkWhite gql 社区资源是真的丰富

不过 gql 拿来给 client 提供 api 是会有点问题的, 除非是 api 先行, 开发完了也不用调整的这种项目。

否则迭代也会疼
apijson 或者 graphql 作为查询层的功能, 放在 client 很容易出现不合适的情况。

个人觉得放到后端或者 bff 层, 保持独立固定的接口, 才能做好总体业务的维护。
@frankies 流量为王的时代, 正向口碑和反向口碑, 反正能引流, 吵架也能引流
作者在营销上, 算是豁出去了。

现在前后端分离上很多方案都点歪了科技树, 前端总想多做点业务层的事情, 后端总想给点万能接口...

但实际上, 前端,或者说 UI , 应该只是业务数据的一种具体实现, 是核心业务的外层 presenter 层, 讲道理就是应该稍作业务逻辑处理相关的事情的。
都暴力推荐了, 那就顺带也推荐个后端驱动的 API 构建工具吧~~
肥肠小巧 https://allmonday.github.io/pydantic-resolve/
57 天前
回复了 metavoidx 创建的主题 Python 写了一个 Python 后端元框架: UtilMeta
@metavoidx
同意的, 我其实因为是个 sqlalchemy 苦手, 感觉配置 relationship 之类的太繁琐了,就刻意从这层脱身出来 囧
59 天前
回复了 metavoidx 创建的主题 Python 写了一个 Python 后端元框架: UtilMeta
hi, 我是 pydantic-resolve 作者(也是那篇 composition oriented development pattern 的作者) , 在声明式描述数据这点上我很赞同你的观点, 我观点上的一些分歧在于是否应该和 ORM 关联的紧密,下面是我的解释。

首先,既然用申明方式来生成数据, 目的肯定是为了更加优雅的组合数据

一般来说, 后端生成一个前端直接可用的视图数据包括几个步骤。
1. 数据获取
2. 数据组装 (utilmeta 把 1 ,2 两个环节一次性解决了)
3. 面向业务进行转换 这么几个步骤。

(一般情况下 2 ,3 可能会混在一起)

我选择使用 dataloader 的原因是:

通过 ORM 获取关联数据的手段之一,这个行为如果用通用的描述来说的话, 类似于:

def batch_query(keys, filters) 然后把查询结果绑定回每一条父记录。

这样在一对多查找的时候,既能使用外键, 还能指定额外过滤条件
https://github.com/allmonday/composition-oriented-development-pattern/blob/master/src/router/sample_2/readme-cn.md

( ORM relationship 上添加额外条件的查询写起来通常都会比较繁杂)

也就是说,loader 的内部实现可能是一段查询, 也可能是一个 rpc , 也可能是缓存查询 等等

这样做的好处是隔离了具体实现, 假如我要做服务拆分的话, 就能将封装的查询,转成一个 rpc 调用了。

关于 3 , 数据转换, 这也是直接从 ORM 拼装数据可能存在的一个小问题, 如果视图数据需要对视图数据的中间, 或者底层的数据做些转换, 聚合计算之类的功能, 很多时候就不得不拆开来遍历计算了。

pydantic-resolve 的思路是选择使用 post_method 来在每层提供一个 after resolved hook 来操作数据。 用来处理层级聚合, 字段转换会比较方便, 也符合声明式的风格。
https://github.com/allmonday/composition-oriented-development-pattern/blob/master/src/router/sample_4/readme-cn.md

说了一些拙见,框架本身是服务于特定目的,

utilmeta 这样的申明式方法带来了很好的灵活性和使用便利。

而且被你详细的文档和丰富的 example 给震撼到了!

祝蒸蒸日上
https://github.com/allmonday/pydantic-resolve 可以试试, 用 RESTful + 申明式的类型描述来生成组合数据。
1  2  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3146 人在线   最高记录 6543   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 20ms · UTC 14:37 · PVG 22:37 · LAX 07:37 · JFK 10:37
Developed with CodeLauncher
♥ Do have faith in what you're doing.