长期以来,REST是构建API的唯一“标准”。它取代了混乱的“太多XML”的SOAP。但是近年来,出现了新的替代品。2015 年,Facebook 向公众发布了GraphQL,而 2016 年,Google 推出了gRPC。本文将重点介绍 gRPC,并与仍广泛使用的 REST 进行比较。
概述
以下表格将为您提供讨论点的概述,并显示 REST 和 gRPC 的优劣之处。
主题 | REST | gRPC |
标准化 | 没有标准 | 定义明确 |
范式 | 基于资源 | RPC |
服务模式 | 仅一元 | 一元、客户端流、服务器流和双向流 |
要求 | 任何HTTP版本、JSON解析器 | HTTP/2、语言的gRPC 实现 |
API设计 | 先写代码 | 先设计 |
默认数据格式 |
JSON | Protobuf |
Web浏览器支持 | 原生 | gRPC web,通过解决方案支持 |
工具 | 更成熟的工具 | 语言支持各不相同,有些拥有出色的实现 |
标准化
REST 的缺点之一是缺乏标准化。REST 更像是一种范式,而不是 API 标准,许多人谈到 REST 时意思不一。对于大多数人来说,“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
接口吗?这还是“RESTful”吗?虽然在技术上很容易解决,但通常会在开发人员之间引起长时间的讨论。
另一方面,gRPC 是一个 RPC 框架。它以服务方法为中心。如果以书籍API为例,使用gRPC,我们将创建一个 BookService,其中包含以下方法:
- GetBooks()
- GetBook()
- CreateBook()
- DeleteBook()
我们可以根据需要随意命名这些方法并要求任何参数。如果现在我们想添加一个方法来创建多本书,那么我们可以添加一个 CreateBooks()方法。与REST API相比,gRPC在设计API时提供了更多“自由”,因为它没有(自我)限制。
服务模式
gRPC 支持四种服务方法:
- 一元:发送一个请求,接收一个响应
- 服务器流:发送一个请求,接收多个响应
- 客户端流:发送多个请求,接收一个响应
- 双向流:发送多个请求,接收多个响应
这是gRPC相对于REST的一个很好的优势,因为REST仅支持一元请求。支持其他服务模式在REST API中需要使用不同的协议,例如服务器发送事件或WebSocket,这不太“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 和相应的消息类型首先定义为 Protocol Buffers。每个消息都是强类型的,可能包含有用的注释和许多其他有趣的功能。对于支持的编程语言列表,可以自动生成(反)序列化消息的代码。由于它是二进制格式,不序列化字段名称,因此使用的空间比等效的 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相比,从 Web 上消费 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 中的一个。所有这些原因都是可以接受的。最终,每个人都必须自己决定使用哪种技术。