基于 GraphQL 实践的一点思考

10,959 阅读8分钟

hello~亲爱的观众老爷们大家好~最近一直沉迷于 GraphQL 的应用实践,正好公司黑客马拉松临近,就拉上了两位小伙伴,结合实际的业务场景,把 GraphQL 作为中间层的解决方案提上去~项目完成度还算不错,对 GraphQL 也有了更深入的理解,在此记下整个过程的收获。

注意~这篇文章不是学习 GraphQL 语法的文章,语法学习可左转传送门。但如若你对 GraphQL 有一定的了解,想知道实践中可能碰到什么问题,那估计本文能给你带来一点帮助~以下是正文:

业务中的困局

既然要用 GraphQL,那还是要先了解它到底是什么:

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

看起来没什么复杂的地方,也许你看过 GraphQL 的相关介绍,会认为它的语法很繁杂(这我倒是不否认),但归根到底,它就是为了查询而生。本质上就是丢一段东西给服务器,服务器解读这段东西后,把对应的数据丢回来给你而已,所有的语法都为此而服务。

简单介绍完 GraphQL,也要简单介绍一下业务的背景~我们组真正的后端服务是用 C++ 进行编写的,有一层很薄的 Go 作为网关与中间层,负责沟通页端与 C++ 后端,同时提供解密、鉴权 等基础服务。但由于业务的瓶颈不在前端(也导致话语权比较小),导致 C++ 后端经常表现得不那么“业务”,也不认为为前端提供服务是工作的关键之一。导致他们对于很多后端能做与应该做的事情视而不见。后端隐含的态度基本是:“又不是不能用~”。但这对前端的代码质量与研发效率造成了负面的影响。

站在前端的角度,现在的系统存在以下问题:

  • 接口冗余,影响页端性能;
  • 数据体不规范,不同接口相同的 value,key 可能不一致;
  • 接口文档维护困难,文档中 key 的大小写、value 的类型经常不一致,修改字段后文档也存在滞后性;
  • 无法提供 Mock 服务。

由于各种原因,推动后端改变比较困难,那就自己重新架一套吧。新的中间层,使用 TypeScript 开发,选用了 Egg.js 为框架,魔改了 egg-graphql 这个插件作为 GraphQL 服务的基础。提供实时规范的接口文档、0 配置 Mock 服务、动态配置且热更新等功能。

以下是功能与使用前后区别的一些截图:

接口文档:

配置后台:

页端使用 GraphQL 之前:

页端使用 GraphQL 之后:

(除了合并请求之外,不妨心算一下响应体大小区别)

新服务带来的变化

相信大家对 GraphQL 的优点已了然于胸,通过上文对新的中间层的描述,也能感受到 GraphQL 的优点。这里就不凑字数去详述节省的带宽、性能的提高及其它为前端带来的其他便利。这里主要想讨论一下的是,归根到底,我们是希望解放生产力,那么 GraphQL 解放了什么?

我们认为:GraphQL 能减少前后端矛盾,让彼此更专注自身!

尽管上文吐槽了一把后端同学,但换个角度上,他们的做法无可厚非。接口单一、专注是合理的设计,冗余的接口是因为版本与多端需要,Mock 服务更是后端无关的。无论前端还是后端,都是站在自己的立场去思考业务,并根据自身的技术为业务提供解决方案。但是业务是一个整体,程序员却被分为了前后端。两端中间的灰色区域,尽管能提高某一端的研发能力,但对另一端而言,却是相当麻烦的。那么:

All problems in computer science can be solved by another level of indirection.

既然灰色区域难于处理,那就再抽象一层!GraphQL 应运而生~它整合与提供了大量的功能,其中包括但不限于:文档服务、Mock 服务、单一入口、去冗余、数据聚合等功能。解决了前后端的矛盾,后端可专注于自身,提供稳定而健壮的服务,不再关心前端 UI 所需数据及其意义;前端专注于页面与交互,按需获取对应数据,接口稳定、文档清晰。

个人认为,GraphQL 根本的优点是:让各端更专注自身,更有效地解耦彼此。

阳光下的阴影

既然有光,那么肯定会有影子,以下是劝退使用 GraphQL 的时间(笑)。由于我是一个相当怕麻烦又容易放弃的人,每当我碰到表现比现存系统更差或实现起来相当麻烦的时候,就会不自觉地思考新的技术是否适用于当前场景。以下是实践过程的一些思考(已经明确的缺点:如缓存、n+1 的问题等在此不再详述):

中间层使用 GraphQL 可能是一个伪命题

GraphQL 很好,它清晰的标识了每个字段的类型与含义,以此为基础扩展出文档服务、Mock 服务、去冗余等功能。但成也萧何败萧何,作为中间层,一般是比较轻薄的,一般只是校验与动态转发内容即可,然而 GraphQL 其实相当重的。这导致了后端改字段的时候,不单后端要发版,中间层也需要发版。为解决这个问题,我们想方设法对 GraphQL 服务进行动态配置及热更新,尽管实现了这一功能,但里面使用的都是 JavaScript 的黑魔法,如何迁移到其他语言(如 Go,公司在 Go 语言运用上已经比较成熟),说实话,我真不知道。而对于合并请求优化性能而言,GraphQL 也不是唯一的解决方案,Http2 一样能解决问题~

BFF 中尴尬的处境

如果不将 GraphQL 作为中间层转发的技术,而作为 BFF(Backend For Frontend)中请求的主要查询语言,则会碰到一个比较尴尬的场景。在 BFF 中,前端掌握了服务器,那么接口的质量其实前端是能把握的。此时 GraphQL 只是一种选择,它并没有解决太多的问题,但它的学习与接入成本又不低。多少有点高不成低不就的感觉。

接入成本的评估

此处的接入成本,不单指编写 GraphQL 相关代码的成本,还包括如何接入到公司现行系统的成本。由于 GraphQL 获取数据的方式,存在瞬间请求大量数据的可能,很容易拖垮数据库,然而请求的人却对此一无所知。这有点像当初邮件系统刚出的时候,用户往邮件中扔视频文件一样,尽管是无意的行为,却对系统造成极大的伤害。同时,谁去接也是一个值得商榷的问题。说到底,这是“爽”前端的技术,后端同学对此兴趣乏乏。然而接入需要相关的开发与运维能力,前端同学不一定有足够的能力去接,毕竟不是每一位同学都是全栈。其他的问题还有 rpc 接入、权限控制等等。在方案落地之前,这些都需要仔细进行评估,不能盲目“追新”。

小结

综上所述,GraphQL 可以为我们带来很多便利,但它的接成本并不低。个人认为,GraphQL 并不是从根本上解决某个问题,而是在前后端之间几个龌龊的地方进行了改进。在考虑是否使用 GraphQL 之前应进行多方面的考量。它比较适合以下的场景:

  1. 技术包袱比较小且业务不断迭代。
  2. 业务接口十分复杂或多版本兼容的场景(如报表系统、基础的万用接口等)。
  3. 接口细碎,但又对网络十分敏感的场景。

最后感谢 @Scott 相关的指导,尽管你给的建议每一条都用了其他方式去实现(笑)~但无疑是开阔了我们的眼界。

以上是个人的一点浅见,感谢各位看官大人看到这里。知易行难,希望本文对你有所帮助~谢谢!