在很长一段时间里,REST 是构建 API 的唯一“标准”。它在某种程度上取代了 SOAP,后者因为“太多的 XML”而变得混乱不堪。
但近年来,新的选择出现了。2015 年,Facebook 向公众发布了 GraphQL,2016 年,谷歌紧随其后,发布了 gRPC。在这篇文章中,我们将重点关注后者,并将其与仍然广泛使用的 REST 进行比较。
概述
下表将为您提供讨论要点的概览,并展示了 REST 和 gRPC 真正闪耀的地方。
主题 | REST | gRPC |
---|---|---|
标准化 | 无标准 | 定义明确 |
范式 | 基于资源 | RPC |
服务模式 | 仅单向 | 单向、客户端流、服务器流和双向流 |
要求 | 任何 HTTP 版本,JSON 解析器 | HTTP/2,gRPC 语言实现 |
API 设计 | 代码优先 | 设计优先 |
默认数据格式 | JSON | Protobuf |
浏览器支持 | 原生 | gRPC Web,通过变通方法 |
工具 | 更成熟的工具 | 语言支持各异,部分有出色的实现 |
标准化
REST 的一个缺点是缺乏标准化。REST 更像是一种范式,而不是 API 标准,许多人对它的理解各不相同。对大多数人来说,“REST API”一词用于指代基于 HTTP 的 JSON API。对其他人来说,REST 可以与某些规范如 HATEOAS 或 JSON:API 互换使用。但即使使用 XML 而不是 JSON,API 仍然可以是 RESTful 的,尽管这一点并不广为人知。REST 这个术语甚至不与 HTTP 绑定。这在处理 REST API 时可能导致很多混淆。例如,消费者可能会自动期望某些 REST API 端点具有幂等性或可缓存性,尽管这些并没有明确定义。
相比之下,gRPC 定义明确。例如,gRPC 在 HTTP/2 上的实现非常详细。
根本差异
REST 和 gRPC 的范式不同。
在 REST 中,一切都围绕资源展开,资源可以被检索和操作。如果我们以书籍为例,REST API 通常会提供以下端点:
GET /books
(获取所有书籍,很可能带有用于过滤和分页结果的参数)GET /books/{id}
(获取特定书籍)POST /books
(创建书籍)DELETE /books/{id}
(删除书籍)
大多数基于 HTTP 的 REST API 都遵循这种模式。虽然这种方式运作良好,但在某些情况下,作为 REST API 表示起来比较困难。例如,如果我想一次性创建多本书籍,而不想为每本书重复调用POST /books
(出于性能、幂等性或其他原因),我该怎么办?我创建一个POST /books/batch
端点吗?这还是“RESTful”的吗?虽然技术上容易解决,但它经常在开发者之间引发长时间的讨论。
另一方面,gRPC 是一个 RPC 框架。它围绕服务方法展开。如果我们以书籍 API 为例,使用 gRPC,我们会创建一个BookService
,包含以下方法:
GetBooks()
GetBook()
CreateBook()
DeleteBook()
我们可以随意命名这些方法,并需要任何我们需要的参数。如果我们现在想添加一个创建多本书籍的方法,没有什么可以阻止我们添加一个CreateBooks()
方法。gRPC 在设计 API 时提供了更多的“自由”,因为(自我施加的)限制更少。
服务模式
gRPC 支持四种服务方法:
- 单向: 发送单个请求,接收单个响应
- 服务器流: 发送单个请求,接收多个响应
- 客户端流: 发送多个请求,接收单个响应
- 双向流: 发送多个请求,接收多个响应
与仅支持单向请求的 REST 相比,这是 gRPC 的一个非常大的优势。在 REST API 中支持其他服务模式将需要使用不同的协议,如服务器发送事件或 WebSockets,这并不完全是“RESTful”的。
要求
REST API 通常“只要工作”就可以与任何类型的 HTTP 版本一起使用。只要编程语言具有 HTTP 客户端和 JSON 解析库,消费 REST API 就变得轻而易举。
gRPC 明确需要 HTTP/2 支持,否则它将无法工作。近年来,这已不再是一个问题,因为大多数代理和框架都增加了对 HTTP/2 的支持。
由于 gRPC 需要代码生成(用于创建客户端或服务器存根),因此只支持一组编程语言。
API 设计
REST API 通常是其实现的结果,被称为“代码优先”。虽然可以先设计 OpenAPI,然后生成服务器存根,但这不是许多开发者采取的方法。OpenAPI 定义更有可能从 API 实现中生成,如果有 OpenAPI 定义的话。因此,API 定义与实现紧密耦合。错误的模型/类的更改可能导致 API 的意外破坏性更改。
gRPC 采用不同的方法,其中 API 必须在实现之前定义(被称为“设计优先”)。然后从这个 API 定义生成客户端和服务器存根。这需要一些预先思考,因为不能直接跳入实现 API。
两种方法都有其优缺点。通常的 REST API 方法允许更快的迭代,因为服务器始终是真实的来源。使用 gRPC,可能很烦人,必须首先更改 API 定义,然后才能调整实现。然而,它通过明确定义 API 带来了一些安全优势。
数据格式
REST 和 gRPC 都可以使用不同的格式传输数据。大多数 REST API 使用 JSON,而 gRPC 默认使用 Protocol Buffers(Protobuf),因此我们将比较这两种。
JSON 对数据类型的支持有限,也有一些怪癖(例如,大数字需要作为字符串表示)。它是一种文本格式,人类可读。字段名被序列化,这占用了一些空间。在某些编程语言中,这也需要使用反射来反序列化 JSON 消息,这相当慢。
如上所述,gRPC API 及其相应的消息类型首先被定义为 Protocol Buffers。每个消息都是强类型的,可能包含有用的注释,并且有许多其他有趣的特性。对于支持的编程语言列表,可以自动生成(反)序列化消息的代码。由于它是一种二进制格式,并且不序列化字段名,它比等效的 JSON 消息占用的空间更少。这确实有一个缺点,即它不再是人类可读的,需要 Protobuf 定义来反序列化消息,这可能会妨碍开发体验。
以下 JSON 示例大约占用 66 字节(去除空格)。
{
"persons": [
{
"name": "Max",
"age": 23
},
{
"name": "Mike",
"age": 52
}
]
}
等效的序列化 protobuf 消息只会使用 19 字节。
0x0A070A034D617810170A080A0448616E731034
大消息
Protobuf 旨在在内存中序列化和反序列化消息。因此,不建议使用 Protobuf/gRPC 传输大消息。大多数 gRPC 实现对单个消息设置了默认的 4MB 限制。
使用 REST API 处理大数据大小(如文件上传)相对直接。接收到的文件可以作为流处理,使用很少的内存。这在 gRPC 中并非不可能,但需要更多的手动努力。文件需要在发送方分成几个部分。然后每个部分作为单独的消息通过客户端流方法发送到服务器。服务器接收每个部分,并可以从中构建数据流,从而实现与 REST API 类似的行为,尽管需要更多的努力。
浏览器兼容性
这是 REST 真正闪耀的地方。它被 Web 浏览器原生支持,使得从 Web 应用程序消费 REST API 变得毫不费力。
gRPC 并不直接被浏览器支持,因为它需要明确的 HTTP/2 支持和访问某些 HTTP/2 特性,而 Web 浏览器并不提供。作为变通方法,可以使用 gRPC Web。它是 gRPC 协议的轻微变体,使其可以被 Web 浏览器消费。对于某些编程语言,gRPC Web 支持已经包含在框架中。对于其他语言,需要一个代理来将 gRPC 流量转换为 gRPC Web 流量,反之亦然。与不需要依赖的 REST API 相比,从 Web 消费 gRPC API 更加繁琐。
一个变通方法是使用 JSON 转码,它允许开发人员将 gRPC API 作为 REST API 公开。
gRPC 和 REST 工具在编程语言和框架之间的差异很大。在某些情况下,gRPC 感觉更“原生”,而在其他情况下,REST 工具更加先进。
适当的 gRPC 语言支持非常重要,因为它需要工具来生成客户端和服务器存根。对于不支持的编程语言,你将无计可施。REST API 的客户端总是可以手动创建的,但这可能需要一些努力。虽然存在从 OpenAPI 定义创建 REST 客户端的工具,但与 gRPC 等效工具相比,它们的开发体验通常较差。
由于 REST API 已经存在了很长时间,因此存在更多帮助构建、测试和部署 REST API 的工具。它们的功能通常比 gRPC 工具更先进。
结论
REST 和 gRPC 都有其优点和缺点。
从 Web 应用程序消费 REST API 通常更容易。 REST 也更广泛地被使用,对于某些开发者来说,可能更简单,因为他们可能不了解 gRPC。
在我看来,gRPC 在服务器到服务器通信(例如,微服务之间)中肯定有优势。 能够共享确切的 API 定义,并在多种编程语言中创建 API 客户端是一个巨大的胜利。