最近一段时间关于GraphQL的讨论很多,一些项目中也相继用到了这种风格,但使用是否合理,是否存在杀鸡用牛刀这样的问题,还有待商榷。
译自:Comparing API Architectural Styles: SOAP vs REST vs GraphQL vs RPC
两个不同的应用需要一个中间程序才能互通,开发者通常会使用应用程序接口(API)进行搭桥,使一个系统能够访问另一个系统的信息或功能。
为了在扩容时快速集成应用,实际的API会使用协议或规范来定义消息传递的语义和语法。这些规范构成了API架构。
过去几年曾出现了几种不同的API架构风格,每种风格都有其特定的标准数据交互模式。而对API架构的选择引起了无休止的讨论。
现在,很多API用户放弃REST,并拥抱GraphQL。而在十年之前,对于REST来说则是相反的情况,在于SOAP的竞争中,REST大获全胜。这种观念的问题在于用于单方面去选择一个技术,而没有考虑实际价值以及以与特定场景的匹配度。
本文将会按照API风格出现的顺序对它们进行讨论,对比各自的优劣势,并给出各自适合的场景。
Remote Procedure Call (RPC):唤醒另一个系统的功能
RPC是一个规范,它允许在一个不同的上下文中远程执行功能。RPC将本地程序调用扩展到了HTTP API的上下文中(RPC的最上层大部分都是HTTP)。
一开始的XML-RPC问题比较多,它很难保证XML载体的数据类型。后来出现了一个基于JSON-RPC的RPC API,由于JSON的规范更加具体,因此被认为是SOAP的替代品。 gRPC是一个谷歌在2015年开发的全新RPC版本,插件化支持负载均衡、跟踪、健康检查以及身份认证等,gRPC非常适用于微服务间的通信。
RPC如何工作
客户端唤醒远端程序,序列化参数,并在消息中添加额外的信息,然后将消息发送给服务端。在接收到客户端的消息后,服务端会反序列化消息中的内容,执行请求的操作,并将结果返回给客户端。服务端存根(stub)和客户端存根(stub)负责参数的序列化和反序列化。
RPC的优点
- 直接简单的交互方式:RPC使用GET获取信息,并使用POST处理其他功能。服务端和客户端的交互归结为对后端的调用,并获取响应结果。
- 方便添加功能:如果我们对API有新的需求,可以通过简单地添加新的后端来满足该需求:1)编写一个新的功能,然后发布;2)然后客户端就可以通过这个后端来满足需求。
- 高性能:轻量载体提升了网络传输的性能,这对于共享服务器以及在网络上进行并行计算的工作站来说非常重要。RCP可以优化网络层,使其可以每天在不同的服务间发送大量消息。
RPC的缺点
- 与底层系统的强耦合:API的抽象程度与其可复用性相关。与底层系统的耦合越高,API的可复用性就越低。RPC与底层系统的强耦合使其无法在系统和外部API之间进行抽象,同时也增加了安全风险,很容易在API中泄露底层系统的实现细节。RPC的强耦合使其很难实现需求扩展和团队解耦,客户要么会担心调用特定后端可能带来的副作用(如安全问题),要么会因为无法理解服务端的功能命名规则而不知道调用哪个后端。
这里说的"与底层系统"的耦合,并不是说与内核等底层实现之间的耦合,而是与底层服务的耦合,如与日志服务,鉴权服务等耦合。
- 可发现性低:RPC无法对API进行自省或无法通过发送的RPC请求来理解其调用的功能。
应该是RPC并没有像REST API那样相对严格的调用规范,因此有些调用会比较难以理解
- 功能爆炸:由于很容易添加新的功能,因此相比编辑现有的功能,新增的功能可能会导致大量功能重叠,也很难去理解。
RPC使用场景
RPC模式始于80年代,但它一直没有过时。像Google,Facebook (Apache Thrift)和Twitch(Twirp) 这样的大型公司利用RPC的高性能特性来获得高性能、低开销的消息处理能力(规模庞大的微服务使用短消息进行通信,需要保证通信的畅通)。
- 命令式API:RPC非常适合向远端系统发送命令。例如,Slack API就是重命令的接口:加入频道、离开频道、发送消息等。因此Slack API的设计者可以使用RPC风格的模型,使功能更简单、紧凑,也更方便使用。
- 用于内部微服务客户API:在整合单个供应商和用户时,我们不希望(像REST API那样)花费大量时间来传输元数据。凭借高消息速率和消息性能,gRPC和Twirp是微服务使用RPC的典范。gRPC背后使用的是HTTP 2,因此能够优化网络层,每天可以在不同的服务间传送大量消息。但如果不关心高性能网络,转而期望团队间能够使用稳定的API来发布不同的微服务,那么可以选择使用REST。
Simple Objects Access Protocol (SOAP): 让数据作为服务
SOAP是一种XML格式的,高度标准化的web通信协议。在XML-RPC面世一年之后,Microsoft发布了SOAP,SOAP继承了XML-RPC的很多特性。而后出现了REST,二者并驾齐驱,但很快REST就后来居上。
SOAP如何工作
XML数据格式多种多样,加上大量消息结构,使得SOAP称为一种最冗长的API样式。
一个SOAP消息包含:
- 每个消息的开始和结束都要包含一个信封标签
- 包含请求或响应的消息体
- 标头(如果消息必须确定某些具体要求或额外要求)
- 请求过程中的错误信息
SOAP API的逻辑是用Web服务描述语言(WSDL)编写的,该API描述语言定义了后端并描述了可执行的流程。它允许使用不同的编程语言和IDEs快速配置通信。
SOAP同时支持有状态和无状态消息。在有状态场景中,服务端会保存接收到的信息,该过程可能比较繁重,但对于涉及多方和复杂交易的操作来说是合理的。
SOAP的优点
- 语言和平台无关:支持创建基于Web的服务内置功能使SOAP能够处理独立于语言和平台的通信,并作出响应。
- 适用于各种传输协议:SOAP支持大量传输协议,可以用于多种场景。
- 内置错误处理:SOAP API规范可以返回Retry XML消息(携带错误码和错误解释)
- 大量安全扩展:集成了WS-Security,SOAP符合企业级事务质量。它为事务提供了隐私和完整性,并可以在消息层面进行加密
SOAP的缺点
如今,由于多种原因,很多开发人员对必须集成SOAP API的想法感到不安。
- 仅支持XML:SOAP消息包含大量元数据,且请求和响应仅支持使用冗长的XML结构。
- 厚重:由于XML文件的大小,SOAP服务需要比较大的带宽。
- 狭窄的专业知识:构建SOAP API需要深刻理解各种协议,以及严格的协议规则。
- 乏味的消息更新:在添加和移除消息属性时需要额外的工作量,这导致SOAP的采用率下降。
SOAP的使用场景
目前,SOAP架构大部分用于内部集成企业或其他可信任的伙伴。
- 高度安全的数据传输:SOAP的刚性结构、安全和授权能力使其特别适用于在遵守API提供者和API使用者之间的契约的同时,在API和客户端之间履行正式的软件契约。这也是为什么金融机构和其他企业用户选择SOAP的原因。
Representational state transfer (REST): 将数据作为资源
REST是一个自解释的、由一组架构约束定义的API架构风格,并被很多API使用者广泛采用。
作为当今最通用的API风格,它最初出现在2000年的Roy Fielding 的博士论文中。REST使用简单格式(通常是JSON和XML)来表达服务侧的数据。
REST如何工作
REST没有像SOAP那样严格。RESTful架构应该遵循以下六个架构约束:
- 统一接口:为一个给定的服务(无论是设备还是应用类型)提供统一的接口。
- 无状态:处理请求本身所包含的请求状态,而服务器不会存储与会话相关的任何内容
- 缓存
- 客户端-服务端架构:允许两端独立演进
- 应用系统分层
- 服务端可以给客户端提供可执行的代码
实际上,某些服务仅在一定程度上是RESTful的,而核心使用了RPC风格,将大型服务分割成多个资源,并有效地利用HTTP基础设施。但关键部分使用的是超媒体(又称HATEOAS,Hypertext As The Engine of Application State),意味着对于每个响应,REST API提供了如何使用API的所有元数据信息。REST使用这种方式来解耦客户端和服务端,这样,API提供者和消费者就可以独立演进,且不会妨碍它们的通信。
Richardson Maturity Model as a goalpost to achieving truly complete and useful APIs, Source: Kristopher Sandoval
"HATEOAS 是REST的关键特性,这也是REST之所以称为REST的原因。但由于很多人并不使用HATEOAS,导致他们实际上用的是HTTP RPC",这是Reddit上的一些激进意见。确实,HATEOAS是最成熟的REST版本,但很难实现比通常使用和构建的API客户端更加高级和智能的API客户端。因此,即使是如今非常好的REST API也不能保证面面俱到。这也是为什么HATEOAS主要作为RESTful API设计的长期开发愿景。
REST和RPC之间有一些灰色区域,特别是当一个服务具有一部分REST特性,一部分RPC特性时。REST基于资源,而不是基于动作或动词。
在REST中,会用到像GET, POST, PUT, DELETE, OPTIONS, PATCH这样的HTTP方法。
REST的优点
- 解耦客户端和服务端:REST的抽象比RPC更好,可以更好地解耦客户端和服务端。具有一定抽象的系统可以更好地封装其细节并维持其属性。这使得REST API足够灵活,可以在保持系统稳定的同时,随时间进行演化。
- 可发现性:客户端和服务端的通信描述了所有细节,因此无需额外的文档来理解如何使用REST API进行交互。
- 缓存友好:重用了大量HTTP工具,REST是唯一一种允许在HTTP层缓存数据的风格。相比之下,要在其他API风格中实现缓存,则要求配置额外的缓存模块。
- 支持多种格式:支持多种格式的数据存储和交互功能也是使REST成为当前流行的构建公共APIs的原因之一。
REST的缺点
- 没有单一的REST结构:不存在正确地构建REST API的方式。如何对资源进行建模,以及对哪些资源建模取决于具体场景,这使得REST在理论上是简单的,但实践上是困难的。
- 载荷较大:REST会返回大量元数据,因此客户端可以从响应的信息中了解到应用的状态。对于具有大容量带宽的大型网络通道来说,这种交互方式没有问题。但实际情况并不总是这样,这也是Facebook在2012年推出GraphQL风格的主要驱动因素。
- 过度获取和不足获取问题:由于有时候会出现包含的数据过多或过少的情况,导致在接收到REST的响应之后,通常还会需要另一个请求。
REST的使用场景
- 管理API:专注于管理系统中的对象,并面向多个消费者是最常见的API风格。REST可以帮助这类APIs实现强大的发现能力,良好的文档记录,并符合对象模型。
- 简单资源驱动的APPs:REST是一种非常有用的方法,可用于连接不需要灵活查询的资源驱动型应用。
GraphQL:仅请求需要的数据
它需要多次调用REST API才能返回所需的内容。 因此,GraphQL被认为是一种改变API规则的风格。
GraphQL 的语法描述了如何发起精确的数据请求。GraphQL适合那些相互之间具有复杂实体引用关系的应用数据模型。
现在,GraphQL生态扩展了相关的库,并出现了很多强大的工具,如Apollo, GraphiQL, and GraphQL Explorer。
GraphQL如何工作
一开始,GraphQL会创建一个schema(模式),它描述了在一个GraphQL API中的所有请求以及这些请求返回的所有types。构建模式会比较困难,它需要使用模式定义语言(DSL)进行强类型输入。
由于在请求前已经构建好了模式,因此客户端可以对请求进行校验,确保服务器能够进行响应。在到达后端应用后,会有一个GraphQL操作,负责使用前端应用的数据来解析整个模式。在给服务端发送包含大量查询的请求之后,API会返回一个JSON响应,内容正对应请求的资源。
除RESTful CRUD操作外,GraphQL还有订阅功能,允许接收服务端的实时通知。
GraphQL 的优点
- 类型化的模式:GraphQL 会提前发布它可以做的事情,这种方式提升了可发现性。通过将客户端指向GraphQL API,我们可以知道哪些查询是可用的。
- 非常适合类似图形的数据:适合深度关联的数据,不适合扁平数据。
- 没有版本控制:最好的版本控制就是不对API进行版本控制。
REST提供了多种API版本,而GraphQL是一种单一的、演化的版本,可以持续访问新的特性,方便服务端代码的维护。 - 详细的错误消息:与SOAP类似,GraphQL提供了详细的错误信息,错误信息包括所有的解析器以及特定的查询错误。
- 灵活的权限:GraphQL允许在暴露特定的功能的同时保留隐私信息。而REST架构不能部分展示数据(要么全部显示,要么全部隐藏)。
GraphQL 的缺点
- 性能问题:GraphQL用复杂度换来功能上的提升。在一个请求中包含太多封装的字段可能会导致系统过载。因此,即时对于复杂的查询,REST仍然是一个比较好的选择。
- 缓存复杂:GraphQL 没有使用HTTP缓存语义,需要客户自定义。
- 需要大量开发前培训:由于没有足够的时间弄清楚GraphQL 的基本操作和SDL,很多项目决定沿用REST方式。
GraphQL的使用场景
- 手机端API:这种情况下,对网络性能和单个消息载体的优化非常重要。因此GraphQL为移动设备提供了一种更有效的数据载体。
- 复杂系统和微服务:GraphQL能够将复杂的系统集成隐藏在API背后。从不同的地方聚合数据,并将它们合并成一个全局模式。这对于扩展遗留基础设施或第三方API尤为重要。
如何选择API模式
每种API项目都有不同的要求,通常基于如下几点进行选择:
- 使用的编程语言
- 开发环境,以及
- 涉及的人力和财务资源等
在了解到每种API设计风格之后,API设计者就可以根据项目的需要选择最合适的API模式。
由于强耦合特性,RPC通常用于内部微服务间的通信,不适用于外部API或API服务。
SOAP比较麻烦,但它本身丰富的安全特性仍然是交易操作、订单系统和支付等场景的不二之选。
REST具有高度抽象以及最佳的API模型。但往往会增加线路和聊天的负担--如果使用的是移动设备,这是不利的一面。
在获取数据方面,GraphQL迈出了一大步,但并不是所有人都有足够的时间和精力来处理这种模式。
归根结底,最好在一些小场景下尝试每种API风格,然后看是否满足需求,是否能够解决问题。如果可以,则可以尝试扩展到更多的场景。