六种API风格对比:REST、GraphQL、WebSockets、Webhooks、gRPC、SOAP
了解 REST、GraphQL、WebSockets、webhooks、gRPC 和 SOAP 如何分别解决不同的数据交换问题,以及用于选择合适技术的决策指南。
大多数人首先接触的是REST这种API风格,并将其视为通用解决方案。但实际上并非如此。REST只是六种选项中的一种,其余五种存在的理由正是REST在某些场景下会遇到局限——如实时更新、快速的内部服务间调用、严格的企业安全要求以及灵活的数据结构。列表中的其他每种API风格都是为了解决REST难以处理的难题而设计的。
如果您已经熟悉REST,接下来的内容将明确指出何时以及为何需要更换工具。
什么是API(一段文字后即可继续)
从根本上说,API位于两个系统之间,使它们能够相互通信。当你在外卖应用中输入“印度香饭”时,相关结果并不会已经存储在你的手机上。你的应用会向该公司的服务器发送请求,服务器则会返回匹配的数据。规范这种数据交换的规则——包括请求的构成方式以及响应的内容形式——就是API。想象一下餐厅里的服务员:你不会亲自走进厨房去取食物,只需告诉服务员你的需求,剩下的就由服务员处理。那个服务员本质上就是API。
实际上,你会遇到六种不同类型的“服务员”。
REST:标准及其局限性
REST即表述性状态转移,它运行在HTTP之上,依赖于两个核心理念:用于标识所需资源的URL,以及用于描述要对该资源执行的操作的HTTP方法。
四种方法几乎可以涵盖所有操作:GET用于获取数据,POST用于创建新记录,PUT用于更新或替换现有记录,而DELETE则用于删除记录。REST的一个关键特性是无状态性——服务器不会存储与用户之前的交互历史。每次请求都必须包含所有必要的上下文信息。
GET https://api.zomato.com/v1/restaurants?search=biryani
Authorization: Bearer <token>
一旦该请求到达服务器,服务器会验证用户的身份,从数据库中提取相关记录,然后返回JSON格式的数据。
适用场景:面向公众的 API、标准的创建-读取-更新-删除类应用,以及客户端与服务器完全分离且需要可预测、有完善文档说明的接口协议的任何场景。REST 能成为默认标准是有充分理由的——它简单易用,无需服务器维护会话状态,被广泛理解,且基于普通的 HTTP 协议。
不足之处:任何需要实时更新的场景(如聊天应用、实时位置追踪),那些单个界面就需要同时从多个不同资源获取数据的情形,或是内部服务通信中速度优先于可读性的情况。
GraphQL:按需获取所需数据
REST存在一个众所周知的问题:过度获取数据。当你调用/user接口时,可能会收到姓名、头像、年龄、部门、薪资以及更多字段的信息——而实际上你只想要姓名和头像而已。另一个问题是数据获取不足,即某个视图需要来自多个资源的字段,这就迫使你必须发起多次REST请求,然后在客户端将结果拼接起来。
GraphQL能够同时解决这两个问题:通过一个接口加上查询语言,让客户端能够精确指定所需返回的字段。
# Instead of hitting /employees/123 and getting everything,
# you describe precisely what you need in the request body
query {
employee(id: "123") {
name
photo
}
}
响应中只包含那两个请求的字段——没有其他多余内容。如果还需要薪资信息,只需在查询中添加即可,无需为此创建单独的接口。
GraphQL支持三种操作类型。查询用于读取数据,功能与REST的GET请求相同。变异用于写入或修改数据,相当于POST、PUT和DELETE请求的合集。而订阅则能建立实时数据流以实现持续更新,其作用类似于WebSockets。
适用场景:需要灵活数据结构的功能丰富的前端应用、注重减少数据传输量的移动端应用,以及各种情况下——无论是网页、移动设备还是第三方集成——多个客户端类型访问同一后端但各自需要不同数据片段的情形。
其不足之处:在那些REST简单直接的接口已能很好地完成任务的场景中,GraphQL反而增加了复杂性。它在服务器端带来了真正的复杂度,缓存处理也比REST更为棘手;而当数据需求稳定且明确时,使用GraphQL往往属于不必要的额外开销。
WebSockets:持久连接
实时功能暴露了REST的一个根本缺陷。要判断是否有新的聊天消息到达,基于REST的客户端必须不断轮询:“有新内容吗?”“有新内容吗?”——如此反复。如果同时有百万用户使用,那么每秒就会产生百万次请求,其中绝大多数都会得到“没有”的回复,完全是浪费资源。
WebSockets通过采用持久的双向连接来替代请求-响应模式,从而完全避免了这一问题。它最初是一个普通的HTTP请求,但带有特殊的升级标头:
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
一旦服务器接受,该HTTP连接就会转变为WebSocket连接。从那时起,任意一方都可以在任何时刻向另一方发送消息,无需事先请求许可。该通道会一直保持开放状态,直到其中一方主动关闭它为止。
WebSocket连接会经历四种不同的状态:Connecting(正在建立握手连接)、Open(数据可双向传输)、Closing(开始关闭连接)以及Closed(连接已终止)。试图在已经关闭的连接上发送数据会导致服务器崩溃——这是初学者常犯的错误。
应用场景:实时聊天、多人游戏、类似共享文档的实时协作编辑工具、实时体育比分显示以及推送通知——基本上凡是服务器需要主动发送数据的情况都适用。
其不足之处:适用于普通数据检索场景,因为客户端仅在明确请求时才需要信息。由于WebSocket会持续保持连接状态,因此会占用服务器资源。在仅需使用普通REST即可完成工作的场景中使用WebSocket,只会无端消耗资源。
Webhooks:服务器主动联系你
REST和WebSocket都是以客户端为起点——客户端建立连接、发送请求,然后服务器予以响应。而Webhooks则完全颠倒了这一流程:不是你向服务器请求更新,而是在有值得知晓的事件发生时,服务器会主动联系你。
其原理非常简单。你只需在某个第三方服务上注册一个URL,并指定对该URL的处理方式:例如“当支付完成时,向此处发送POST请求”。一旦支付真正成功,支付提供商——无论是Razorpay、Stripe还是其他你使用的服务——就会自动向你的接口发送请求。无需轮询,也无需持续维护连接,只需坐等调用到来即可。
# What you give Razorpay in setup:
Webhook URL: https://yourapp.com/webhooks/payment
# What Razorpay sends when payment completes:
POST https://yourapp.com/webhooks/payment
{
"event": "payment.captured",
"payload": { "amount": 50000, "order_id": "order_abc" },
"signature": "sha256_hash_here"
}
在这里,签名验证并非可选功能——它是整个安全体系的基石。您的 webhook 端点是对公开放的,这意味着理论上任何人都可以发送伪造的“payment.captured”事件,诱使您的系统释放那些实际上并未完成支付的订单。消息体中包含的签名是一种加密哈希值,可用于证明该请求确实来自服务提供商。在处理请求体中的任何内容之前,您的服务器必须先验证该签名。
适用场景:支付确认、订单状态变更、CI/CD 流水线(每当有新代码提交时 GitHub 会通知您的服务器),以及任何需要对外部系统中发生的事件作出响应的工作流程。
何时不宜使用它:在用户当前交互过程中需要即时回复的场景。Webhooks 是事后处理的——本质上属于异步操作。当用户正坐在屏幕前等待确认时,REST 仍是更合适的选择。
gRPC:内部服务的二进制高速传输
大型应用很少由单一服务器构成。例如 Zomato 这样的平台就需要为订单、支付、通知以及餐厅数据等运行独立的服务,而这些服务之间每秒会相互调用数千次。如果所有这些内部通信都通过 REST 完成,就不得不不断对 JSON 进行序列化与反序列化操作。JSON 对于查看日志的开发者来说可读性很高,但当数据量增大时,其可读性所带来的解析成本也会十分显著。
gRPC最初由谷歌开发,用于处理其内部流量,它用Protocol Buffers(Protobuf)替代了JSON——这是一种二进制格式,体积更小,编码和解码速度也更快。REST会以可读文本形式传输的相同数据,gRPC则将其作为密集的二进制数据块发送,机器处理起来速度要快得多。
// You define your data structure once in a .proto file
message OrderRequest {
string order_id = 1;
string user_id = 2;
float amount = 3;
}
性能提升并不仅取决于数据格式。gRPC 还运行在 HTTP/2 之上,后者支持多路复用——数千个请求可同时通过同一个连接传输,而 HTTP/1.1 则需逐个处理这些请求。此外,gRPC 还提供了四种不同的通信模式:单向模式(单个请求对应单个响应,结构与 REST 相同)、服务器端流式传输(一个请求触发一系列响应,适用于实时订单跟踪等场景)、客户端流式传输(多个请求汇总为最终响应,适合分块上传文件),以及 双向流式传输(双方持续互相发送数据,适用于实时协作功能)。
适用场景:在自身基础设施内的服务间通信,此时速度和严格的类型检查非常重要。那些需要服务之间交换大量请求且JSON解析已构成显著成本的情况。
不推荐使用的场景:被浏览器或外部开发者调用的公开API。Protobuf的二进制特性使得其难以检查和调试,要在浏览器中实现它还需要额外的配置。对于任何面向用户的场景,REST仍然是更实用的选择。
SOAP:严格、冗长,却仍在为银行提供支持
SOAP(简单对象访问协议)诞生于1998年,其历史比REST还要悠久。如今,大多数开发者只有在连接银行系统、保险平台或大型企业软件时才会遇到它——这些行业很早就采用了SOAP,且没有强烈的理由去放弃它。
SOAP的结构十分严格。每条消息都是封装在严格定义的框架中的XML格式。而REST在数据结构设计上留有较大灵活性,SOAP则要求双方都必须遵循精确预定义的架构。
<!-- Every SOAP message follows this envelope structure -->
<Envelope>
<Header>
<Security><!-- authentication goes here --></Security>
</Header>
<Body>
<GetAccountBalance>
<AccountId>ACC123</AccountId>
</GetAccountBalance>
</Body>
</Envelope>
这种复杂的结构是刻意设计的。SOAP的WS-Security标准将身份验证、数字签名和加密功能整合到同一条消息中。在金融交易领域,数据在传输过程中一旦被篡改就会造成严重后果,因此这种内置的保护机制足以弥补其额外的体积开销。
适用场景:连接银行的API、要求使用SOAP的支付网关、政府系统、保险平台,或是任何仅提供SOAP接口的旧版企业系统。对于从零开始构建的系统,你不太可能选择SOAP,但当需要与基于该协议的系统进行交互时,了解它就非常重要。
不推荐使用的场景:任何由你掌控通信双方的新项目。SOAP的实现耗时较长,其XML格式的数据载荷使得调试变得繁琐;在无需兼容旧系统的情况下,它相比REST或gRPC并无优势。
决策指南
可将其作为选择合适工具的快速参考:
标准的网页应用或面向公众的 API 会使用 REST。需要灵活、可按需定制数据结构的移动应用则会选择 GraphQL。实时聊天、多人互动或即时推送通知则适合使用 WebSockets。支付确认及 CI/CD 触发流程需要 Webhooks。追求高性能的内部微服务则适用 gRPC。而银行系统及传统企业集成则依赖 SOAP。
你现在已了解的内容
REST仍然是默认选择。其他各种架构模式都是为了解决REST无法满足的特定需求:当不同客户端需要的数据各不同时,就使用GraphQL;当需要保持双向持续连接时,就使用WebSockets;当需要对事件做出响应而非频繁查询时,就使用Webhooks;当JSON在内部服务通信中速度过慢时,就使用gRPC;而当企业级安全要求使得其他方案不可行时,则使用SOAP。
下次在设计集成系统时,不要首先考虑如何强行使用REST,而应先判断哪种通信模式真正符合系统的需求。应当由需求决定所使用的工具,而非习惯。
下一步,挑选一个你尚未使用过的模式。查找其官方文档或基于该模式开发的小型开源项目,在迫于压力需要自行实现之前,先仔细阅读实际的实现代码。