Facebook 将 GraphQL 作为开源项目发布已经有两年的时间了,现在成千上万的公司在生产环境中使用 GraphQL。在 2017 年 10 月举行的 GraphQL 峰会上,来自 Apollo 的 Sashko Stubailo 做了一次技术演讲,对 GraphQL 的技术栈进行了梳理,并总结了三项与整个栈相关的功能。
Facebook 将 GraphQL 作为开源项目发布已经有两年的时间了。从那时算起,社区就以指数级的速度在增长,现在成千上万的公司在生产环境中使用 GraphQL。在 2017 年 10 月举行的 GraphQL 峰会上,我非常荣幸地受邀在第二天演讲。读者可以在 YouTube 上观看完整的视频,也可以通过阅读本文对演讲有一个大致的了解(演讲的演示文稿可以在 SlideShare 站点下载——译者注)。
视频:https://youtu.be/ykp6Za9rM58
PPT:https://www.slideshare.net/sashko1/graphql-across-the-stack-how-everything-fits-together
首先,我会简要介绍一下 GraphQL 的现状,然后阐述它未来一段时间内的演化会给开发人员带来哪些好处,尤其会重点介绍全栈 GraphQL 集成的三个样例:缓存、性能跟踪和模式拼接(schema stitching)。
主要有三个因素使得 GraphQL 在与其他 API 技术的对比中脱颖而出:
-
GraphQL 有一个很好的查询语言,这是用来描述数据需求(data requirement)的好办法,另外它还具有一个定义良好的模式,它暴露了 API 的能力(API capability)。在主流技术中,GraphQL 是唯一一个同时指定了等式两侧的主流技术,它的所有好处都来源于这两个概念的相互作用。
-
GraphQL 能够帮助我们将 API 的消费者与提供者解耦。在像 REST 这种基于端点的 API 中,所返回数据的形式是由服务器决定的。在 GraphQL 中,数据的形式则是由使用它的 UI 代码所决定的,这样的话会更加自然,能够让我们聚焦于关注点分离,而不是技术。
-
GraphQL 查询是与使用它的代码息息相关的,所以我们可以将查询视为一个数据获取单元。GraphQL 预先了解 UI 组件的所有数据需求,因此能够实现一些新类型的服务器功能。比如,在某个查询的底层 API 调用中,使用批处理和缓存,这些调用代表了 UI 部分所需的数据,借助 GraphQL 来实现就会变得非常容易。
分离关注点,而不是分离技术:GraphQL 将数据的需求放到了客户端
接下来,我们看一下人们经常提到的关于数据获取的三个方面,然后讨论 GraphQL 如何使用上述特性来对其进行改善。
需要注意的是我所讨论的功能有很大一部分是现在就可用的,还有一部分是将来要实现的。如果这些功能让你感到兴奋的话,那么可以滚动的页面底部了解如何参与。
人们经常问到的一个问题就是如何为 GraphQL API 实现跨请求的缓存。在将常规的 HTTP 缓存应用到 GraphQL 上时,会有一些问题:
但是,GraphQL 同时带来了众多新的机会:
在 GraphQL 中我们该如何更好地使用缓存呢,我们又该如何利用这些新机会呢?
我们首先需要决定将缓存功能放到何处。最初的设想可能会计划将缓存逻辑放到 GraphQL 服务器里面。但是,像 DataLoader 这样的简单工具无法跨 GraphQL 请求良好地运行,另外将缓存功能放到服务器端的代码中有可能会导致实现变得非常复杂。所以,我们应该将其放到其他的地方。
就像 REST 一样,在 API 层的两侧都进行缓存是非常明智的做法:
-
在 GraphQL API 外边的基础设施层缓存整个响应;
-
在 GraphQL 服务器之下缓存底层对数据库和微服务访问所获取到的结果。
对于第二项,已有的缓存基础设施依然可用。对于第一项,我们需要在 API 之外创建一个新的分层,它能够以感知 GraphQL 的方式实现诸如缓存这样的功能。从本质上来讲,这种架构能够让我们将复杂性放到 GraphQL 服务器之外:
将复杂性转移到客户端和服务器之间的一个新的层中
我将这个组件称为 GraphQL 网关。在 Apollo 团队中,我们认为这种新的网关层非常重要,每个人都需要将其作为 GraphQL 架构的一部分。
这也是为什么在本年的 GraphQL 峰会期间,我们启动了 Apollo Engine,将其作为第一个 GraphQL 网关。
正如我在前面的序言中所述,GraphQL 的优势之一就是它有一个巨大的工具生态系统,它们都是通过 GraphQL 的查询和模式来运行的。我认为缓存应该按照相同的方式运行,为此我们引入了 Apollo 缓存控制,它使用了 GraphQL 规范内置的一项名为扩展(extension)的特性,在响应中包含缓存控制信息。
通过我们的 JavaScript 参考实现,很容易就可以在模式中添加缓存控制信息:
通过 apollo-cache-control-js 在模式中定义缓存信息
在这里,我们在 GraphQL 的主要功能之上创建了这个新的缓存控制规范,对这种实现方式我感到很兴奋。它能够以细粒度的方式指定数据的信息,并且利用 GraphQL 的扩展机制将相关的缓存控制信息发送给消费者。在实现方式上,它完全是独立于语言和传输的。
我在 GraphQL 峰会发表完演讲之后,Oleg Ilyenko 发布了一个针对 Sangria 的可运行缓存控制功能,他在维护着 Scala GraphQL 实现。
现在,我们重新回到 GraphQL 服务器中的缓存控制信息,借助网关,我们能够以一种清晰的方式来实现缓存功能。栈中的每一部分都能在各自的位置发挥作用:
缓存会协调技术栈中的每个组成部分
还有另外一件很酷的事情值得一提,大多数人已经在 GraphQL 技术栈中使用过缓存了:比如在前端使用 Apollo Client 和 Relay 缓存数据。在未来版本的 Apollo Client 中,来自响应中的缓存控制信息将会自动过期来自客户端的旧数据。所以,就像 GraphQL 中的其他组成部分一样,服务器描述了它的功能,客户端指定其数据需求,所有组成部分都能很好地协作。
接下来,我们看一下另外一个跨越整个栈的 GraphQL 功能样例。
相对于基于端的系统,GraphQL 能够让前端开发人员以一种更加细粒度的方式来使用数据。他们能够精确地请求想要的数据,忽略不会使用的字段。这样的话,就有机会探测详细的性能信息,并且能够以过去无法实现的方式来进行性能的跟踪。
不要满足于一个不透明的总查询时间——GraphQL 能够让我们获取每个字段详细计时
我们可以说 GraphQL 是第一个能够细粒度获取内部信息的 API 技术。这并不是某项工具做到的——GraphQL 第一次能够让前端开发人员以合法的方式获取每个字段的执行计时,然后让他们基于此修改查询以解决相关的问题。
跟踪与缓存类似,协调整个栈才能真正有用。
在提供跟踪信息时,每个组成部分都有其作用,并且都可以参与进来
服务器可以在结果中提供额外信息,就像提供缓存相关的信息类似,网关可以抽取并聚集这些信息。与缓存类似,在服务器中不想关心的复杂功能,都由网关组件负责处理。
在这里,客户端的主要角色将查询与 UI 组件连接起来。这是非常重要的,我们就可以将 API 层的性能与其对前端影响关联起来。我们第一次能够把后端获取数据的性能与它所影响的 UI 组件在页面上显示出来。
与缓存非常类似,我们可以借助 GraphQL 的响应扩展功能,以独立于服务器的方式来实现。Apollo Tracing 规范目前已经有了 Node、Ruby、Scala、Java 和 Elixir 实现,该规范定义了 GraphQL 服务器返回计时数据的方式,解析器(resolver)会以一种标准的方式进行解析,其他的工具都可以使用解析得到的性能数据。
我们假设所有 GraphQL 工具都要访问性能数据:
抽象共享让所有工具都能使用像跟踪数据这样的信息
借助 Apollo Tracing,我们能够在 GraphiQL、编辑器或其他任意地方使用性能数据。
到目前为止,我们已经看过了一个客户端和一个服务器之间的交互。最后一个样例,我们看一下 GraphQL 能够如何帮助我们模块化架构。
GraphQL 最大的好处之一就是能够在一个地方访问所有的数据。但是,直到最近,这种方式也是有一定成本的:我们需要将整个 GraphQL 模式实现为一个代码库,这样的话,才能在一个请求中对所有数据进行查询。如果你的架构是模块化的,又想使用统一 GraphQL API 所带来的收益,那该怎么处理呢?
模式拼接是一个很简单的理念:GraphQL 能够很容易地将多个 API 合并成一个,这样的话,我们就可以按照独立服务的方式来实现模式中的各个组成部分。这些服务可以独立进行部署,使用不同的语言进行编写,甚至还可以归属不同的组织。
如下是一个样例:
将 GraphQL 峰会票务系统的数据和一个天气 API 的数据组合到一个查询之中:
https://launchpad.graphql.com/130rr3r49