本文包含以下信息内容:
什么是REST?
什么是GraphQL?
关于GraphQL的详细介绍:
一. GraphQL为什么会出现?
二. GraphQL官方定义:一种用于 API 的查询语言
三. GraphQL和RESTful的区别
四. GraphQL的缺点
五. GraphQL语法及核心Schema
REST即表述性状态传递(英文:Representational State Transfer,简称REST),它符合特定的指南,是 Web API 实现的约束。是 Roy Fielding 博士在他的博士论文中提出来的一种软件架构风格。它鼓励客户端和服务器以无状态模式交换信息。 请记住,并非所有 API 都是 REST,但所有 RESTful 服务都是 API。
GraphQL是Web API 的查询语言。它由Facebook于2012年创建,并于2015年开源。它既不是架构模式,也不是Web服务。它是个中介,用来查询从各种数据源接收的数据。 这些数据源可以是数据库或Web服务。
当提起API设计的时候,大家通常会想到SOAP,RESTful等设计方式,从2000年RESTful的理论被提出的时候,在业界引起了很大反响,因为这种设计理念更易于用户的使用,所以便很快的被大家所接受。我们知道REST是一种从服务器公开数据的流行方式。
当REST的概念被提及出来时,客户端应用程序对数据的需求相对简单,而开发的速度并没有达到今天的水平。
因此REST对于许多应用程序来说是非常适合的。然而在业务越发复杂,客户对系统的扩展性有了更高的要求时,API环境发生了巨大的变化。特别是从下面三个方面在挑战api设计的方式:
Facebook开发GraphQL的最初原因是移动用户的增加、低功耗设备和松散的网络。GraphQL最小化了需要网络传输的数据量,从而极大地改善了在这些条件下运行的应用程序。
前端框架和平台运行客户端应用程序的异构环境使得我们在构建和维护一个符合所有需求的API变得困难,使用GraphQL每个客户机都可以精确地访问它需要的数据。
持续部署已经成为许多公司的标准,快速的迭代和频繁的产品更新是必不可少的。对于REST api,服务器公开数据的方式常常需要修改,以满足客户端的特定需求和设计更改。这阻碍了快速开发实践和产品迭代。
GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
向你的 API 发出一个 GraphQL 请求就能准确获得你想要的数据,不多不少。 GraphQL 查询总是返回可预测的结果。使用 GraphQL 的应用可以工作得又快又稳,因为控制数据的是应用,而不是服务器。
GraphQL 查询不仅能够获得资源的属性,还能沿着资源间引用进一步查询。典型的 REST API 请求多个资源时得载入多个 URL,而 GraphQL 可以通过一次请求就获取你应用所需的所有数据。这样一来,即使是比较慢的移动网络连接下,使用 GraphQL 的应用也能表现得足够迅速。
GraphQL API 基于类型和字段的方式进行组织,而非入口端点。你可以通过一个单一入口端点得到你所有的数据能力。GraphQL 使用类型来保证应用只请求可能的数据,还提供了清晰的辅助性错误信息。应用可以使用类型,而避免编写手动解析代码。
前面提到GraphQL可以理解为基于RESTful的一种封装,目的在于构建使Client更加易用的服务,可以说GraphQL是更好的RESTful设计。
在过去的十多年中,REST已经成为设计web api的标准(虽然只是一个模糊的标准)。它提供了一些很棒的想法,比如无状态服务器和结构化的资源访问。
然而REST api表现得过于僵化,无法跟上访问它们的客户的快速变化的需求。 GraphQL的开发是为了应付更多的灵活性和效率,它解决了与REST api交互时开发人员所经历的许多缺点和低效之处。
为了说明在从API获取数据时REST和GraphQL之间的主要区别,让我们考虑一个简单的示例场景:在blog应用程序中,应用程序需要显示特定用户的文章的标题。同一屏幕还显示该用户最后3个关注者的名称。
REST和GraphQL如何解决这种情况?
使用REST API来现实时,我们通常可以通过访问多次请求来收集数据。
比如在这个示例中,我们可以通过下面的三步来实现:
通过 /user/
获取初始用户数据
通过/user/
返回用户的所有帖子
请求/user/
返回每个用户的关注者列表
调用关系如下图所示:
如果用GraphQL的话,我们只需要一次请求就可以完成上述的需求
在GraphQL的世界里我们不用多取数据,也不用担心数据取少了,我们只需要按需获取即可。
REST最常见的问题之一是API的返回数据过多或者过少,这是因为客户端下载数据的唯一方法是通过访问返回固定数据结构的endpoint,这就会导致我们设计API非常困难,因为它既要能够为客户提供精确的数据需求,又需要满足不同调用者的需求,这本身就是相互矛盾的。GraphQL的发明者Lee Byron提出了一个很重要的概念: “用图形来思考,而不是endpoint”
通过上述直观展示我们可以得出一下几点:
通常情况下我们在调用一个通用API接口时,客户端获取的信息比应用程序中实际需要的要多。例如UI需要显示一个用户列表,而实际上只需要使用他们的名字。在REST API中通常会调用 /user 这个endpoint,并接收一个带有用户数据的JSON数组。但是这个响应可能包含更多关于返回的用户的信息,例如他们的生日或地址,而这些信息对客户来说是无用的,因为它只需要显示用户的名字。
一般来说数据获取不足意味着某个特定的endpoint没有提供客户端需要的足够信息,客户端将需要额外的请求来获取它所需要的一切。这可能会升级到客户端需要首先获取列表信息,然后需要对单条数据添加一个额外的请求以获取其他所需的数据。
REST api的一个常见模式是根据您在应用程序内部的展现逻辑来构造endpoint,这很方便,因为它允许客户端通过访问相应的endpoint获取特定视图的所有所需信息。
这种方法的主要缺点是它不允许前端的快速迭代。对于UI所做的每一个更改,现在都存在比以前更多(或更少)的数据的高风险。
因此,需要对后端进行调整,以满足新的数据需求,这会降低生产力并显著降低将用户反馈集成到产品中的能力。 使用GraphQL这个问题就解决了。
由于GraphQL的灵活性,无需在服务器上额外工作就可以在客户端上进行更改。由于客户端可以指定准确的数据需求,所以当前端的设计和数据需求发生变化时,并不需要后端API做出任何的修改就可以满足展现层的变化。
GraphQL使用强大的Type System来定义API的功能。所有在API中公开的类型都是使用GraphQL schema Definition Language (SDL)在模式中编写的。
该模式充当客户端和服务器之间的契约,以定义客户机如何访问数据。 一旦定义了模式,在前端和后端工作的团队就可以在没有进一步通信的情况下完成工作,因为他们都知道通过网络发送的数据的确切结构。
前端团队可以通过mock所需的数据结构来轻松测试他们的应用程序。一旦后端API实现完成,就可以对客户端应用程序进行切换来调用实际的API获取数据,这也可以使得我们实现更好的客户端和服务端的分离。
哇,GraphQL 很棒,这是一个众所周知的事实。但是世界上的任何东西都是有缺陷的,GraphQL 也无法置身事外。
GraphQL 不支持浏览器和移动手机缓存,这一点区别于使用本地 HTTP 缓存机制的 RESTful 服务,因此我们要为实现 GraphQL 缓存付出额外努力。虽然有 Relay 这样的工具提供了一些缓存支持,但是它们还没有 RESTful 服务使用的缓存机制成熟。
RESTful 服务利用 HTTP 状态代码来处理可能遇到的不同错误。对开发人员来说,这使得 APIs 的检验变得非常简单和轻松。但是使用 GraphQL 服务总是返回 200 OK
响应。一个典型的 GraphQL 错误消息是这样的,状态码为 200 OK
。
{
errors: [
{
message: 'Some error occurred'
}
]
}
这使得处理错误场景非常困难,并且使得检验过程非常麻烦。
和RESTful服务不同,GraphQL服务要求客户端必须知道要查询的数据模式。 如果您将API暴露给第三方,则基本上暴露了您的内部数据结构。 必须非常小心,因为客户端不用很高的代价就可以发起连接查询,这可能会导致服务器上的拒绝服务(DoS)攻击。
GraphQL社区仍然对如何处理GraphQL服务的安全部分感到困惑。仍然没有集成身份验证和授权的原生解决方案。它通常被抽象到业务逻辑层来授权用户,但是我们是否真的必须解析和验证一个未经验证的用户的查询仍然是GraphQL领域中的一个问题。
在RESTful服务中,很容易记录执行的SQL查询并进一步优化它。但在GraphQL的情况下,解析性质是动态的,因此很难获得精确的查询并进一步优化它。有时,字段解析器可能会导致N+1次查询问题和复杂的连接操作。但是Facebook正在开发像DataLoader这样的工具来解决这个确切的问题。所以,也许在未来,这不会是一个不利因素。
GraphQL在这个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来达到不同操作的效果。