作者
Bryan Kane讲述了Coursera是如何在他们的生产环境使用GraphQL的。以下内容翻译自作者的博客,查看原文:Coursera’s journey to GraphQL。
在Coursera,前端开发人员非常喜欢GraphQL的灵活性、类型安全和社区支持,但后端开发人员却不怎么直接接触GraphQL。
在过去的一年,我们开发了一些工具将REST API转成GraphQL,这样后端开发人员就可以继续开发他们熟悉的API,而前端开发人员可以通过GraphQL访问他们想要的数据。
在这篇文章里,我们将介绍我们的GraphQL之旅以及在这一过程中经历的成功与失败。
初始调研
Coursera的REST API都是基于资源的,比如课程API、导师API、年级API等。这些API的开发和测试都很容易,而且在后端提供了非常好的关注点分离。不过,随着产品规模的增长,API的数量也在增长,我们开始面临一系列问题,如性能问题、文档问题和易用性问题。我们发现很多页面需要四到五个网络来回才能获取到必要的数据。
我还记得在Facebook推出GraphQL时那种激动的心情,我们立刻意识到GraphQL将会解决我们的很多问题,我们可以一次性拿到必要的数据,并为API提供结构化的文档。但要在客户端完全使用GraphQL端点代替REST似乎是件不可能的事,因为:
- 那个时候,我们有超过1000个不同的REST端点(现在则更多),从REST到GraphQL的迁移成本是巨大的。
- 所有后端的服务间通信都使用了REST API,而且后端服务为前端和其他后端服务暴露出来的是同一套API。
- 我们有三种客户端(Web、iOS和Android)。
经过调研,我们找到了一种可以采用GraphQL的方案——我们决定在REST API之上增加一个GraphQL代理层。这种方式已经很常见了,并有详细的文档记录,所以这里就不再详述。
在生产环境使用GraphQL
我们先是构建了一个新的GraphQL处理器,然后在生产环境启动了一个GraphQL服务器用于向REST端点发起调用,并将数据展示在演示页面上。经过几天的测试,我们确定这个方案是可行的。
短暂的成功
我们从这个项目中学到了一个教训,就是不要高兴得太早。
GraphQL服务器在头几天很稳定,但在我们向团队演示数据页面那天,所有的GraphQL查询都失败了。这个让我们有点措手不及,因为从上次确认这个方案可行之后,就没有动过GraphQL服务器。
后来我们发现,下游的课程目录服务为了修复一个不相关的bug回滚到了前一个版本,导致GraphQL服务中的schema出现不一致。我们很快修复了schema问题,但我们也意识到,当GraphQL的schema规模增长到1000个并由50多个不同的服务来支撑的时候,要保持一切都同步是不可能的事情。在微服务架构里,如果有多个事实来源(source of truth),那么出现不同步是迟早的事。
自动化流程
于是我们试图寻找如何能够实现单个事实来源的解决方案——我们完全可以将REST API作为事实来源,因为我们的GraphQL schema就是基于这些API定义的。所以,我们需要自动化、决策性地构建我们的GraphQL层,体现出当前架构里正在运行的东西,而不是我们的臆想。
幸运的是,我们的REST框架为我们提供了所需的一切:
- 每个服务为我们提供动态的REST资源清单。
- 对于每一种资源,我们可以检查它们的端点和参数(比如,课程可以通过id获取到,或者通过导师查询到)。
- 我们可以收到由我们的Courier Schema Language为每个模型定义的Pegasus Schema。
我们在GraphQL服务器上设置了一个任务,每五分钟ping一次下游的服务,获取所有必要的信息,然后在Pegasus Schema和GraphQL类型之间构建一对一的转换层。
接下来,我们使用之前开发的处理器逻辑在GraphQL查询和REST请求之间建立映射,得到一个全功能的GraphQL服务器,其更新速度的落后时间不会超过五分钟。
关联资源
我们使用GraphQL最主要的原因之一就是希望能够为页面一次性获取到必要的数据。不过,我们最初的方案只提供了REST API到GraphQL之间一对一的映射。如果不将资源关联起来,我们仍然需要进行多次GraphQL查询。尽管开发者体验得到了提升,但在性能方面并没有获得实际的好处。
我们的REST API都是一个个孤岛,但在使用了GraphQL之后,模型和资源需要对彼此有所了解,因为它们之间存在必要的关联。
资源之间并不会自动构建链接,所以我们定义了一个简单的注解,开发人员可以将它加在资源上面,用于指定资源之间的关系。例如,一个课程资源需要有一个导师字段,表示教授该课程的导师是谁。我们可以使用课程里的导师ID获取导师信息。我们称之为“前向关系”,因为我们知道使用ID可以获得哪些导师的信息。
courseAPI.addRelation( "instructors" -> ReverseRelation( resourceName = "instructors.v1", finderName = "byCourseId", arguments = Map("courseId" -> "$id", "version" -> "$version"))
如果我们想从一个资源跳到另一个资源,但又没有显式指定链接,那么可以使用反向查找。比如,为了找出某个用户的某一门课程的注册信息,我们可以在userEnrollments.v1资源上调用byCourseId,这样就可以返回某个用户在某门课程上的注册信息。
有了这些链接,Coursera的所有数据和资源就形成了一个网络。
结论
我们在Coursera的生产环境运行GraphQL服务器超过六个月的时间,虽然道路仍然崎岖,但GraphQL为我们提供的帮助无所不在。开发人员操作数据变得更加容易,GraphQL提供的类型安全特性也让我们的网站变得更可靠,使用GraphQL加载数据也更快。
转自 http://www.infoq.com/cn/news/2017/12/Coursera-GraphQL-tour