GraphQL与REST:两种API架构

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

在过去十年中,REST已经成为Web API的设计标准,提供了一些很棒的想法,例如无状态服务器和对资源的结构化访问,可以去这里详细了解REST API。但是,REST API已经显示出太不灵活,无法满足访问客户端快速变化的需求。

GraphQL的开发是为了满足更高灵活性和效率的需求!它解决了开发人员在与REST API交互时遇到的许多缺点和低效问题。

 RESTGraphQL区别

为了说明从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 接近完美),还有一些问题需要注意,例如:

  1. 它有单点故障吗?

  2. 它可以扩展吗?

  3. 谁在使用 GraphQL?

最后,以下列出了有关 GraphQL 可能不是一个好选择的主要原因:

  • 当客户端的需求很简单时:如果你的 API 很简单,例如 /users/resumes/123,那么 GraphQL 就显得有点重了;

  • 为了加快加载速度使用了异步资源加载;

  • 在开发新产品时使用新的 API,而不是基于已有的 API;

  • 不打算向公众公开 API;

  • 不需要更改 UI 和其他客户端;

  • 产品开发不活跃;

  • 使用了其他一些 JSON schema 或序列化格式。