GraphQL技术栈概览:如何将所有的功能组合起来
caobiao
7年前
<p>非死book将GraphQL作为开源项目发布已经有两年的时间了。从那时算起,社区就以指数级的速度在增长,现在成千上万的公司在生产环境中使用GraphQL。在2017年10月举行的GraphQL峰会上,我非常荣幸地受邀在第二天演讲。读者可以在油Tube上观看 <a href="/misc/goto?guid=4959755884130948737" rel="nofollow,noindex">完整的视频</a> ,也可以通过阅读本文对演讲有一个大致的了解(演讲的演示文稿可以在SlideShare 站点 <a href="/misc/goto?guid=4959755884226375351" rel="nofollow,noindex">下载</a> ——译者注)。</p> <p>首先,我会简要介绍一下GraphQL的现状,然后阐述它未来一段时间内的演化会给开发人员带来哪些好处,尤其会重点介绍全栈GraphQL集成的三个样例:缓存、性能跟踪和模式拼接(schema stitching)。</p> <h2>GraphQL的与众不同之处是什么?</h2> <p>主要有三个因素使得GraphQL在与其他API技术的对比中脱颖而出:</p> <ol> <li>GraphQL有一个很好的查询语言,这是用来描述 <strong>数据需求(data requirement)</strong> 的好办法,另外它还具有一个定义良好的模式,它暴露了 <strong>API的能力(API capability)</strong> 。在主流技术中,GraphQL是唯一一个同时指定了等式两侧的主流技术,它的所有好处都来源于这两个概念的相互作用。</li> <li>GraphQL能够帮助我们将API的消费者与提供者解耦。在像REST这种基于端点的API中,所返回数据的形式是由服务器决定的。在GraphQL中,数据的形式则是由使用它的UI代码所决定的,这样的话会更加自然,能够让我们聚焦于关注点分离,而不是技术。</li> <li>GraphQL查询是与使用它的代码息息相关的,所以我们可以将查询视为一个 <strong>数据获取单元</strong> 。GraphQL预先了解UI组件的所有数据需求,因此能够实现一些新类型的服务器功能。比如,在某个查询的底层API调用中,使用批处理和缓存,这些调用代表了UI部分所需的数据,借助GraphQL来实现就会变得非常容易。</li> </ol> <p><img src="https://simg.open-open.com/show/cc0346464db1dc969002399743c9c171.png" alt="GraphQL技术栈概览:如何将所有的功能组合起来" width="800" height="432"></p> <p>分离关注点,而不是分离技术:GraphQL将数据的需求放到了客户端</p> <p>接下来,我们看一下人们经常提到的关于数据获取的三个方面,然后讨论GraphQL如何使用上述特性来对其进行改善。</p> <p><img src="https://simg.open-open.com/show/f8586d0eca04570a4b1cc8947f9a9cba.png" alt="GraphQL技术栈概览:如何将所有的功能组合起来" width="800" height="258"></p> <p>需要注意的是我所讨论的功能有很大一部分是现在就可用的,还有一部分是将来要实现的。如果这些功能让你感到兴奋的话,那么可以滚动的页面底部了解如何参与。</p> <h2>1.跨请求缓存</h2> <p>人们经常问到的一个问题就是如何为GraphQL API实现跨请求的缓存。在将常规的HTTP缓存应用到GraphQL上时,会有一些问题:</p> <ul> <li>HTTP缓存通常不支持POST请求或较长的cache key;</li> <li>请求的多样性通常会意味着更低的缓存命中率;</li> <li>GraphQL是独立于传输层的,所以HTTP并不一定总是有效。</li> </ul> <p>但是,GraphQL同时带来了众多新的机会:</p> <ul> <li>在访问后端的模式和解析器(resolver)上声明缓存控制信息;</li> <li>模式方案所带来的自动化细粒度缓存控制,而不需要考虑每个请求的命中率。</li> </ul> <p>在GraphQL中我们该如何更好地使用缓存呢,我们又该如何利用这些新机会呢?</p> <h3>应该将缓存功能放在何处?</h3> <p>我们首先需要决定将缓存功能放到何处。最初的设想可能会计划将缓存逻辑放到GraphQL服务器里面。但是,像DataLoader这样的简单工具无法跨GraphQL请求良好地运行,另外将缓存功能放到服务器端的代码中有可能会导致实现变得非常复杂。所以,我们应该将其放到其他的地方。</p> <p>就像REST一样,在API层的两侧都进行缓存是非常明智的做法:</p> <ol> <li>在GraphQL API外边的基础设施层缓存整个响应;</li> <li>在GraphQL服务器之下缓存底层对数据库和微服务访问所获取到的结果。</li> </ol> <p>对于第二项,已有的缓存基础设施依然可用。对于第一项,我们需要在API之外创建一个新的分层,它能够以感知GraphQL的方式实现诸如缓存这样的功能。从本质上来讲,这种架构能够让我们将复杂性放到GraphQL服务器之外:</p> <p><img src="https://simg.open-open.com/show/825cfd5e9d91f1b1b96c7d62d86c2e08.png" alt="GraphQL技术栈概览:如何将所有的功能组合起来" width="800" height="266"></p> <p>将复杂性转移到客户端和服务器之间的一个新的层中</p> <p>我将这个组件称为GraphQL网关。在Apollo团队中,我们认为这种新的网关层非常重要,每个人都需要将其作为GraphQL架构的一部分。</p> <p>这也是为什么在本年的GraphQL峰会期间,我们 <a href="/misc/goto?guid=4959755884308177548" rel="nofollow,noindex">启动了Apollo Engine</a> ,将其作为第一个GraphQL网关。</p> <h3>用于缓存控制的GraphQL响应扩展</h3> <p>正如我在前面的序言中所述,GraphQL的优势之一就是它有一个巨大的工具生态系统,它们都是通过GraphQL的查询和模式来运行的。我认为缓存应该按照相同的方式运行,为此我们引入了 <a href="/misc/goto?guid=4959755884392698788" rel="nofollow,noindex">Apollo 缓存控制</a> ,它使用了GraphQL规范内置的一项名为 <em>扩展(extension)</em> 的特性,在响应中包含缓存控制信息。</p> <p>通过我们的 <a href="/misc/goto?guid=4959755884471574403" rel="nofollow,noindex">JavaScript参考实现</a> ,很容易就可以在模式中添加缓存控制信息:</p> <p><img src="https://simg.open-open.com/show/66dced4030e5a82aaa5ad57f63b69d4a.png" alt="GraphQL技术栈概览:如何将所有的功能组合起来" width="1076" height="330"></p> <p>通过apollo-cache-control-js在模式中定义缓存信息</p> <p>在这里,我们在GraphQL的主要功能之上创建了这个新的缓存控制规范,对这种实现方式我感到很兴奋。它能够以细粒度的方式指定数据的信息,并且利用GraphQL的扩展机制将相关的缓存控制信息发送给消费者。在实现方式上,它完全是独立于语言和传输的。</p> <p>我在GraphQL峰会发表完演讲之后, <a href="/misc/goto?guid=4959755884556539336" rel="nofollow,noindex">Oleg Ilyenko</a> 发布了一个 <a href="/misc/goto?guid=4959755884640642840" rel="nofollow,noindex">针对Sangria的可运行缓存控制功能</a> ,他在维护着Scala GraphQL实现。</p> <h3>通过网关实现缓存</h3> <p>现在,我们重新回到GraphQL服务器中的缓存控制信息,借助网关,我们能够以一种清晰的方式来实现缓存功能。栈中的每一部分都能在各自的位置发挥作用:</p> <p><img src="https://simg.open-open.com/show/4a575f081c34e265ed5c16045077006f.png" alt="GraphQL技术栈概览:如何将所有的功能组合起来" width="800" height="322"></p> <p>缓存会协调技术栈中的每个组成部分</p> <p>还有另外一件很酷的事情值得一提,大多数人已经在GraphQL技术栈中使用过缓存了:比如在前端使用Apollo Client和Relay缓存数据。在未来版本的Apollo Client中,来自响应中的缓存控制信息将会自动过期来自客户端的旧数据。所以,就像GraphQL中的其他组成部分一样,服务器描述了它的功能,客户端指定其数据需求,所有组成部分都能很好地协作。</p> <p>接下来,我们看一下另外一个跨越整个栈的GraphQL功能样例。</p> <h2>2.跟踪</h2> <p>相对于基于端的系统,GraphQL能够让前端开发人员以一种更加细粒度的方式来使用数据。他们能够精确地请求想要的数据,忽略不会使用的字段。这样的话,就有机会探测详细的性能信息,并且能够以过去无法实现的方式来进行性能的跟踪。</p> <p><img src="https://simg.open-open.com/show/08552bd4b13f7d7fb70c1346726d4b94.png" alt="GraphQL技术栈概览:如何将所有的功能组合起来" width="800" height="249"></p> <p>不要满足于一个不透明的总查询时间——GraphQL能够让我们获取每个字段详细计时</p> <p>我们可以说GraphQL是第一个能够细粒度获取内部信息的API技术。这并不是某项工具做到的——GraphQL第一次能够让前端开发人员以合法的方式获取每个字段的执行计时,然后让他们基于此修改查询以解决相关的问题。</p> <h3>跨越整个栈进行跟踪</h3> <p>跟踪与缓存类似,协调整个栈才能真正有用。</p> <p><img src="https://simg.open-open.com/show/989d4091550faf3695a20ba61eee398a.png" alt="GraphQL技术栈概览:如何将所有的功能组合起来" width="800" height="289"></p> <p>在提供跟踪信息时,每个组成部分都有其作用,并且都可以参与进来</p> <p>服务器可以在结果中提供额外信息,就像提供缓存相关的信息类似,网关可以抽取并聚集这些信息。与缓存类似,在服务器中不想关心的复杂功能,都由网关组件负责处理。</p> <p>在这里,客户端的主要角色将查询与UI组件连接起来。这是非常重要的,我们就可以将API层的性能与其对前端影响关联起来。我们第一次能够把后端获取数据的性能与它所影响的UI组件在页面上显示出来。</p> <h3>GraphQL跟踪扩展</h3> <p>与缓存非常类似,我们可以借助GraphQL的响应扩展功能,以独立于服务器的方式来实现。Apollo Tracing规范目前已经有了 <a href="/misc/goto?guid=4959641827084532034" rel="nofollow,noindex">Node</a> 、 <a href="/misc/goto?guid=4959755884760723462" rel="nofollow,noindex">Ruby</a> 、 <a href="/misc/goto?guid=4959755884846181934" rel="nofollow,noindex">Scala</a> 、 <a href="/misc/goto?guid=4959755884926147065" rel="nofollow,noindex">Java</a> 和 <a href="/misc/goto?guid=4959755885013539282" rel="nofollow,noindex">Elixir</a> 实现,该规范定义了GraphQL服务器返回计时数据的方式,解析器(resolver)会以一种标准的方式进行解析,其他的工具都可以使用解析得到的性能数据。</p> <p>我们假设所有GraphQL工具都要访问性能数据:</p> <p><img src="https://simg.open-open.com/show/620f1e4c6f158ad9382419ae73b7461b.png" alt="GraphQL技术栈概览:如何将所有的功能组合起来" width="800" height="387"></p> <p>抽象共享让所有工具都能使用像跟踪数据这样的信息</p> <p>借助Apollo Tracing,我们能够在GraphiQL、编辑器或其他任意地方使用性能数据。</p> <p>到目前为止,我们已经看过了一个客户端和一个服务器之间的交互。最后一个样例,我们看一下GraphQL能够如何帮助我们模块化架构。</p> <h2>3.模式拼接</h2> <p>GraphQL最大的好处之一就是能够在一个地方访问所有的数据。但是,直到最近,这种方式也是有一定成本的:我们需要将整个GraphQL模式实现为一个代码库,这样的话,才能在一个请求中对所有数据进行查询。如果你的架构是模块化的,又想使用统一GraphQL API所带来的收益,那该怎么处理呢?</p> <p>模式拼接是一个很简单的理念:GraphQL能够很容易地将多个API合并成一个,这样的话,我们就可以按照独立服务的方式来实现模式中的各个组成部分。这些服务可以独立进行部署,使用不同的语言进行编写,甚至还可以归属不同的组织。</p> <p>如下是一个 <a href="/misc/goto?guid=4959755885099298897" rel="nofollow,noindex">样例</a> :</p> <p><img src="https://simg.open-open.com/show/25586c535c7d3e1512a4844e975bf78b.png" alt="GraphQL技术栈概览:如何将所有的功能组合起来" width="800" height="444"></p> <p>将GraphQL峰会票务系统的数据和一个天气API的数据组合到一个查询之中: <a href="/misc/goto?guid=4959755885099298897" rel="nofollow,noindex">https://launchpad.graphql.com/130rr3r49</a></p> <p>在上面的截图中,我们可以看到拼接后的API是如何将针对两个不同服务的独立查询联合起来的,这种方式对客户端是完全不可见的。通过这种方式,我们完全可以像搭建乐高积木那样组合GraphQL模式。</p> <p>我们目前正在提供一个该功能的实现,读者现在就可以进行尝试,它作为Apollo graphql-tools库的一部分,通过 <a href="/misc/goto?guid=4959755885184198815" rel="nofollow,noindex">文档</a> 可以了解更多信息。</p> <h3>在网关中进行拼接</h3> <p>模式拼接也可以在整个栈中很好地运行。长期来看,我们认为非常适合在新的网关层进行拼接,这样的话,就能使用任意你想要的技术来构建模式了,比如 <a href="/misc/goto?guid=4959755885278443417" rel="nofollow,noindex">Node.js</a> 、 <a href="/misc/goto?guid=4959755885364641709" rel="nofollow,noindex">Graphcool</a> 或 <a href="/misc/goto?guid=4959755885443434488" rel="nofollow,noindex">Neo4j</a> 。</p> <p><img src="https://simg.open-open.com/show/0342fb0377e826d4d51f179156eb660d.png" alt="GraphQL技术栈概览:如何将所有的功能组合起来" width="800" height="329"></p> <p>最终,拼接会与栈中的每个组成部分关联</p> <p>客户端也可以加入进来。就像可以通过一个查询加载多个后端的数据,我们同样可以在客户端组合数据源。在最近发布的 <a href="/misc/goto?guid=4959755885531275481" rel="nofollow,noindex">Apollo Client 2.0</a> 中新增了状态管理功能,该功能允许我们在一个查询中加载来自客户端状态和任意数量后端的数据。</p> <h2>结论</h2> <p>通过阅读本文或观看演讲,我希望读者能够意识到GraphQL工具如今已经非常强大了,未来有非常大的潜力。我们刚刚所接触的只是GraphQL抽象和功能的一个皮毛。</p> <p>最后,我想基于以上的理念分享一个TODO列表:</p> <p><img src="https://simg.open-open.com/show/b8fb99ee65a3bbe651eaeb765306bbeb.png" alt="GraphQL技术栈概览:如何将所有的功能组合起来" width="800" height="457"></p> <p>要集成这些新的特性,还有很多工作需要完成,尤其是开发工具和编辑器领域</p> <p>要释放GraphQL的全部潜力,还有很多的事情要做。在Apollo团队中,我们正为此竭尽全力,但是没有任何一个人、团队或组织能够完成所有的事情。为了让未来的蓝图实现,我们需要协作工作,将这些解决方案构建出来。</p> <p>不管怎样,有一件事情是非常清晰的:GraphQL已经作为一项变革性的技术用到了成千上万的企业中,但这只是开始!我迫不及待地想知道在未来的两年、五年和十年内,我们该如何构建应用,因为这肯定是非常美妙的!</p> <h2>如何参与</h2> <p>如果你像Apollo一样相信GraphQL的潜力,那么可以参与到社区中来。为了让读者快速起步,我们创建了一个 <a href="/misc/goto?guid=4959755885613860778" rel="nofollow,noindex">帮助页面</a> 。</p> <p>感谢徐川对本文的审校。</p> <p> </p> <p>来自:http://www.infoq.com/cn/articles/the-graphql-stack-how-everything-fits-together</p> <p> </p>