GraphQL 探索之路

GraphQL 一直是热度很高的技术,就以技术社区掘金上的数据为例,GraphQL话题的关注者是 8429,文章数量是303,远超 RESTful 的 2366 关注者和 74 篇文章。

官方的描述语也很吸引人,将 Graph QL 描述成一个万能接口,想要什么格式的数据可以直接通过查询语言去取,不需要再请求后端工程师进行修改。

基于以上种种“广告”效应,我们团队也开始在小项目上尝试使用 GraphQL。

架构选型

使用 GraphQL 有两种架构模式:

  1. 直接用 GraphQL 取代 REST API。这种方式对代码的改动比较大,但也更为彻底。但对于后端工程师来说有一定的学习成本。
  2. 重新搭建一个 GraphQL 服务器作为 API 网关,用来转发请求给 REST API 服务端,这种方式不会改动已有代码。但项目的复杂度会增加,因为新增了 API 设计风格和网关服务器。而且也会增加前端工程师的工作量,前端要一边和后端调试 REST API 还要一边和前端调试 GraphQL API。

由于并不希望增加项目复杂度,所以采用了第一种架构模式,即将已有的 REST API 改成 GraphQL API。

GraphQL 生态

初次通过官网了解 GraphQL,考虑到后端库支持十几种语言,觉得适用性应该不错。而且关注者众多,文章分享也不少,遇到问题时解决方案应该很多。

但实际使用后,这种感觉发生了反转。

首先虽然支持多种后端语言,但只有 graphql-js 是官方的亲儿子,其他的都是第三方开发的,代码质量和后续的维护性都令人担忧。

这不是重点,重点是这些后端库中,只有 JavaScript 和 Java 支持 SDL,其它的都要通过类的方式模拟实现。对比一下就可以感知两者的差异了,下面是 JavaScript 和 Python 的示例代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// JavaScript
var schema = buildSchema(`
type Query {
hello: String
}
`);
var resolver = { hello: () => 'Hello world!' };

# Python
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="World"))

def resolve_hello(self, info, name):
return 'Hello ' + name

在 JavaScript 中,我们可以以字符串的形式定义 schema,然后编写类型对应的 resolver,来告诉 GraphQL 怎么返回相应的数据。而在 Python 中必须以类的形式定义类型,而且 resolver 必须和类型字段对一一对应。
这就带来了两个问题:

  1. 编写繁琐。当遇到字段较多的复杂对象时,你是愿意一个个字段的编写 resolver 函数,还是愿意一次性将整个对象结果返回?而且就以常见数据库CRUD操作的情况而言,一次性返回整个对象更符合实际开发场景。
  2. 不可移植。如果采取字符串形式,schema 可以被抽取成 gql 文件进行复用,还可以很方便地被移植到其它支持 GraphQL 语法的语言或工具上,但 Python 这种和语言绑定的写法就没法办到了。

正是考虑到这一点,还是改为了使用官方推荐的 Node.js 来重构项目。

没有 apollo

目前来看大部分 GraphQL 项目都使用 apollo,apollo 支持平台比较多,文档也算丰富,基本上能做到开箱即用。

但尴尬的是阿里云函数计算并不支持,所以只能通过更底层的 graphql-js 来实现。带来的麻烦就是查找资料相当不方便,大部分文章都是基于 apollo 使用的经验分享,对于 graphql-js 的讨论则很少。

比如在开发中碰到一个了小问题,就是需要在查询时提交参数。
使用 apollo 时,前端可以很简单地在 payload 中提交一个 JSON 对象,query 属性值为查询字符串,variables 属性值为参数对象。

1
2
3
4
{
query: "{ user($id: ID!) { name } }"
variables: {id: "1231232"}
}

后端处理逻辑都被 apollo-server 封装好,不用进行任何处理,定义好 schema 和 resolver,apollo 会自动处理。

但由于不能使用 apollo-server,所以必须手动解析 query 和 variables。搜索 graphql-js 官方文档得到只是如何使用 variables ,完全没提到服务端如何解析。网上文章大多也是把官网的内容抄一遍。

后来查看 apollo 源码知道答案,原来还是依赖 graphql-js 的本身的 API。

1
2
3
4
5
6
7
graphql({
schema, // schema定义
source, // 查询语句
variableValues, // 前端参数
typeResolver // 解析函数
})
})

虽然只是一个小问题,但也体现了文档的不完善和解决方案的缺乏。

总结

从代码仓库 graphQL-js0.1.1版本发布时间来推算,GraphQL 发布时间大致在 2015 年,至今已有 5 年。这个时间已不短,但目前来看,仍然是处于叫好不叫座的情况。

通过对 GraphQL 的使用,不禁开始思考 GraphQL 发展缓慢的原因。
从后端语言的完善程度来看,只有 JavaScript 最具有实用性,对于其它主流后端语言,既没有成熟的库,也存在一定的学习成本,还增加了工作量。所以 GraphQL 就变成了前端工程师自嗨的游戏,API 网关的形式成为了主流。

随着技术的不断发展, GraphQL 的优势其实也在被削弱:

  1. 对于 GraphQL 最引以为傲的可定制化查询功能,即使不通过 GraphQL 语句也可以实现,比如设置过滤字段。而且这个功能在某些场景下会变得比较繁琐,比如想获得一个复杂对象所有字段,那么需要把这个对象的所有字段一个个写出来。
  2. 而至于聚合 API 来减少请求数量在 HTTP/2 似乎也不刚需,因为 HTTP/2 并发性本身就比较强,请求合并和拆分无太大区别,某些场景下,拆分请求逐步加载数据用户体验可能更好。
  3. GraphQL 的模式校验功能 json-schema 规范以及其它库都可以提供。
  4. Schema 即文档倒确实是个亮点,但却不足以点亮 GraphQL 的前景。
  5. 至于 API 工具,GraphQL 无论在数量还是在质量上都是比不过 REST API 的。

整体上来看,对于大多数项目而言,并没有足够的理由使用 GraphQL。
而对于前后端开发而言,更像是一个零和游戏,把 GraphQL 服务架设在网关服务器需要前端工程师多写代码,后端服务改造成 GraphQL 则是增加后端工程师的工作量,除非项目后端采用的 Node.js 开发,那么使用 GraphQL 或许很有优势~


作者信息:朱德龙人和未来高级前端工程师。