这篇文章简单来总结一下,这些天调研 GraphQL 的感受,以及它跟 RESTful 的优缺点对比,顺便征求一下社区大佬们的意见,你们给自己家的项目上 GraphQL 了吗?原文链接: https://www.lanzhiheng.com/posts/graphql-vs-restful
近期公司的业务需要扩张,准备要做 APP,另外就是 ActiveAdmin 所写的后台已经让自家人多少有些不适,也是有重写的打算。跟前端小伙伴讨论是否需要前后端分离的时候,前端小伙伴强烈要求分离,理由如下
我记得社区里面有人针对这个事情回答过
自己项目的话不分离,公司的项目果断分离,因为自己没时间写。
既然如此,那还是分离吧。不过分离之余又面临另一个抉择。我们是要沿用 Rails 官方拥抱的 RESTful 模式的 API,还是尝试一下近几年兴起的 GraphQL?这是一个问题。抛开后台不谈,如果要在 APP 上尝试 GraphQL 的话,那么之前 RESTful 写给小程序的东西可能得重新写一遍。这....还是得多想想,下面来看看 GraphQL 所带来的好处是否值得我们花这个时间去折腾。
似乎每一个新技术的出现都会在文档里面鼓吹自己有多好,别人有多不好。先来看看 GraphQL 有多好吧。
相比起 RESTful,即便要获取多个不同资源的数据,前端只需要调整自己的查询字符串就能只通过一个请求获取对应的数据。假设我一个页面只需要文章Post
的数据,以及数据库里面所有的分类Category
的数据,还有当前用户的数据那么我们可以直接这样去查询
前端拿到数据之后可以自己去解析,原来需要三个请求去完成的工作现在一个请求就搞定了,这咋一看还是蛮吸引的。前端的同学估计已经受够了一个页面(特别是首页)要调用十几个请求,并且还要写十几个回调函数去解析数据的日子了。
GraphQL 这套查询方式正好解决了他们这个难处,只要后端定义好相关的动作,他们只需要适当组装一下请求字符串,就能够获取自己需要的所有资源数据,并且只需要向服务器请求一次。比起要请求服务器多次的 RESTful 模式,这种方式固然节省了不少的请求量,请求量少了,页面加载速度自然要快一些,这点无可厚非。咱们暂不反驳,先继续往下看。
官方说,利用 GraphQL 前端可以自定义数据的返回,后端只需要前期做好定义,后期不用做任何改动。一般来说前端通过接口能获得什么数据都是由后端来决定的,而后端工程师为了减少任务的反复,一般都会返回比前端所需更多的数据。比如:在某个页面前端只需要用户的id
与email
,如果是 RESTful 接口的话,为了接口能够更加通用,它可能还会携带别的信息
{
"id": 1,
"email": "[email protected]",
"gender": "male",
"role": "specialist"
}
哪怕gender
跟role
在这个页面完全就用不上。而 GraphQL 的做法是,前端可以以查询字符串的方式告诉后端我们只需要某个资源的 XX,YY,ZZ 字段,麻烦给我们返回一下,其他的就别给我了。像下面的文章资源,我们通过查询字符串只获取了id
跟title
其他就不需要了
想要更多,则自己往里面加字段就行
这么做的好处是,后端只需要返回前端所必要的数据,减少请求响应的数据量。
一开始看 GraphQL 的时候,对那个内省 (Introspection) 系统是有些懵逼的,在编写 RESTful 接口的时候,我们一般都会通过 Swagger 之类的工具来给前端文档,前端可以根据这个文档来进行接口对接,并且可以在 Swagger 的页面上进行调试。
而在 GraphQL 里面似乎就不用这样子了,我个人感觉它有点像 Redux,后端定义好所有的动作,前端可以通过内省系统查询到这些动作,然后告诉后端系统,自己要执行哪个动作,后端系统就会调度对应的动作,完成前端交代的任务,并返回结果。
在 GraphQL 里面,动作主要分两种类型
可以这样查询出他们两的名字跟描述
如果我们想进一步知道,后端所允许的查询动作,可以进一步去查找。从前面的结果可以知道,查询调度器的名字是Happy,看看它旗下有哪些“艺人”。
可以看到它支持的查询有Post
, allPosts
, Category
, allCategories
等等,还能查看到他们分别接收的参数,这些都是后台定义好的。只要运用熟练,这其实就相当于一份 API 文档,还是蛮方便的。
我不确定大家是否喜欢上面这种模式,反正我本人不是很喜欢,对我来说,似乎有点过早优化了。前端许多社区似乎都有这样一个问题,Demo 看起来很无敌,实际应用的时候要你命。
按 GraphQL 这种说法,前端能够定义自己需要的数据,在一定程度上能够减少响应的数据量。然而,请求的数据量你有没有算进去?用 GraphQL 获取一个人的基本信息的时候大概是这个样子
// Post /graphql
query {
user(id: 1) {
name
gender
avatar {
url
signed_id
}
....
}
}
不过 RESTful 的话,直接/users/1
就好了吧。要是我真的需要一个用户很多数据的话其实 RESTful 更方便。发送请求要容易许多,而 GraphQL 需要自己去定制前端所需要的每一个字段。用我前同事 Hugo 的说法是
这个地方有一个临界点,当 RESTful 一个接口返回很多前端用不着的字段时,才会节省数据量。
其实真的出现这种极端的情况,或许我们
/users/1/simple
的接口,只返回精简的数据。Why you shouldn’t use GraphQL 这篇文章提到了一个我挺认可的说法
GraphQL is an alternative to REST for developing APIs, not a replacement.
试想要是一个页面(比如落地页)需要调用十几二十个接口,你用 GraphQL 的话,确实是可以一个接口拿到所有的数据。举个例子,假设要填充首页需要有 10 个数组,那么如果用 RESTful 的话我们调 10 个接口,而 GraphQL 的话大概这样就能搞定
// Post /graphql
query {
products1 {
name
}
products2 {
title
sku
}
..... // 还有8个类似的东西
}
不过兄弟,这还没完,如果这 10 个数组是需要分页的呢,那是不是有趣一些了?这种时候如果用 RESTful,各个组件会分别调动指定的接口,获取更多对应资源的数据。如果你用 GraphQL,那么你几乎不可能沿用上面的查询字符串,因为你预先并不知道哪个资源要获取更多数据。所以针对分页的情况你可能还要针对不同组件维护对应的查询字符串。大概是这样
// Post /graphql
query getProducts1($page: Int!){
products1(page: $page) {
name
}
}
// query variables
{
"page": 2 // 取决于第几页
}
以上模板还要写 9 个类似的,当然你也可以用工厂函数,然而这种情况用 GraphQL 似乎也没省多少事,就首次加载的时候 RESTful 要调 10 个接口,用 GraphQL 一个接口就搞定了,能节省掉一些请求。Emmmmmm,我针对这种情况用 RESTful 写一个专门的首页加载接口似乎也行?或者只针对首页加载的情况使用 GraphQL 技术,其他地方依旧用 RESTful?
GraphQL 有个特点,就是请求链接都是一样的,而且全是 POST 请求,另外,正常来说所有请求不管成功失败都会返回 200。如果不点开请求详情,你根本不知道这个请求干了什么,下面这张图就比较生动了
链接都一样,只通过定义不同的动作,以及相关的参数来做不同的事情,或者说获取不同的数据。这样后端只需要定义好能用的那些动作,从此“高枕无忧”。反正我玩了好几天,越搞越纠结。RESTful 有个比较舒服的地方就在于,增删查改,弄得清清楚楚,利用了GET
,POST
,PUT
, DELETE
这些语义化的动词,找问题要方便得多。并且对不同资源的操作会有不同的 endpoint(可理解为不同的 url),这样辨识度会更高。而 GraphQL 所有请求都是是统一入口的(假设是/graphql
),只是利用不同的查询字符串来做不同的事情。
// 查询产品
query {
product(id: 1) {
name
sku
}
}
// 删除产品
mutation {
removeProduct(id: 1) {
product {
name
sku
}
}
}
product
, removeProduct
这些动作都后端定义好的,前端只需要告诉后端自己要干嘛,期望返回什么东西即可。只不过接口调用的结果,你只能自己查看响应内容才知道了。
可以看到,只要动作被执行了,哪怕是出错,接口都会返回 200,返回结果会有errors
的字段,附上一堆错误消息。反正个人还是觉得 RESTful 这种能够通过不同状态码,还有请求类型来判断接口性质的做法比较“人性化”。
其实写 GraphQL 接口的时候我已经忘记了自己在写 Ruby 了,哪怕 Ruby 本身是强类型语言,但是我们也不用凡事都去定义类型啊。而 GraphQL 感觉就是一套类型系统构建起来的东西,你得以声明的方式去定义很多的类。这当然也有它的安全性,起码我接收个参数,外层机制就能告诉前端,你传的类型不对。
不过也有难搞的地方,代码的方式是声明式的,前端能够获取什么数据都是后台来决定。比如,一个用户有name
, id
, gender
这三个字段,如果后端限制你只能访问id
跟name
,你就不能访问gender
。幻想一种情形,一开始后端给前端暴露了id
, name
, gender
三个字段
module Types
class PostType < Types::BaseObject
description "Post Type"
field :id, ID, null: false
field :name, String, null: true
field :gender, String, null: true # 后面会删掉
end
end
前端也应用了这几个字段
query {
user(id: 1) {
id
name
gender
}
}
某天一个不知情的后端把gender
给删掉了,那么前端调用接口的时候就会直接报错,对于线上环境来说这还是挺致命的。毕竟它可能一个页面只发送了一次请求,所以请求中某个资源获取失败,会导致页面其他的资源也获取不了,前端页面有可能直接瘫痪。
官方倒是提过这个问题,它称之为Breaking Changes。它也提供了一些解决方案
GraphQL::SchemaComparator
在跑 CI 的时候检测新的提交是否有Breaking Changes。当然我相信适当的测试用例也能避免这种情况的发生,只是笔者当时就想:“这么折腾到底我还要不要用它?”
公司的后台用的 Rails,原生就比较亲近 RESTful 的做法,硬要上 GraphQL 也不是说不行。只是总感觉有那么点“不适应”---好吧,我承认是非常地不适应。
不知道社区的各位怎样,反正笔者是觉得越写越别扭了。似乎省下的时间都可以慢慢地优化以前的接口了。
经历过上面的考量,还有近期的实践之后,我觉得 GraphQL 我已经入门了,可以放弃了。于是乎跟前端小伙伴进行了一次灵魂的对话
我:“我感觉 GraphQL 有以上的 xxxx 问题,我们到底要不要用?” 前端小伙伴:“感觉增加了一堆的工作量,好处就.....” 我:“好处只增加了一丢丢?” 前端小伙伴:“不,目前都没感受到有什么好处。”
前几天我还在犹豫是否要在公司项目的新功能里面引入 GraphQL,现在基本是可以下定决心不用了。我并不否认,GraphQL 减少请求量,自定义数据返回这些好处都十分诱人,或许对于 Facebook,Github 这种量级的公司来说是个不错的选择,然而对我们这种初创公司来说真的经不起这般折腾,而且个人觉得有点过早优化了,省点时间跟朋友吃吃饭好了。
反正我是放弃了,不知社区的朋友怎么看,你们开始用 GraphQL 了吗?