Coursera 的 GraphQL 之旅

首发于众成翻译前端


Coursera 的 GraphQL 之旅

为 REST 和微服务后端添加 GraphQL

Coursera 的客户端开发人员钟情于 GraphQL 的灵活性,类型安全性和良好的社区支持,咱们对 GraphQL 的喜好~~~。然而,咱们并无过多讨论后端开发人员是如何看待 GraphQL 的,由于他们大多数实际上并不须要考虑 GraphQL。git

在过去一年中,咱们构建了一系列的工具来将全部的 REST API 转换为 GraphQL,在咱们的后端开发人员继续编写他们熟悉的 API 的同时,让客户端开发人员能够经过 GraphQL 访问全部数据。github

在这篇文章中,咱们将介绍一下咱们引入 GraphQL 的历程,而且会着重介绍咱们实践过程当中成功和失败的点。后端

初步调研

Coursera 使用 REST API 构建基于资源的 API(好比课程 API,教师 API,成绩 API等)。这些都很容易进行构建和测试,而且对后端提供了很好的关注点分离。然而,随着咱们的产品规模和 API 数量的增加,咱们开始面对许多关于性能,文档和通用性的问题。在许多页面上,咱们不得不执行四五次后端请求来获取渲染页面须要的数据。api

我还记得当 Facebook 首次推出 GraphQL 时,咱们团队都兴奋不已——咱们立即意识到 GraphQL 能够解决咱们的不少问题,让咱们能够在单次的请求中获取全部数据,并为咱们的 API 提供结构化文档。然而,尽管咱们很想开始为全部资源编写 GraphQL,再也不在客户端上使用 REST,但这不切实际,由于:安全

  • 彼时,Coursera 项目拥有超过 1,000 个不一样的 REST 端点(如今更多)——即便咱们想彻底中止使用 REST,GraphQL 的迁移成本也将是天文数字。
  • 全部后端服务都使用 REST API 进行服务间通讯,咱们常常会在后台服务和前端客户端使用相同的 API。
  • 咱们有三个不一样的客户端(Web,iOS 和 Android),但愿可以平滑的升级。

通过一番调研,咱们找到了一个很好的方式来帮助咱们使用 GraphQL——咱们决定在咱们的 REST API 之上添加一个 GraphQL 代理层。这种方式实际上很常见,而且~~记录~详实,因此这里我再也不赘述。服务器

在生产环境应用 GraphQL

封装 REST API 的过程很简单——咱们构建了一些实用程序来执行下游的 REST 请求,从而在解析器中获取数据,并制定了一些关于如何将现有模型转换为 GraphQL 的规则。网络

首先,咱们构建了少许的 GraphQL 解析器,而后在生产环境中启动一个 GraphQL 服务器,以调用下游 REST 接口请求咱们的资源。一旦咱们完成了这项工做(使用 GraphiQL 验证全部内容),咱们就会在提早准备的演示页面上展现这些数据,几天以内 GraphQL 就能暂时调用成功了。架构

短暂的庆祝

若是我从这个项目中获得什么教训,那就是不要高兴得太早了。app

咱们的 GraphQL 服务器完美工做了好几天。可是忽然之间,就在咱们即将给团队演示这个 demo 以前,每个 GraphQL 查询都开始执行失败。这让咱们没有一点点防备,由于上一次确认它能正常工做以后,咱们并未对 GraphQL 服务器作过任何更改。

通过一番调查,咱们才发现因为一个无关的 bug,致使咱们下游的课程目录服务接口被回滚到了之前的版本,而咱们在 GraphQL 服务中构建的 schema 如今已经不一样步了。咱们本能够手动更新 schema 并修复咱们的 demo,可是咱们很快意识到,因为咱们的 GraphQL schema 扩展了1,000多个不一样的资源,由50多个服务提供支持,手动同步全部的更新是不可能的。 若是你在微服务架构中有多个数据源,那么问题就在于它们什么时候同步,而不是是否会同步。

自动化处理

因此咱们从头开始,试图找到一个更精确的方案来实现单一数据源——咱们将 REST API 视为数据源是有依据的,由于咱们的 GraphQL 的 schema 是基于它们构建的。为此,咱们须要自动且准确地构建咱们的 GraphQL 层,以正确反映当前运行在咱们的架构中的业务资源,而不是咱们以前作的那样。

幸运的是(或许还带有一点远见),咱们的 REST 框架能给咱们创建自动化层所需的一切:

  • 咱们架构中的每项服务均可以动态地为咱们提供其运行的 REST 资源列表
  • 对于单个资源,咱们能够内省端点列表和参数(好比课程端点能够经过 id 获取,也能够经过教师查找)
  • 另外,咱们可以接收到由咱们的 Courier 模式语言为每一个返回的模型定义的 Pegasus Schemas

一旦咱们发现不一样步的地方,就会触发构建一个 GraphQL schema,咱们在 GraphQL 服务器上设置了一个定时任务,每五分钟 ping 一次下游服务,并请求全部资源信息。 而后,咱们就能够在 Pegasus Schemas 和 GraphQL 类型之间编写1:1转换层

接下来,咱们利用以前解析器的大部分逻辑,简单地定义了 GraphQL 查询和 REST 请求之间的转换,而且可以生成一个功能完善的 GraphQL 服务器,时间不超过五分钟。

关联资源

咱们采用 GraphQL 的主要缘由之一就是但愿能在单次服务器往返中获取咱们的页面须要的全部数据。可是,咱们最初的方案仅提供了 REST API 返回的模型与 GraphQL 返回的模型之间的一对一映射。这样并无将咱们的资源真正地连接在一块儿,咱们仍然会使用尽量多的 GraphQL 查询来获取数据,就像使用 REST API 同样。尽管使用 GraphQL 替代 REST 获取用户数据能带来极致的开发体验,但若是在获取更多数据以前必须等待前一个查询返回,实际上并不会得到性能的提高。

咱们的 REST API 每个都相互独立 ——它们不须要知道任何其余 API 的存在。然而,使用 GraphQL,则模型和资源之间须要相互关联。

自动创建资源之间的连接并不可行,因此咱们定义了一个简单的注解,开发人员能够添加资源来指定它们之间的关系。例如,一个课程资源应该有一个教师字段表明教授该门课程的教师。为了获取这些数据,咱们能够经过 id 来查询教师信息,这里的 id 可使用课程中已经提供的 InstructorIds 字段。咱们将其称为“向前关联”,由于咱们能够经过 id 获取的数据知道确切的教师信息。

当咱们想要从某个资源跳转到另外一个没有明确连接的资源的状况下,咱们增长了经过反向查询获取数据的功能——例如,经过课程信息获取用户的注册信息,咱们能够调用 byCourseId 来查找 userEnrollments.v1 的资源,这将会在指定的课程资源中返回匹配的用户注册数据。

代码的语法如这般:

courseAPI.addRelation(
  "instructors" -> ReverseRelation(
    resourceName = "instructors.v1",
    finderName = "byCourseId",
    arguments = Map("courseId" -> "$id", "version" -> "$version"))

一旦这些联系就位,咱们的 GraphQL schema 就会开始合并在一块儿——它们并不是是能经过 GraphQL 获取的多块小数据,而是由全部 Coursera 的数据和资源组成的网络。

结论

咱们的 GraphQL 服务器已经在 Coursera 生产环境上运行了6个多月 ,尽管并不是一路顺风,但咱们切身感觉到了 GraphQL 带来的诸多好处。得益于 GraphQL 额外提供的类型安全检查,开发人员更容易检测数据和编写查询,咱们的站点更加可靠,而且使用 GraphQL 加载数据的页面运行得更快。

还有比较重要的一点,迁移到 GraphQL 的过程并不会大幅下降开发人员的生产力。尽管咱们的前端开发人员不得不学习如何使用 GraphQL,但咱们不须要重写任何后端 API 或运行复杂的迁移流程才能使用 GraphQL——开发人员建立新的项目能够直接使用。

总的来讲,咱们很高兴 GraphQL 为咱们的开发人员(也是咱们的终极用户)提供了很大的帮助,而且对 GraphQL 生态的将来感到很是兴奋。

相关文章
相关标签/搜索