本文最初发布于 Apollo 博客,经原作者授权由 InfoQ 中文站翻译并分享。
Facebook 将 GraphQL 作为开源项目发布已经有两年的时间了。从那时算起,社区就以指数级的速度在增长,现在成千上万的公司在生产环境中使用 GraphQL。在 2017 年 10 月举行的 GraphQL 峰会上,我非常荣幸地受邀在第二天演讲。读者可以在 YouTube 上观看完整的视频,也可以通过阅读本文对演讲有一个大致的了解(演讲的演示文稿可以在SlideShare 站点下载——译者注)。
首先,我会简要介绍一下GraphQL 的现状,然后阐述它未来一段时间内的演化会给开发人员带来哪些好处,尤其会重点介绍全栈GraphQL 集成的三个样例:缓存、性能跟踪和模式拼接(schema stitching)。
GraphQL 的与众不同之处是什么?
主要有三个因素使得 GraphQL 在与其他 API 技术的对比中脱颖而出:
- GraphQL 有一个很好的查询语言,这是用来描述数据需求(data requirement)的好办法,另外它还具有一个定义良好的模式,它暴露了API 的能力(API capability)。在主流技术中,GraphQL 是唯一一个同时指定了等式两侧的主流技术,它的所有好处都来源于这两个概念的相互作用。
- GraphQL 能够帮助我们将 API 的消费者与提供者解耦。在像 REST 这种基于端点的 API 中,所返回数据的形式是由服务器决定的。在 GraphQL 中,数据的形式则是由使用它的 UI 代码所决定的,这样的话会更加自然,能够让我们聚焦于关注点分离,而不是技术。
- GraphQL 查询是与使用它的代码息息相关的,所以我们可以将查询视为一个数据获取单元。GraphQL 预先了解 UI 组件的所有数据需求,因此能够实现一些新类型的服务器功能。比如,在某个查询的底层 API 调用中,使用批处理和缓存,这些调用代表了 UI 部分所需的数据,借助 GraphQL 来实现就会变得非常容易。
分离关注点,而不是分离技术:GraphQL 将数据的需求放到了客户端
接下来,我们看一下人们经常提到的关于数据获取的三个方面,然后讨论 GraphQL 如何使用上述特性来对其进行改善。
需要注意的是我所讨论的功能有很大一部分是现在就可用的,还有一部分是将来要实现的。如果这些功能让你感到兴奋的话,那么可以滚动的页面底部了解如何参与。
1. 跨请求缓存
人们经常问到的一个问题就是如何为 GraphQL API 实现跨请求的缓存。在将常规的 HTTP 缓存应用到 GraphQL 上时,会有一些问题:
- HTTP 缓存通常不支持 POST 请求或较长的 cache key;
- 请求的多样性通常会意味着更低的缓存命中率;
- GraphQL 是独立于传输层的,所以 HTTP 并不一定总是有效。
但是,GraphQL 同时带来了众多新的机会:
- 在访问后端的模式和解析器(resolver)上声明缓存控制信息;
- 模式方案所带来的自动化细粒度缓存控制,而不需要考虑每个请求的命中率。
在 GraphQL 中我们该如何更好地使用缓存呢,我们又该如何利用这些新机会呢?
应该将缓存功能放在何处?
我们首先需要决定将缓存功能放到何处。最初的设想可能会计划将缓存逻辑放到 GraphQL 服务器里面。但是,像 DataLoader 这样的简单工具无法跨 GraphQL 请求良好地运行,另外将缓存功能放到服务器端的代码中有可能会导致实现变得非常复杂。所以,我们应该将其放到其他的地方。
就像 REST 一样,在 API 层的两侧都进行缓存是非常明智的做法:
- 在 GraphQL API 外边的基础设施层缓存整个响应;
- 在 GraphQL 服务器之下缓存底层对数据库和微服务访问所获取到的结果。
对于第二项,已有的缓存基础设施依然可用。对于第一项,我们需要在 API 之外创建一个新的分层,它能够以感知 GraphQL 的方式实现诸如缓存这样的功能。从本质上来讲,这种架构能够让我们将复杂性放到 GraphQL 服务器之外:
我将这个组件称为 GraphQL 网关。在 Apollo 团队中,我们认为这种新的网关层非常重要,每个人都需要将其作为 GraphQL 架构的一部分。
这也是为什么在本年的 GraphQL 峰会期间,我们启动了Apollo Engine ,将其作为第一个GraphQL 网关。
用于缓存控制的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 功能样例。
2. 跟踪
相对于基于端的系统,GraphQL 能够让前端开发人员以一种更加细粒度的方式来使用数据。他们能够精确地请求想要的数据,忽略不会使用的字段。这样的话,就有机会探测详细的性能信息,并且能够以过去无法实现的方式来进行性能的跟踪。
不要满足于一个不透明的总查询时间——GraphQL 能够让我们获取每个字段详细计时
我们可以说 GraphQL 是第一个能够细粒度获取内部信息的 API 技术。这并不是某项工具做到的——GraphQL 第一次能够让前端开发人员以合法的方式获取每个字段的执行计时,然后让他们基于此修改查询以解决相关的问题。
跨越整个栈进行跟踪
跟踪与缓存类似,协调整个栈才能真正有用。
在提供跟踪信息时,每个组成部分都有其作用,并且都可以参与进来
服务器可以在结果中提供额外信息,就像提供缓存相关的信息类似,网关可以抽取并聚集这些信息。与缓存类似,在服务器中不想关心的复杂功能,都由网关组件负责处理。
在这里,客户端的主要角色将查询与 UI 组件连接起来。这是非常重要的,我们就可以将 API 层的性能与其对前端影响关联起来。我们第一次能够把后端获取数据的性能与它所影响的 UI 组件在页面上显示出来。
GraphQL 跟踪扩展
与缓存非常类似,我们可以借助 GraphQL 的响应扩展功能,以独立于服务器的方式来实现。Apollo Tracing 规范目前已经有了 Node 、 Ruby 、 Scala 、 Java 和 Elixir 实现,该规范定义了 GraphQL 服务器返回计时数据的方式,解析器(resolver)会以一种标准的方式进行解析,其他的工具都可以使用解析得到的性能数据。
我们假设所有 GraphQL 工具都要访问性能数据:
借助 Apollo Tracing,我们能够在 GraphiQL、编辑器或其他任意地方使用性能数据。
到目前为止,我们已经看过了一个客户端和一个服务器之间的交互。最后一个样例,我们看一下 GraphQL 能够如何帮助我们模块化架构。
3. 模式拼接
GraphQL 最大的好处之一就是能够在一个地方访问所有的数据。但是,直到最近,这种方式也是有一定成本的:我们需要将整个 GraphQL 模式实现为一个代码库,这样的话,才能在一个请求中对所有数据进行查询。如果你的架构是模块化的,又想使用统一 GraphQL API 所带来的收益,那该怎么处理呢?
模式拼接是一个很简单的理念:GraphQL 能够很容易地将多个 API 合并成一个,这样的话,我们就可以按照独立服务的方式来实现模式中的各个组成部分。这些服务可以独立进行部署,使用不同的语言进行编写,甚至还可以归属不同的组织。
如下是一个样例:
将 GraphQL 峰会票务系统的数据和一个天气 API 的数据组合到一个查询之中: https://launchpad.graphql.com/130rr3r49
在上面的截图中,我们可以看到拼接后的 API 是如何将针对两个不同服务的独立查询联合起来的,这种方式对客户端是完全不可见的。通过这种方式,我们完全可以像搭建乐高积木那样组合 GraphQL 模式。
我们目前正在提供一个该功能的实现,读者现在就可以进行尝试,它作为 Apollo graphql-tools 库的一部分,通过文档可以了解更多信息。
在网关中进行拼接
模式拼接也可以在整个栈中很好地运行。长期来看,我们认为非常适合在新的网关层进行拼接,这样的话,就能使用任意你想要的技术来构建模式了,比如 Node.js 、 Graphcool 或 Neo4j 。
客户端也可以加入进来。就像可以通过一个查询加载多个后端的数据,我们同样可以在客户端组合数据源。在最近发布的 Apollo Client 2.0 中新增了状态管理功能,该功能允许我们在一个查询中加载来自客户端状态和任意数量后端的数据。
结论
通过阅读本文或观看演讲,我希望读者能够意识到 GraphQL 工具如今已经非常强大了,未来有非常大的潜力。我们刚刚所接触的只是 GraphQL 抽象和功能的一个皮毛。
最后,我想基于以上的理念分享一个 TODO 列表:
要集成这些新的特性,还有很多工作需要完成,尤其是开发工具和编辑器领域
要释放 GraphQL 的全部潜力,还有很多的事情要做。在 Apollo 团队中,我们正为此竭尽全力,但是没有任何一个人、团队或组织能够完成所有的事情。为了让未来的蓝图实现,我们需要协作工作,将这些解决方案构建出来。
不管怎样,有一件事情是非常清晰的:GraphQL 已经作为一项变革性的技术用到了成千上万的企业中,但这只是开始!我迫不及待地想知道在未来的两年、五年和十年内,我们该如何构建应用,因为这肯定是非常美妙的!
如何参与
如果你像 Apollo 一样相信 GraphQL 的潜力,那么可以参与到社区中来。为了让读者快速起步,我们创建了一个帮助页面。
感谢徐川对本文的审校。
评论