- 原文地址:Coursera’s journey to GraphQL
- 原文做者:Bryan Kane
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:bambooom
- 校对者:sunui、alfred-zhong
将 GraphQL 添加至 REST + 微服务的后端中前端
Coursera 的客户端开发人员喜欢 GraphQL 的灵活性、类型安全性以及社区的支持,这些已经众所周知。可是,咱们没有谈过多少咱们的后端开发者们对于 GraphQL 的感觉,这是由于实际上他们大多数并不须要为 GraphQL 考虑太多。react
过去的一年中,咱们构建了将全部 REST API 动态转换为 GraphQL 的工具。这使得后端开发者能够继续编写他们熟悉的 API,同时客户端开发者也能够经过 GraphQL 访问全部数据。android
本文中将介绍咱们的 GraphQL 之旅,特别是过程当中的成功及失败。ios
Coursera 的 REST API 是基于资源构建的(即课程 API、教师 API、课程成绩 API 等)。这样使得开发和测试都很容易,而且在后端很好地实现了关注分离。然而,随着产品规模扩大以及 API 数量增加,咱们开始面临性能、文档以及易用性等问题。在许多页面上,咱们发现须要四到五次与服务器的往返来获取全部咱们须要渲染的数据。git
还记得 Facebook 首次推出 GraphQL 时咱们团队很是兴奋,由于咱们几乎马上就意识到 GraphQL 能够解决咱们的诸多问题,例如在一次往返获取全部数据,并为 API 提供结构化的文档等。虽然咱们想立刻中止使用 REST 并开始编写 GraphQL,但事情并不是如此简单,由于:github
当时,Coursera 有超过 1000 个不一样的 REST 端点(如今更多),即便咱们想彻底中止使用 REST,GraphQL 的迁移成本将是极大的。web
咱们全部的后端服务都使用 REST API 进行服务间通讯,因此常常会有给后端服务以及前端提供相同 API 的状况。后端
咱们有三个不一样的客户端(web、iOS 以及 Android),但愿能灵活缓慢地推动。api
在一些调查以后,咱们发现了一个引入 GraphQL 的好方法,那就是在 REST API 上添加 GraphQL 的代理层。这个方法实际上也很常见,而且有详细的文档验证过了,因此这里我就不深刻展开了。安全
包装 REST API 是个很是简单的过程,咱们针对下游 REST 调用经过解析器获取数据构建了一些实用程序,并写了一些将现有模型转为 GraphQL 的规则。
第一步是构建 GraphQL 解析器,而后在生产环境中启动一个 GraphQL 服务器,使下游 REST 调用到源端点。一旦完成了这项工做(用 GraphQL 来验证一切),咱们就会在设置的演示页面展现数据,几天以内就能够说 GraphQL 的尝试成功了。
若是说我从这个项目中学到了一件事,那必定是不要高兴太早。
咱们的 GraphQL 服务器完美工做了几天,可是忽然之间,在咱们准备给团队演示以前,每一个 GraphQL 查询都失败了。咱们措手不及,由于自从上次验证它正常工做以来并无对 GraphQL 服务器进行任何更改。
在调查以后,终于发现因为一个不相关的 bug,下游课程目录服务回滚到了以前的版本,致使 GraphQL 中构建的模式不一样步了。咱们能够手动更新并修复演示页面,但很快咱们意识到当咱们的 GraphQL 架构若是扩展到由超过 50 个不一样的服务支持的 1000 个不一样的资源以后,想保持全部数据都更新到最新几乎是不可能的。若是在微服务体系中你有多于一个数据来源,那么问题在于什么时候,而不是他们是否不一样步。
因此咱们回到了白板上,试图找出一个清晰的解决方案得到真实数据源。将 REST API 视为真实数据源是有道理的,由于 GraphQL 是基于它们构建的。为此,咱们须要自动地肯定性地构建 GraphQL 层,以反映当前体系中正在运行的内容,而不是咱们认为正在运行的。
幸运的是(也许算有远见),咱们的 REST 框架给咱们提供了构建这个自动化层须要的一切:
基础架构中的每个服务均可以动态地提供正在运行的 REST 资源列表。
针对每个资源,咱们能够内省获取其一系列端点和参数列表(即一个课程能够经过 id 获取,也能够由讲师查找)
另外,咱们接受由 Courier 的模式语言定义的 Pegasus Schemas,用于每一个模型返回数据
只要发现不一样的部分,咱们就须要构建一个 GraphQL 模式,在 GraphQL 服务器上设置一个任务,每五分钟对全部下游服务 ping 一次,请求全部信息。而后,咱们就能够在 Pegasus 模式和 GraphQL 类型之间编写 1:1 的转化层了。
接下来,咱们只须要简单定义如何将 GraphQL 查询转化为 REST 请求,使用之前的解析器中的大部分逻辑,就能够生成功能完整的 GraphQL 服务器,再也不会过时 5 分钟以上。
咱们但愿使用 GraphQL 的一个主要缘由是在一个往返中获取某个页面须要的全部数据。可是一开始咱们的方法只能提供 REST API 以及 GraphQL 之间一对一的映射。没有将资源链接在一块儿,咱们仍然会像使用 REST API 同样,使用屡次 GraphQL 查询来获取数据。虽然经过 GraphQL 获取用户数据相比使用 REST 来讲,开发者体验有所提高,但若是在获取更多数据以前必须等待前序查询返回的话,那么在性能上没有实质提高。
咱们的每一个 REST API 都独立存活,他们不须要知道其余任何 API 的存在。可是,若是使用 GraphQL,模型和资源确实须要彼此的存在,以及如何链接。
资源之间的链接是不能自动添加的,因此咱们定义了一个简单的标记方法,使得开发者能够添加资源并指定资源之间的关系。例如,咱们能够指定一个课程应该有讲师字段,表明教授这门课程的讲师。获取这些讲师的时候,须要使用 id 查询,此时就可使用课程已经提供的 instructorIds
字段。咱们称之为「前置关系」,由于咱们经过 id 确切知道哪些讲师须要获取。
在想要从一个资源到另外一个资源但没有显式关联的状况下,咱们添加了反向查询的支持,也就是获取一个用户在一个课程的注册状况。咱们能够在 userEnrollments
.v1 资源上经过 byCourseId
进行查询,就能够返回在指定的课程中指定用户的注册数据。
咱们开发的语法看起来像这样:
courseAPI.addRelation(
"instructors" -> ReverseRelation(
resourceName = "instructors.v1",
finderName = "byCourseId",
arguments = Map("courseId" -> "$id", "version" -> "$version"))复制代码
一旦这些关联到位,咱们的 GraphQL 模式就开始聚集在一块儿了,再也不是小量数据碎片,而是整个 Coursera 数据和资源的网络。
咱们已经在生产环境中运行 GraphQL 服务器 6 个月了,这条路有时是颠簸的,但咱们切实认识到 GraphQL 带来的好处。开发人员更容易发现数据及编写查询,咱们的产品也因为 GraphQL 额外提供的类型安全性更加可靠,使用 GraphQL 获取数据的页面加载也更快。
须要重点提出的是,这种迁移并不以开发效率为代价。咱们的前端工程师的确须要学习如何使用 GraphQL,但咱们并不须要重写后端 API 或运行复杂的迁移才能享受 GraphQL 带来的好处。当建立新的应用程序的时候,它就可供开发人员使用了。
总的来讲,咱们对 GraphQL 为开发人员(最终为用户)提供的帮助很是满意,并对 GraphQL 生态的发展充满期待。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。