长期以来,REST 是构建 API 的唯一“标准”。它在某种程度上取代了 SOAP,后者是“太多 XML”的丑陋混乱。但近年来,新的替代方案出现了。2015 年,Facebook 向公众发布了 GraphQL,2016 年 Google 也紧随其后,发布了gRPC。在本文中,我们将重点关注后者,并将其与仍然广泛使用的 REST 进行比较。
概述
下表将概述所讨论的要点,并显示 REST 和 gRPC 的真正优势。
话题 | 休息 | 远程过程调用 |
---|---|---|
标准化 | 无标准 | 明确定义 |
范例 | 基于资源 | 远程过程调用 |
服务方式 | 仅限一元 | 一元、客户端流、服务器流和双向流 |
要求 | 任何 HTTP 版本,JSON 解析器 | HTTP/2、gRPC 语言实现 |
API设计 | 代码优先 | 设计第一 |
默认数据格式 | JSON | 原始缓冲区 |
网络浏览器支持 | 本国的 | 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 定义明确。例如,通过 HTTP/2 的 gRPC 实现非常详细。
根本差异
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
端点吗?这还是“安逸”吗?虽然技术上很容易解决,但它经常会在开发人员之间引起长时间的讨论。
另一方面,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 设计 API,然后生成服务器存根,但这并不是许多开发人员采用的方法。如果存在 OpenAPI 定义,则 OpenAPI 定义更有可能是从 API 实现生成的。因此,API 定义与实现紧密耦合。错误模型/类的更改可能会导致 API 意外发生重大更改。
gRPC 使用不同的方法,必须先定义 API,然后才能实现它(称为“设计优先”)。然后根据此 API 定义生成客户端和服务器存根。这需要提前进行一些思考,因为无法直接跳到实现 API。
两种方法都有其优点和缺点。通常的 REST API 方法允许更快的迭代,因为服务器始终是事实的来源。对于 gRPC,在调整实现之前先更改 API 定义可能会很烦人。然而,它通过显式定义 API 带来了一些安全优势。
数据格式
REST 和 gRPC 都可以使用不同的格式来传输数据。大多数 REST API 使用 JSON,而 gRPC默认使用Protocol Buffers (Protobuf),因此我们将比较这两者。
JSON 对数据类型的支持有限,并且也有一些怪癖(例如,大数字需要表示为字符串)。它是一种人类可读的文本格式。字段名称是序列化的,会占用一些空间。在某些编程语言中,这还需要使用反射来反序列化 JSON 消息,这是相当慢的。
如上所述,gRPC API 和相应的消息类型首先被定义为协议缓冲区。每条消息都是强类型的,可能包含有用的注释,并且有许多其他有趣的功能。对于受支持的编程语言列表,可以自动生成用于(反)序列化消息的代码。由于它是二进制格式并且不会序列化字段名称,因此它比等效的 JSON 消息使用更少的空间。这样做的缺点是它不再是人类可读的,并且需要 Protobuf 定义来反序列化消息,这可能会妨碍开发人员的体验。
以下 JSON 示例将占用大约 66 个字节(删除空格)。
{
"persons": [
{
"name": "Max",
"age": 23
},
{
"name": "Mike",
"age": 52
}
]
}
等效的序列化 protobuf 消息仅使用 19 个字节。
0x0A070A034D617810170A080A0448616E731034
大消息
Protobuf 旨在对内存中的消息进行序列化和反序列化。因此,不建议使用 Protobuf/gRPC 传输大消息。大多数 gRPC 实现对单个消息设置默认的 4 MB 限制。
使用 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 相比,gRPC API 在网络上使用起来更加麻烦。
解决方法可能是使用JSON 转码,它允许开发人员将 gRPC API 公开为 REST API。
模具
gRPC 和 REST 工具在编程语言和框架之间存在很大差异。在某些方面,gRPC 感觉更“原生”,而在另一些方面,REST 工具则要先进得多。
对 gRPC 的适当语言支持更为重要,因为它需要工具来生成客户端和服务器存根。对于不受支持的编程,你就不走运了。REST API 的客户端始终可以手动创建,但这可能需要一些努力。虽然存在根据 OpenAPI 定义创建 REST 客户端的工具,但与 gRPC 等价物相比,它们的开发人员体验通常乏善可陈。
由于 REST API 已经存在了很长时间,因此存在更多有助于构建、测试和部署 REST API 的工具。它们的功能通常比 gRPC 工具更先进。这也是我们构建Kreya的主要原因之一,它试图成为最好的 gRPC GUI 客户端(同时也支持 REST)。
结论
REST 和 gRPC 都有各自的优点和缺点。
从 Web 应用程序使用 REST API 通常更容易。REST 也得到了更广泛的使用,这使得一些开发人员更容易使用,因为他们可能不了解 gRPC。
在我看来,gRPC 在服务器到服务器通信(例如微服务之间)方面绝对具有优势。能够共享准确的 API 定义并以多种编程语言创建 API 客户端是一个巨大的胜利。
“我们应该使用 REST 还是 gRPC?”没有明确的答案。某些 API 可能有独特的用例,gRPC 或 REST 可能更适合。或者,开发人员可能对 REST 或 gRPC 更加熟悉或更有经验。所有这些理由都很好。最后,每个人都必须自己决定使用哪种技术。