什么是 GraphQL ?第一次看到这个名词未免让人联想到数据库查询语言 SQL 。但本质上,这是两个彻底不一样的东西, GraphQL 在官方文档里的定义以下:前端
GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data.react
即 GraphQL 既是一个 API 查询语言,也指其服务端实现。但 GraphQL 不仅是为了在 API 领域搞个相似数据库的查询语言,它的诞生更涉及到 API 设计的思路转变。git
一般,一项新技术的产生都会伴随着两个背景,一个是该技术所在的领域出现了新趋势、二是原有的技术难以应对新趋势。而近几年, API 领域有几个趋势愈发值得关注:github
首先是日益增多的移动端应用,和移动端性能自己较低下的矛盾,要求数据加载过程更高效。web
再者,要知足客户端和前端快速开发、快速添加特性的需求, API 必须能快速拓展。算法
第三则是各类不一样的前端框架和平台层出不穷,然后端 API 服务面对众多的前端框架、乃至前端和客户端共享 API 的状况,其可否按需提供数据,会影响接口复用度和开发效率。数据库
而现现在在 API 领域被普遍使用的 REST 模式,面对上述愈发复杂的客户端和服务端交互,问题也渐渐浮现:编程
首先是接口灵活性差。因为设计接口粒度较粗或历史遗留缘由,接口中有时会存在当前数据交互不须要的字段,致使取到无用且多余的数据;而另外一方面,有时前端须要一份数据,却须要手动访问多个接口才能完整获取。segmentfault
第二是接口操做流程繁琐,回想下前端获取数据的过程,一般咱们要先构造 HTTP 请求,而后接收和解析服务端响应。有时还要对收到的或处理后的数据另做本地数据转储,最后才进行 UI 展现。后端
第三,随着客户端功能拓展,服务端不断增长接口。这样维护众多接口,不只服务端维护成本高,此外也不能按需提供数据、阻碍了客户端的快速迭代和拓展。
还有 REST 模式实质上是基于 HTTP 协议的,这虽让其易于被 Web 开发人员理解和上手,但也决定它不能灵活选择网络协议来解决问题。
面对 REST 模式的上述不足, Facebook 提出了他们的解决方案 – GraphQL :
前面提到 GraphQL 既是一个 API 查询语言,也指其服务端实现,因此 GraphQL 自己也由两部分组成,Facebook 将它们分别开源:
咱们来逐条了解下 GraphQL 的特性:
以下图所示,声明式的数据查询带来了接口的精确返回,服务器会按数据查询的格式返回一样结构的 JSON 数据、真正照顾了客户端的灵活性:
另外,这种数据获取方式也带来更简洁的数据查询流程。 GraphQL 认为,客户端只需描述查询结构发起查询,再把服务端响应数据用于 UI 展现便可。中间构造请求和转储数据的操做能够交由 GraphQL 客户端辅助完成。
上图是一个 GraphQL 应用的基本架构,其中客户端只和 GraphQL 层进行 API 交互,而 GraphQL 层再日后接入各类数据源。这样一来,只要是数据源有的数据, GraphQL 层均可以让客户端按需获取,没必要专门再去定接口了。
带来了更灵活的技术栈选择,好比咱们能够选择对移动设备友好的协议,将网络传输数据量最小化,实如今网络协议层面优化应用。
既然 GraphQL 有诸多优势,那又该如何接入呢?大致上,有三种接入的方式:
最为简洁的服务配置,直接操做数据库也能减小中间环节的性能损耗。
这种配置适合于旧服务的改造,尤为是在涉及第三方服务时、依然能够经过原有接口进行交互。
前两种方式的混合:
GraphQL 的一大特色即是声明式的 API Schema ,GraphQL 的 Schema 是一个声明式的查询规范(可认为是服务器和客户端间的一个查询协议),它主要由两部分组成:
GraphQL 的类型系统包含了各编程语言中通用的一些数据类型,具体可参考规范文档了解。
接下来简单介绍下 GraphQL 的 SDL 语法:
自定义类型的定义主要是在服务端完成的,语法以下:
type 类型名 {
字段名: 类型
}
复制代码
此外, GraphQL 还有 Query
, Mutation
, Subscription
等特殊的根类型,用于定义 API Schema 。咱们能够定义一个用户:
type User {
id: Int!
name: String
}
复制代码
而后定义几个用于数据操做的 API Schema :
type Query { // 基本查询 Schema
user(id: Int!): User // 传入一个 id ,返回具体用户
}
type Mutation { // 操做数据的 Schema
createUser( // 传入用户名自动建立一个用户
name: String
): User
}
type Subscription { // 监听数据变动的 Schema
userChanged: User
}
复制代码
有了这些定义好的 API Schema ,咱们就能够此来发起数据操做了。 GraphQL 的数据操做也分为 Query
, Mutation
, Subscription
三种类型。简单来说, Query
就是获取数据的基本查询;Mutation
支持对数据的增、删、改等操做;而 Subscription
则用于监听数据变更、并靠 Websocket 等协议推送变更的消息给订阅方。
基于前面的定义的用户 Schema ,咱们能够写出以下的数据操做:
query {
user(id:3) { // 查询用户 id 为3的用户
name
}
}
mutation {
createUser(name: "Tom") { // 新增一个名为 "Tom" 的用户
name
id
}
}
subscription {
userChanged { // 监听用户数据变更
name
id
}
}
复制代码
上面这些查询,根字段以后的全部内容称为查询的 payload 。服务端会按查询格式,在 data
字段返回 payload 中指定的数据,好比 createUser
这个操做就会返回以下的数据:
{
"data": {
"createUser": {
"name": "Tom",
"id": 9
}
}
}
复制代码
经过 API Schema,咱们既可指定 API 功能、同时也能定义客户端如何请求数据。但前面介绍的只是个规范,而这个 GraphQL 的规范又是如何落地实现的呢?接下来会围绕服务端、客户端、调试工具,介绍下 GraphQL 应用开发的 “生态圈”。
在服务端, GraphQL 服务器可用任何可构建 Web 服务器的语言实现。除 JavaScript 以外, Ruby , Python , Scala , Java , Clojure , Go 和 .NET 都有实现供参考。
服务端查询执行的核心算法也很简单:就是查询逐字段遍历,并为各字段执行一个 resolver 以处理数据操做。下图举了一个例子:
最左边为一个 GraphQL 查询,该语句查询了 id 为 'abc'
的做者全部文章的标题和内容。中间一副图展现了每一个查询字段对应的数据类型,而后在最右边可看到每一个字段的解析过程:首先查询 id 为 'abc'
的做者,再从该做者处获取其全部文章;而因为文章是一个列表,最后咱们还要遍历这个列表以获取各文章对应的标题、内容。
这个逐字段解析的流程清晰易懂,但若是服务器只是这么实现的话,就会面临性能问题。见下图的例子,若用户要查询文章列表下各个做者的信息,因为文章列表中可能有大量重复的做者,当处理到同一做者的文章时就要重复查询该做者信息,甚至当“查询做者信息”这操做自己就包含大量子操做的话、对服务器性能的消耗就很是可观:
对这种一个查询触发大量相同的数据操做的问题,一种解决思路是将数据操做改成批量处理。仍是用上面的例子,下图中咱们把查询做者信息的操做改成存入一个队列,待合适的时机再批量发起查询,这时查询的数量就只是队列里的一个最小子集,避免了重复操做。 Facebook 推出的 DataLoder 就是一个这样的数据批量处理和缓存的方案。
上面讨论了 GraphQL 服务端的基本实现思路,而针对 Node.js 的实现,我基于前文示例中的 API Schema 写了一个简单的 Demo ,读者可了解下 GraphQL 的服务端具体是如何实现和使用的。
常见的 GraphQL 客户端库有:
至于如何使用这两个客户端库,能够参考官方文档,这里再也不赘述。而对于 Apollo 的入门, Full-stack React + GraphQL Tutorial 一文提供了深刻浅出的示例,建议动手尝试下,构建本身第一个 GraphQL 应用吧。
GraphQL 有大量实用的开发工具,基本都是基于 introspection 查询实现的。所谓 introspection 查询,就是指客户端向服务器询问 API Schema 信息的查询。好比,咱们能够经过查询 __schema
等元字段来获取完整的类型信息:
query {
__schema {
types {
name // 获取根字段名
fields {
name // 获取字段名
}
}
}
}
复制代码
有了这样一个查询 Schema 信息的功能,就使得 GraphQL 的文档浏览器,自动补全,代码生成等开发工具很是容易实现。而开发工具中,最有名的就是 GraphiQL 了,其本质上可认为是个 GraphQL 客户端,但配有编辑、自动补全、文档浏览等功能,经常使用于服务端的调试。
前面咱们那个服务端 Demo 也以中间件形式引入了基于 GraphiQL 的调试工具 GraphQL PlayGround 。运行 Demo 后,你能够访问 localhost:3000/playground
试试上面列举的全部查询~
固然, GraphQL 也不是天衣无缝的,如今 GraphQL 主要存在安全性和服务端缓存能力两方面的问题。
GraphQL 声明式的的数据查询提供了灵活、易拓展的接口;但若是咱们发起的一次查询包含了过多的数据操做,那么这一次查询就会给数据服务器的带来巨大的压力,提高了被 DDOS 的风险。
此外,每次发起的查询语句,实质上也反映了查询文档的结构,若是被攻击者截取了咱们的请求、拼凑出完整的接口内容,这也不利于接口的安全。
面对查询压力,咱们能够经过服务端限流、客户端限流等措施来进行缓解,具体限流的措施可参见这篇文章。
而对于 API 结构公开传输的问题,有人提出一个持久化查询的方案。简单来说,就是客户端和服务端分别将约定好查询内容转换为查询ID,转而使用查询ID进行查询。这样一来既解决了查询语句公开传输的问题,而只传 ID 还顺便减小了传输的数据量、提高了传输速度。
GraphQL 能让客户端灵活地请求数据,这就样一来客户端请求内容就是不肯定的,服务端难以根据同一个链接来维护查询缓存。
关于这个问题,前面提到 Facebook 有一个 DataLoader 的技术,可用于实现查询的批量处理和缓存,但其文档中描述的缓存也只是针对单个请求进行、粒度仍是较粗。
GraphQL 做为一个新的 API 标准,经过声明式的数据获取方式,给客户端提供了简洁、灵活、高效的数据查询。适应了移动互联网时代客户端技术的快速发展和需求的快速迭代,是当前 REST 模式的有力竞争者。
同时其活跃的社区和日渐成熟的生态圈也证实了这是一个颇有生命力的技术,目前 GraphQL 已被许多的公司( Facebook , GitHub , Twitter 等等)用于生产环境中,相信其将来还有很大的发展前景。
但 GraphQL 自身存在的安全性等问题也不容忽视;此外引入 GraphQL 势必存在学习成本,在 API 设计思想上的变化页还会影响到相应的开发模式、开发流程。因此只有权衡好引入成本和收益,才能让这项新技术用在刀刃上。