GraphQL:一种更高效、强大和灵活的数据提供方式

13次阅读

共计 4987 个字符,预计需要花费 13 分钟才能阅读完成。

在前几天的《StateOfJS: 2018 年 JavaScript 生态圈趋势报告》一文中,我们看到了 2018 年在数据层 GraphQL 的发展势头猛烈,并且大部分用户用过都说好,但如上图数据显示,目前国内的使用人数还很少,大部分人连听都没听过,今天小肆就为大家介绍一下,何为 GraphQL。
一. GraphQL 为何会出现?
当提起 API 设计的时候,大家通常会想到 SOAP,RESTful 等设计方式,从 2000 年 RESTful 的理论被提出的时候,在业界引起了很大反响,因为这种设计理念更易于用户的使用,所以便很快的被大家所接受。我们知道 REST 是一种从服务器公开数据的流行方式。
当 REST 的概念被提及出来时,客户端应用程序对数据的需求相对简单,而开发的速度并没有达到今天的水平。
因此 REST 对于许多应用程序来说是非常适合的。然而在业务越发复杂,客户对系统的扩展性有了更高的要求时,API 环境发生了巨大的变化。特别是从下面三个方面在挑战 api 设计的方式:
1. 移动端用户的爆发式增长需要更高效的数据加载
Facebook 开发 GraphQL 的最初原因是移动用户的增加、低功耗设备和松散的网络。GraphQL 最小化了需要网络传输的数据量,从而极大地改善了在这些条件下运行的应用程序。
2. 各种不同的前端框架和平台
前端框架和平台运行客户端应用程序的异构环境使得我们在构建和维护一个符合所有需求的 API 变得困难,使用 GraphQL 每个客户机都可以精确地访问它需要的数据。
3. 在不同前端框架,不同平台下想要加快产品快速开发变的越来越难
持续部署已经成为许多公司的标准,快速的迭代和频繁的产品更新是必不可少的。对于 REST api,服务器公开数据的方式常常需要修改,以满足客户端的特定需求和设计更改。这阻碍了快速开发实践和产品迭代。
二. GraphQL 官方定义:一种用于 API 的查询语言
GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
请求你所要的数据:不多不少
向你的 API 发出一个 GraphQL 请求就能准确获得你想要的数据,不多不少。GraphQL 查询总是返回可预测的结果。使用 GraphQL 的应用可以工作得又快又稳,因为控制数据的是应用,而不是服务器。
获取多个资源:只用一个请求
GraphQL 查询不仅能够获得资源的属性,还能沿着资源间引用进一步查询。典型的 REST API 请求多个资源时得载入多个 URL,而 GraphQL 可以通过一次请求就获取你应用所需的所有数据。这样一来,即使是比较慢的移动网络连接下,使用 GraphQL 的应用也能表现得足够迅速。
描述所有的可能:类型系统
GraphQL API 基于类型和字段的方式进行组织,而非入口端点。你可以通过一个单一入口端点得到你所有的数据能力。GraphQL 使用类型来保证应用只请求可能的数据,还提供了清晰的辅助性错误信息。应用可以使用类型,而避免编写手动解析代码。
三. GraphQL 和 RESTful 的区别

前面提到 GraphQL 可以理解为基于 RESTful 的一种封装,目的在于构建使 Client 更加易用的服务,可以说 GraphQL 是更好的 RESTful 设计。
在过去的十多年中,REST 已经成为设计 web api 的标准(虽然只是一个模糊的标准)。它提供了一些很棒的想法,比如无状态服务器和结构化的资源访问。
然而 REST api 表现得过于僵化,无法跟上访问它们的客户的快速变化的需求。GraphQL 的开发是为了应付更多的灵活性和效率,它解决了与 REST api 交互时开发人员所经历的许多缺点和低效之处。
为了说明在从 API 获取数据时 REST 和 GraphQL 之间的主要区别,让我们考虑一个简单的示例场景:在 blog 应用程序中,应用程序需要显示特定用户的文章的标题。同一屏幕还显示该用户最后 3 个关注者的名称。
REST 和 GraphQL 如何解决这种情况?
使用 REST API 来现实时,我们通常可以通过访问多次请求来收集数据。
比如在这个示例中,我们可以通过下面的三步来实现:

通过 /user/<id> 获取初始用户数据
通过 /user/<id>/posts 返回用户的所有帖子
请求 /user/<id>/followers 返回每个用户的关注者列表

调用关系如下图所示:
如果用 GraphQL 的话,我们只需要一次请求就可以完成上述的需求
在 GraphQL 的世界里我们不用多取数据,也不用担心数据取少了,我们只需要按需获取即可。
REST 最常见的问题之一是 API 的返回数据过多或者过少,这是因为客户端下载数据的唯一方法是通过访问返回固定数据结构的 endpoint,这就会导致我们设计 API 非常困难,因为它既要能够为客户提供精确的数据需求,又需要满足不同调用者的需求,这本身就是相互矛盾的。GraphQL 的发明者 Lee Byron 提出了一个很重要的概念:“用图形来思考,而不是 endpoint”
通过上述直观展示我们可以得出一下几点:
1. 获取了许多多余的数据
通常情况下我们在调用一个通用 API 接口时,客户端获取的信息比应用程序中实际需要的要多。例如 UI 需要显示一个用户列表,而实际上只需要使用他们的名字。在 REST API 中通常会调用 /user 这个 endpoint,并接收一个带有用户数据的 JSON 数组。但是这个响应可能包含更多关于返回的用户的信息,例如他们的生日或地址,而这些信息对客户来说是无用的,因为它只需要显示用户的名字。
2. 获取的数据少于 Client 所需要的数据
一般来说数据获取不足意味着某个特定的 endpoint 没有提供客户端需要的足够信息,客户端将需要额外的请求来获取它所需要的一切。这可能会升级到客户端需要首先获取列表信息,然后需要对单条数据添加一个额外的请求以获取其他所需的数据。
3. 前端的快速产品迭代对 API 有很大的挑战
REST api 的一个常见模式是根据您在应用程序内部的展现逻辑来构造 endpoint,这很方便,因为它允许客户端通过访问相应的 endpoint 获取特定视图的所有所需信息。
这种方法的主要缺点是它不允许前端的快速迭代。对于 UI 所做的每一个更改,现在都存在比以前更多 (或更少) 的数据的高风险。
因此,需要对后端进行调整,以满足新的数据需求,这会降低生产力并显著降低将用户反馈集成到产品中的能力。使用 GraphQL 这个问题就解决了。
由于 GraphQL 的灵活性,无需在服务器上额外工作就可以在客户端上进行更改。由于客户端可以指定准确的数据需求,所以当前端的设计和数据需求发生变化时,并不需要后端 API 做出任何的修改就可以满足展现层的变化。
4. Schema 和类型系统的好处
GraphQL 使用强大的 Type System 来定义 API 的功能。所有在 API 中公开的类型都是使用 GraphQL schema Definition Language (SDL)在模式中编写的。
该模式充当客户端和服务器之间的契约,以定义客户机如何访问数据。一旦定义了模式,在前端和后端工作的团队就可以在没有进一步通信的情况下完成工作,因为他们都知道通过网络发送的数据的确切结构。
前端团队可以通过 mock 所需的数据结构来轻松测试他们的应用程序。一旦后端 API 实现完成,就可以对客户端应用程序进行切换来调用实际的 API 获取数据,这也可以使得我们实现更好的客户端和服务端的分离。
四. GraphQL 语法
基础语法
其实 GraphQL 所需要学习的语法很少,大部分语法与我们平时的语法一致,可以通过官网详细了解。
首先,GraphQL 是一门强类型语言,所以和我们在数据库定义一张表一样,我们需要定义每一个属性的类型. 如下图所示:
下面是一个简单的类型定义,先是定义了一个枚举,然后我们定义了一个类型,类型中有四个属性:id、name、friends、appearsIn, 其中 id 和 name 是标量类型,而 friends 是一个 Person 类型,这是一个嵌套类型,仔细想想应该没什么毛病,毕竟你的朋友和你一样,都是人,而 appearsIn 是一个枚举类型,看起来还是很熟悉的。
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
type Person{
id: ID!
name: String!
friends: [Person]
appearsIn: [Episode]!
}
了解完类型,再了解一下 Arguments 和 resolver, 两者都是偏服务端一些,但是了解一下,对 graphql 的使用原理有进一步的认识。
对于一个 Restful API 来讲,除了知道接口 URL,我们还需要知道接口的传参定义,对于 GraphQL 其实也一样,虽然 URL 只有一个,不同的接口通过 type 来区别,但传参同 Restful API 一样,体现了客户端与服务端的交互。
比如下面,查询的目标是 id = 2 的用户,获取他的用户名:
Query{
user(id: 2) {
id
userName
}
}
而在服务端定义一个接口时,我们也需要去定义入参,主要从两个方面,一是类型,二是其是否必填,比如下面这样:
接口定义
user: {
type: UserType,
args: {
id: {type: GraphQLID}
},
resolve: (root, args, context, info) => {
const {id} = args;
return getUser(id);
}
}

查询定义

上面的代码只是定义了一个输入属性 id, 并未定义其是否是必填,所以当查询时,如果没有配置查询 id,查询不会报错,只会获取一个为 null 的空值结果。但是讲道理的话,我们希望这是一个必填项,所以我们需要修改服务端的代码, 将 id: {type: GraphQLID} 更换为 id: {type: new GraphQLNonNull(GraphQLID)},这句代码的含义就表示 id 是一个类型为 ID 的必填项,再次执行我们的查询可以得到下面的错误提示,提示 id 是一个必填的 ID 类型,同时右侧也没有获取到为空的查询结果.

在讲上面 Arguments 时候,可以零星的看到 type 中有一个 resolve 方法,其接收 root, args, context, info 四个参数。
其中 root 代表这个 type 上父节点的 resolve 值(因为 GraphQL 支持嵌套查询),args 就是上面讲的,context 表在 Resolver 解析链中不断传递的中间变量, 和 react 的上下文相似,而 info 这个概念,是当前 Query 的 AST 对象,比较抽象,但是可以通过查看 info, 获取这个 QUERY 的编译对象。这个方法也是后端服务编写的重点部分,常常我们可以在这里与已有的 Restful API 关联起来。
核心概念
Schema 可以说是 GraphQL 最具核心的部分,其描述了整个接口向外暴露的形式。
像 Restful API,我们会定义一个查询所有人的接口 url 定义为:/api/v1/user/getUsers
查询人具体信息的接口 url 为:/api/v1/user/getUserById
新增一个人员的接口 url 定义为:/api/v1/user/createUser
这样前端人员调用起来会很直观。
但是 graphql 是完全不一样的使用方式,其向前端暴露的 url 就一个像 /api/graphql 之类的,那这么多接口怎么区分呢?我们来看看:
奥妙就是上面这张图,一个 graphql 接口都有一个 Schema 定义。
其定义三种操作方式:query(查询),mutation(变更)和 subscription(监听)。
再往下延伸,一个查询中包含多个 field,也就是多种不同的查询,比如 query user 查询人,query message 查询消息,query weather 查询天气。
通过这些就实现了 Restful API 使用多个 url 来达到不同操作的效果。
总结:
今天我们只是讲了一些 GraphQL 的基本知识,但我们依然可以看出 GraphQL 的出现可以使我们后端 API 具有更大的灵活性以及扩展性,满足了不同 client 对数据的需要,大大丰富了 API 的数据提供的能力。
部分内容来源:https://segmentfault.com/a/11…

正文完
 0