GraphQL与REST:两种API架构
GraphQL
既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。GraphQL
对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
在过去十年中,REST
已经成为Web API的设计标准,提供了一些很棒的想法,例如无状态服务器和对资源的结构化访问,可以去这里详细了解REST API。但是,REST
API已经显示出太不灵活,无法满足访问客户端快速变化的需求。
GraphQL
的开发是为了满足更高灵活性和效率的需求!它解决了开发人员在与REST
API交互时遇到的许多缺点和低效问题。
REST
和GraphQL
区别
为了说明从API获取数据时REST和GraphQL之间的主要区别,让我们考虑一个简单的示例场景:在博客应用程序中,应用程序需要显示特定用户的帖子标题。同时还显示该用户的最后3个关注者的姓名。
如何通过REST和GraphQL解决这种情况?
使用REST与GraphQL进行数据获取
使用REST
API,需要设计三个接口来获取数据:
/users/<id>
:获取初始用户数据/users/<id>/posts
: 返回用户的所有帖子/users/<id>/followers
:返回每个用户的关注者列表。
在GraphQL
中只需向GraphQL
服务器发送一个包含具体数据要求的查询。然后,服务器使用JSON对象进行响应,满足需求。
使用GraphQL,客户端可以准确指定查询中所需的数据属性。
REST
REST
是最通用,也是最常用的接口设计方案,它是无状态的,以资源为核心,针对如何操作资源定义了一系列 URL 约定,而操作类型通过 GET
POST
PUT
DELETE
等 HTTP Methods 表示。
-
下载多余的数据
客户端下载的信息量超过了应用程序中实际需要的信息量。例如一个页面,只需要显示一个用户名列表。在
REST
API中,此应用程序通常会命中/users
接收带有用户数据的JSON数组,为了通用性,该接口可能返回更多的用户信息,如生日或者地址。 -
信息缺失和N+1问题
信息缺失通常意味着特定接口不能提供足够的所需信息。客户端需要提出额外的接口来获取所需要的信息。
GraphQL
GraphQL
不是 REST
的替代品,而是另一种交互形式:前端决定后端的返回结果。
GraphQL
API 的主要应用场景是 API 网关,在客户端和服务之间提供了一个抽象层。
GraphQL
带来的最大好处是精简请求响应内容,不会出现冗余字段,前端可以决定后端返回什么数据。但要注意的是,前端的决定权取决于后端支持什么数据,因此 GraphQL
更像是精简了返回值的 REST
,而后端接口也可以一次性定义完所有功能,而不需要逐个开发。现在来说说GraphQL
带来的好处:
-
提高开发速度
首先,
GraphQL
有助于减少请求数。通过单个调用来获取所需的数据比使用多个请求要容易得多。从开发的角度来看,这加快了开发速度。后端和客户端团队需要通过密切合作来定义 API、测试,并做出更改。前端、移动、物联网等客户端团队不断迭代功能,并尝试使用新的 UX 和设计。数据需求经常会发生变化,后端团队必须跟上节奏。如果客户端和后端代码由同一团队负责,那么问题就没那么严重了。
使用
GraphQL
,客户端工程师可以完全控制前端,不需要依赖任何人,因为可以使用GraphQL
查询告诉后端他们需要什么以及响应结构应该是怎样的。而不需要花时间让后端 API 团队添加或修改某些内容。GraphQL
具有自文档的特点,可以节省一些用于查找文档以便了解如何使用 API 的时间。当拥有大量
GraphQL
API后,然后有人想出了一个新的产品创意,使用已有的GraphQL
API 可以快速实现原型,比调用各种 REST API 或为新应用程序构建新的 REST API 要快得多。 -
提升性能
工程师并不是唯一从
GraphQL
中受益的人。用户也会从中受益,因为应用程序的性能获得了提升(可以感知到的):- 减少了有效载荷(客户端只需要必要的东西)
- 多个请求合并为一个请求可减少网络开销;
- 更快的 UI 更新
-
改进的安全性、强类型和验证
GraphQL
的 schema 与语言无关。对前面的示例进行扩展,我们可以在 schema 中定义 Address 类型:type Address { city: String! country: String! zip: Int }
String 和 Int 是标量类型,! 表示字段不可为空。
schema 验证是 GraphQL 规范的一部分,因此像这样的查询将返回错误,因为 name 和 phone 不是 Address 对象的字段:
{ user (id: 123) { address { name phone } } }
GraphQL 的实现
在选择实现 GraphQL API 的平台时,Node 是一个候选项,因为最初 GraphQL 用于 Web 应用程序和前端,而 Node 是开发 Web 应用程序的首选,因为它是基于 JavaScript 的。使用 Node 可以非常容易地实现 GraphQL(假设提供了 schema)。事实上,使用 Express 或 Koa 来实现只需要几行代码:
const Koa = require('koa');
const Router = require('koa-router'); // koa-router@7.x
const graphqlHTTP = require('koa-graphql');
const app = new Koa();
const router = new Router();
router.all('/graphql', graphqlHTTP({
schema: schema,
graphiql: true
}));
app.use(router.routes()).use(router.allowedMethods());
schema 是使用 npm 的 graphql 中的类型来定义的。Query 和 Mutation 是特殊的 schema 类型。
GraphQL API 的大部分实现都在于 schema 和解析器。解析器可以包含任意代码,但最常见的是以下五个主要类别:
-
调用 Thrift、gRPC 或其他 RPC 服务;
-
调用 HTTP REST API(当优先事项不是重写现有 REST API 时);
-
直接调用数据存储;
-
调用其他 GraphQL schema 查询或服务;
-
调用外部 API。
GraphQL 不一定需要客户端。一个简单的 GraphQL 请求就是一个常规的 POST HTTP 请求,其中包含了查询内容。我们可以使用任意的 HTTP 代理库(如 CURL、axios、fetch、superagent 等)来生成请求。例如,在终端中使用 curl 发送请求:
curl \
-X POST \
-H "Content-Type: application/json" \
--data '{ "query": "{ posts { title } }" }' \
https://1jzxrj179.lp.gql.zone/graphql
以下代码可以在任意一个现代浏览器(为了避免 CORS,请访问 launchpad.graphql.com)中运行。
fetch('https://1jzxrj179.lp.gql.zone/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: '{ posts { title } }' }),
})
.then(res => res.json())
.then(res => console.log(res.data));
虽然构建 GraphQL 请求很容易,但是还需要实现很多其他东西,比如缓存,因为缓存可以极大地改善用户体验。构建客户端缓存不是那么容易,所幸的是,Apollo 和 Relay Modern 等提供了开箱即用的客户端缓存。
什么时候不该使用 GraphQL?
当然,完美的解决方案是不存在的(尽管 GraphQL 接近完美),还有一些问题需要注意,例如:
-
它有单点故障吗?
-
它可以扩展吗?
-
谁在使用 GraphQL?
最后,以下列出了有关 GraphQL 可能不是一个好选择的主要原因:
-
当客户端的需求很简单时:如果你的 API 很简单,例如 /users/resumes/123,那么 GraphQL 就显得有点重了;
-
为了加快加载速度使用了异步资源加载;
-
在开发新产品时使用新的 API,而不是基于已有的 API;
-
不打算向公众公开 API;
-
不需要更改 UI 和其他客户端;
-
产品开发不活跃;
-
使用了其他一些 JSON schema 或序列化格式。