将现有的 API 从 REST 迁移到 GraphQL

将现有的 API 从 REST 迁移到 GraphQL
原文标题:Moving existing API from REST to GraphQL
原文链接:https://medium.com/open-graphql/moving-existing-api-from-rest-to-graphql-205bab22c184
作者:Roman Krivtsov

最近,我发现几乎每一场有关于 Web 开发的大会都谈论到了 GraphQL。也有大量与其相关的文章被发表。但是这些文章几乎都是在讲 GraphQL 的基础概念或者是新特性,并且说得很肤浅。因此我打算谈谈我在真实大型系统中采用 GraphQL 的个人经验。

REST 存在的问题

REST 没有分离传输、安全和数据逻辑层面。这将会带来很多问题,比如:

GET 查询能力差

无法用 GET 语句进行复杂深入的查询。假设我们需要查询用户,举一个很简单的例子:

GET /users/?name=Homer

我们需要查找名字是 Homer 或者 Marge 的用户就会比较棘手。当然,我们可以为这种需求定义一些分隔符:

GET /users/?name=Homer|Marge

但不要忘记转义这些字符,如果有的人名字中包含 “|” 那么你就无法使用这种方法了。如果要结合两个不同的字段那么就更复杂了,更别说是需要同时满足上面两种条件的查询。

目前我们一般都是使用字段来查询对应的内容。但是也时常需要用查询语句来传递一些服务数据。比如页码:

GET /users/?name=Homer|Marge&limit=10&offset=20

按逻辑来说,我们后端的查询解析器应该会将 limitoffset 识别为数据库的字段,因为他们被声明为和 “name” 字段同级的参数名。我们可以发明我们自己的语法或是用 POST 方法(但其实这是不对的,因为这是一个幂等请求)但是这看起来像是在造轮子。

数据更新的问题

使用 PUT 发送整个对象是最简单的 REST 更新数据的方式。但显而易见的是,当你仅仅只需要更新 1 Mb 大小的对象中的一个字段时,这并不是最有效的方式。

HTTP 还有一个 PATCH 方法。但是它有一个问题。用算法来定义如何更新实体并不简单。现有多个规范建议你应该如何去做,比如 RFC 6902,RFC 7396 以及许多自定义解决方案。

命名问题

我想每个曾与 REST 打交道的开发人员都明白那种,当你不知道如何去命名新路由时的感受。并非所有的业务实例都可以被描述为资源。例如我们想要搜索带有商店信息的商品。

GET /search?product_name=IPhone&shop_name=IStore

这里的资源指什么?商品?商店?搜索? 这个 API 不再是 REST 风格了。

另一个典型的例子便是用户登录。它的资源又指的是什么?(其实这里没有资源,这里只是个远程过程调用而已)

后端处理 REST

app.post((req, res) => {
  const user = db.getUserByName(req.headers.name);
  const user = db.getUserByName(req.query.name);
  const user = db.getUserByName(req.path.name);
  const user = db.getUserByName(req.body.name);
});

这是一个 Express.js 路由的例子。这里我们试图让 user_id 来查找用户。让我们看一看 API 函数通常应该是什么样子:

函数接收参数,进行特定的处理并返回特定的结果。

在这个 Express 路由的例子中,我们的参数是什么?一个庞大而混乱的 req 对象,但是我们需要的仅仅是其中很小的一部分数据。

当然,这也是 Express 的一个问题(准确的说是 Node 的 HTTP 模块的问题),但是这样的接口也是因为 HTTP 的实现逐步进化而产生的——请求参数可以在任何位置。所以如果你本人不知道这个特性或者没有一份描述清晰的文档时,是不可能知道准确的参数位置。 所以你应该知道,使用没有接口文档的 REST 是如此的痛苦。

GraphQL

接下来的内容是基于你早就熟悉 GraphQL 的基础知识。如果还不熟悉,你可以从 Apollo 的关于 GraphQL 基础知识的介绍开始。如果你觉得有点太长了,你也可以从基础的GraphQL 入门指南和教程 开始。

正如我们前面所说的,REST 存在一些 GraphQL 所没有的设计上的问题。并且 GraphQL 有着巨大的发展潜力。

首先 GraphQL 提供了 RPC 方式,这意味着你将不受客户端-服务端的交互限制。GraphQL 有它自己的类型系统,这意味不容易出现令人误解的错误和漏洞。并且类型系统意味着你的客户端可以提供 item 级别的数据智能缓存。还拥有大量像是网络连接(游标和分页)、批处理、延时等的面向 Web 的特性。它使你的客户端-服务端交互尽可能的高效

但是 REST 仍然是业内标准

是的,无论我们是否喜欢,REST 都是近几年 API 的主流形式。

但是我们仍然可以为一些内部需求(比如对接一些高级客户端)去使用 GraphQL,其他的使用 REST。

为此,我们需要将 REST 路径包装成 GraphQL 类型。关于从 REST 迁移到 GraphQL,目前被提到最多的是 swapi-rest-graphql,但是它们建议使用自定义解析器,这无法满足拥有成百上千路径的大型项目。

在我在最近的三个项目中,我都是使用 Swagger 来描述 REST 接口。它或多或少都算是声明式 API 描述的标准。坦白说,我真的不知道其他人是如何能够编写毫无描述的接口的。

我们把 Swagger 作为声明式 REST 接口的标准,当然 GraphQL 也可以,我们可以看到它们其实非常相似,只是除此之外 Swagger 还尝试去描述 HTTP 细节和业务逻辑。它们都描述了传入参数和传出响应的类型。这意味着我们可以在它们之间写适配器。

REST 路径是这样子的:

GET /user/id

它可以采用 GraphQL 类型。所以现在我们只需一个库来帮助我们自动转换,你可以点击这个链接来实现:GitHub 文档

Swagger2graphQL 接收你的 Swagger schema 然后返回 GraphQL schema,同时解析程序将自动构建 HTTP 请求到已有的 REST 路径上。

它被构建为一个将拥有超过 150 个路径的真实大型系统迁移到 GraphQL 的副项目。我们需要高效快捷、稳定地尽快迁移到 GraphQL。

只需要克隆资源库,运行

npm install && npm start 

然后访问 http://localhost:3009/graphql 。你会看到封装在 petstore.swagger.io/ Swagger 示例接口上的 GraphQL 接口。

现在你同时拥有了这两个API:REST 和 GraphQL API。此外,你还可以考虑GraphQL模式拼接,它可以将多个不同的API源合并到一个GraphQL API中。例如,你可以缝合转换后的遗留REST API,并使用纯GraphQL继续编写新的端点。或许你可以看看GraphQL API 设计指南:最佳实践

而且,有了 Swagger 和 GraphQL 编写新的路径将变得十分方便。如果你早就熟悉 GraphQL,你可能会发现有时候类型描述看起来相当冗长,因为你需要去创建大量的隐式类型。Swagger2graphQL 可以自动完成这些步骤,你只需要在 Swagger schema 中创建一个新的带有声明的路径就可以了,这个操作一般来说是很简单的。当然除了 Swagger,还有很多其他工具可以使用,比如 Apifox,你可以看看GraphQL 工具推荐

订阅
qrcode

订阅

随时随地获取 Apifox 最新动态