为什么 gRPC 很重要?
现在我们对 gRPC 有了一定的了解,接下来我们可以讨论它为何重要。为了阐明 gRPC 的作用,我们将与另外两种客户端/服务器通信方式进行对比。第一种是基于 HTTP 和 JSON 的传统 REST API 架构,第二种是 GraphQL。
REST
虽然我假设阅读本书的大多数人都熟悉 REST API,但我仍然认为介绍设计这类 API 的原则是很重要的。这将帮助我们理解 gRPC 在哪些方面与 REST 相似,哪些方面又不同。
REST API 和 gRPC、GraphQL 的主要区别在于,REST 并不是一个框架,而是一组可以以不同方式实现的架构实践。其主要约束如下:
-
客户端、服务器和资源是通信中的主要实体。客户端从服务器请求资源,服务器返回相关的资源。
-
请求和响应通过 HTTP 管理。使用
GET
来读取资源,POST
来创建资源,PUT
来更新资源,PATCH
来部分更新资源,DELETE
来删除资源。 -
请求之间不应存储与客户端相关的信息。这是一种无状态的通信,每个请求都是独立的。
最后,尽管 REST API 并不绑定于任何特定的数据格式,但最常用的数据格式是 JSON。这主要是因为 JSON 拥有广泛的社区支持,许多语言和框架都能够处理这种数据格式。
GraphQL
GraphQL 被呈现为一种 API 查询语言。它允许开发者编写数据架构,描述可用的数据,并允许他们查询架构中某些特定的字段。
由于它允许我们编写查询,我们可以拥有更通用的端点,并仅请求我们关心的特定字段。这解决了过度获取(overfetching
)和获取不足(underfetching
)的问题,因为我们只获取请求的数据量。
此外,由于 GraphQL 主要使用 JSON
作为数据格式,并且在其数据架构中使用了显式的类型和注释,这使得 GraphQL 更具可读性并且自带文档功能。这使得 GraphQL 对于大规模公司更为成熟,因为我们可以在编译时进行类型检查,从而缩短反馈循环,且无需将文档和代码分开,这样就不太可能出现文档和代码不同步的情况。
与 gRPC 的对比
现在我们已经对每种技术有了一个概述,我们可以开始将它们与 gRPC 进行比较。我们将专注于这四种 API 设计方式之间的最大区别。以下是这些区别:
-
传输、数据格式和数据架构
-
API 端点的关注点分离
-
开发者在编写 API 时的工作流程
-
开箱即用功能的便利性
传输、数据格式和数据架构
在这方面,GraphQL 和 REST API 是相似的。它们都使用 HTTP/1.1 作为底层传输协议,且通常开发者使用 JSON 进行结构化数据传输。而另一方面,gRPC 默认使用 HTTP/2 和 Protobuf
。这意味着,在 gRPC 中,我们可以传输更小的负载,并且能够更高效地管理连接。
与 JSON 相比,处理 Protobuf
时需要更小心一些。Protobuf
会根据字段类型提供隐式默认值,这些默认值不会序列化到最终的二进制文件中。例如,int32
类型的默认值是 0。这意味着我们无法区分 0 是被显式设置的值,还是字段没有被设置。当然,有办法处理这种情况,但这使得客户端的使用变得稍微复杂一些。在这方面,GraphQL 处理默认值的方式则不同。我们可以将默认值作为端点的参数传递,这意味着我们可以以更用户友好的方式处理特殊情况。
最后,值得一提的是,这些技术在数据格式的传输上都很灵活。REST API 可以处理二进制数据和其他类型的数据,GraphQL 也可以接受二进制数据,而 gRPC 也可以传输 JSON 数据。然而,这种灵活性带来了问题。如果你在 REST API 中使用二进制数据,那么你让客户端和服务器自己解释这些二进制数据的含义。这样就没有了类型安全性,我们需要手动处理序列化/反序列化错误,而这些通常是由库或框架来处理的。如果在 GraphQL 中使用二进制数据,你将大大减少可以使用的社区工具数量。而如果在 gRPC 中使用 JSON 数据,你将失去 Protobuf
带来的所有优势。
API 端点的关注点分离
设计 API 的关注点分离可能很棘手,并可能导致如获取过多数据或过少数据的问题。GraphQL 旨在解决这些问题,它可以让你仅请求你需要的特定字段。虽然使用 gRPC 和 REST API 也可以做类似的事情,但当你的 API 面向外部用户时,这种方式并不那么友好。
然而,API 中的关注点分离可以帮助我们做一些事情。首先,它可以帮助我们减少对某个端点的测试范围。我们不需要考虑该端点可能接受的所有输入和返回的所有输出,而是专注于特定的输入和特定的输出。
其次,拥有更小、更具体的端点有助于防止 API 滥用。由于我们可以明确知道每个请求是对哪个端点发出的,因此可以根据客户端对每个端点进行限流,从而保护我们的 API。对于 GraphQL 这样的灵活 API 端点,这一点内在地更加难以实现,因为我们需要思考是对整个路由、特定输入,还是单独的查询进行限流。
开发者工作流程
另一个通常被忽视的方面是开发者在编写 API 时的工作流程。使用 REST API 时,我们通常是在服务器和客户端之间独立工作,这个过程容易出错。如果没有明确的规范来说明期待的数据格式,我们就会进入长时间的调试环节。此外,即使我们有数据规范,开发者也是人,难免会犯错。客户端可能期望某种类型的数据,而服务器却发送了另一种。
这并非仅仅是 REST API 的问题,gRPC 和 GraphQL 也有这个问题。然而,问题的范围有所减少,因为我们可以确保只使用特定类型的请求和响应。这让我们能够专注于正常的开发路径,而无需写代码来检查序列化和反序列化是否失败。
gRPC 和 GraphQL 的开发 API 方式被称为 “架构驱动开发(Schema-Driven Development,SDD)”。我们首先专注于编写定义了 API 所有高层需求的架构,然后再进行实现。这大大减少了运行时可能遇到的错误,并且缩短了开发者的反馈循环。我们不能发送一个字符串代替整数,编译时就会提示我们错误。这还使得测试的范围更小,因为我们现在可以专注于特性本身,而不是外部问题导致的各种错误。
开箱即用功能的便利性
最后,另一个被忽视的话题是使用这些技术的便利性。这可能是由于社区开发了许多工具,或者是框架本身提供了许多开箱即用的功能。在这方面,使用 JSON
的技术通常会有更多的工具和支持。因为 JSON
已经被广泛使用了很长时间,并且由于其可读性,它成为了一种有吸引力的选择。
然而,即便与基于 JSON
的 API 相比,gRPC 由于其帮助 Google 扩展和保障其产品的设计原则,提供了许多无需额外依赖的强大功能。gRPC 内建了拦截器、TLS 身份验证等许多高级特性,因此它让编写安全且高效的代码变得更加简单。
最后,GraphQL 是三者中唯一明确指出端点可能会有副作用的技术。虽然我们可以为 gRPC 或 REST API 编写文档来说明这一点,但这无法静态检查。这一点很重要,因为它让 API 的用户更清楚地了解后台发生了什么,也可能帮助他们做出更合适的路由选择。