BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

Coursera的GraphQL之旅

| 作者 Bryan Kane 关注 0 他的粉丝 ,译者 薛命灯 关注 12 他的粉丝 发布于 2017年12月7日. 估计阅读时间: 7 分钟 | QCon北京2018全面起航:开启与Netflix、微软、ThoughtWorks等公司的技术创新之路!

亲爱的读者:我们最近添加了一些个人消息定制功能,您只需选择感兴趣的技术主题,即可获取重要资讯的邮件和网页通知

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加载数据也更快。


感谢徐川对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT