假设你要为一个新的 Web 服务设计 API。
你会从哪里开始呢?
高层级别上,你会考虑哪种通用的 API 风格(REST、RPC、graphQL 等)最适合你的应用程序的业务域。你需要确定一个传输协议(可能是 HTTP,但你可能会考虑使用 WebSocket,或者同时使用两者)。你还需要在一系列通用序列化标准(这里首选的是 JSON 和 XML)之间做出选择,或者你可以选择使用特定的纯文本消息协议(SOAP、OData、GraphQL)或二进制消息编码(Thrift、Avro、Protocol Buffers)。
在你开始考虑对应用程序的业务域建模之前,就得把所有这些事情都落实下来。
但是你首先想到的肯定是:“好吧,我们上 REST!”
REST 肯定是目前 Web 服务 API 设计领域最酷的东西吧?近年来,每当有组织希望发布新的公共 Web 服务时,他们通常都将 REST 作为其 API 的首选架构风格,不会考虑诸如 SOAP 或各种 RPC 约定之类的替代方法。
那么,REST 是什么?
这个问题值得一问。因为尽管 REST 备受推崇,它遭遇的误解和歪曲还是非常普遍的。
1REST 是什么?
REST 是 REpresentational State Transfer(表征状态转移)的简写。实现 REST 架构风格的系统被称为“RESTful”。
与 SOAP、GraphQL、OData 和其他多数 Web 服务 API 解决方案不同,REST 不是一个协议,甚至不是任何类型的标准。相反,REST 是一种架构风格,或者说是一组崇高的设计约束。
REST 是计算机科学家 Roy Fielding 在他的博士学位论文中命名的,这篇论文发表于本世纪初。但这篇论文没有吸引很多人研究,这是可以理解的,因为它的学术性很强。它的目标受众是学术界而不是商业界。不管怎样,在这篇论文发布后不久,术语“REST”和“RESTful”就成为了 IT 行业的流行语。
接下来发生的事情很不幸,那就是 REST 偏离了最初的概念。我们行业对流行语的偏爱,再加上人们对 Fielding 的原始研究普遍一无所知,导致 REST 的定义随着时间的流逝变得越来越模糊和失真。
在为这篇文章做准备的过程中,我回头探索了这个主题的权威源头,并研究了 Fielding 的原始论文以及关于 REST 的后续著作。
2REST 约束
在他的论文中,Fielding 为 RESTful 系统定义了六个约束。我会用自己的话来总结这些约束。阅读下面的介绍时,请试着想一想在现实世界中满足所有约束条件的应用程序示例是什么样的。
客户端 – 服务器模型
RESTful 系统的基础架构是客户端 – 服务器模型。客户端在与服务器不同的进程中运行,并且必须有足够的逻辑分离,让客户端与业务逻辑和数据存储无关,并保证服务器与用户界面或用户状态无关。
无状态
客户端和服务器之间的所有交互都必须是无状态的。这意味着服务器处理客户端请求所必需的所有应用程序状态(即会话状态或外部状态)都包含在请求本身内。因此,服务器永远不需要在单个请求 – 响应生命周期内保持任何客户端的状态。这样服务器只需负责持久化资源状态(又称内部状态),这些资源需要在所有客户端之间同步。
无状态系统具有极强的可扩展能力。这是因为服务器要存储的数据更少,它们可以在满足客户端请求后更快地释放资源,并且它们有更大的空间来并行处理请求。
此外,监视也更容易了(因为你不需要重现客户端状态的请求时间顺序),可靠性也随之提升(因为当系统为无状态时,有更多方法可以设计出从故障中恢复的系统)。
客户端应用程序可以设计为对状态转换优化(例如通过预期将来的用户交互并相应地预取资源来优化),并且可以公开控件以供用户直接操作其状态(典型的示例是 Web 浏览器中无处不在的“后退按钮”,在先前的状态转换上执行某种“撤消”操作)。
缓存能力
无状态客户端 – 服务器架构的另一个优点是响应消息变得更加通用,因此这些消息中有更大比例可以在多个客户端之间共享,增加了服务端缓存响应的可能性。可以引入专业的中间服务器来处理响应缓存,从而让这一跨域问题与终端服务器上的业务逻辑分离开。
此外,REST 风格鼓励服务器向客户端发出指示,告知后者是否可以在本地缓存资源。客户端被鼓励在可行的情况下存储和重用可缓存的响应,并避免使用过时和无效的数据。服务器资源的客户端缓存减少了客户端 – 服务器流量,从而进一步提高了服务器的可扩展性和客户端的性能。
统一的接口
REST 架构风格要求客户端和服务器通过一致的接口通信,该接口应尽可能通用。实现“统一接口”的先决条件是以下设计约束:
基于资源的 API:REST 中信息的主要抽象是资源(resource)。可以命名的任何信息都可以是资源。资源可能是虚拟对象(例如数字文档或图像),或者是表征现实世界中事物(例如人或地点)属性的数据结构。无论其性质如何,都必须使用一套通用的唯一标识符系统来识别所有资源。
通过表征来操作资源:客户端不需要直接访问原生格式的资源就可以对其进行更改。REST 允许客户端使用资源的部分表征(representation)来创建、操作甚至删除资源。例如,在服务端数据库中持久化的一个数据实体将以和原始格式不同的格式编码在客户端 – 服务器消息中。
自描述消息:请求和响应消息必须包含对其解码所需的所有信息。当然,一个负载的媒体类型就能确定要调用的解析器。但是,在服务器对客户端的响应消息的上下文中,所选的媒体类型还必须提供足够的元数据,以充分描述所有编码的资源表征的结构和语义。客户端应该只需要了解媒体类型(而不是编码资源的性质),就可以按照服务器的意图收集处理服务器提供的数据所需的所有信息。
作为应用程序状态引擎的超媒体(HATEOAS):服务器的响应媒体类型不仅应完全自描述其编码的数据,而且还应嵌入指令以使客户端对该数据及其相关数据执行进一步的操作。这种元数据类别称为控制数据。这是 REST 的关键约束。RESTful 服务器必须响应客户端请求,并更新该客户端的状态,以及该客户端现在可使用的其他所有操作的详细信息,并赋予该客户端新的状态和访问权限。为此,RESTful 服务器以超媒体(hypermedia)格式编码其响应消息——超媒体格式是一种包含超媒体控件(如链接和表单)的媒体类型。客户从给定的可用超媒体控件列表中选择下一步操作。因此,尽管客户端保留了自己的应用程序状态,但由服务器来将所有更改推到该下游状态。由于客户端应用程序不需要服务器上可用资源和操作的硬编码知识,因此这种设计具有进一步让客户端与服务器解耦的效果。
分层系统
REST 的客户端 – 服务器模型具有无状态性质,因此可以在水平和垂直方向上扩展服务端基础架构。但是,要维护这么复杂的多层服务端系统,更换或添加新层就应该无需更新最终服务器或客户端的代码或配置。随之而来的是,客户端必须不知道自己是直接连接到终端服务器还是连到中间服务器上。
按需编码
REST 架构风格的最后一条约束是可选的。服务器可以在运行时将附加程序代码传输给客户端来动态扩展或自定义客户端的功能,然后客户端可以执行这些代码。
这里的想法是,如果新功能可以在部署到客户端后再下载到客户端,那么整个系统就可以更快扩展了。
3REST 其实就是为分布式信息系统打造的
你能想到一个适合所有这些约束(包括按需编码概念)的软件应用程序示例吗?
确实存在这样的系统,而且你肯定听说过它。
它被称为万维网。
今天,我们倾向于将 Web 视为一个应用程序平台。不过当然,万维网本身就是一个应用程序——一个分布式应用程序。Fielding 在其论文中描述的就是已经应用在这个应用程序架构设计上的那些约束。
Fielding 的论文是对让万维网——一种分布式超媒体驱动的信息检索系统——取得了可喜成功的那些设计决策的回顾总结。
实际上,正确来说,REST 约束是 Fielding 的最终论文发表之前的几年中就制定好的。在此期间,Fielding 参与了核心 Web 标准(特别是 HTTP/1.0 和 HTTP/1.1)以及统一资源标识符(URI)标准的规范制定,并为 Apache HTTP Server 软件的建立做出了贡献。REST 原则与这些项目并行迭代、微调,其宗旨也很明确,就是为万维网生态系统整体架构的“设计、定义和部署”提供一个框架。REST 约束被认为是“满足互联网规模的分布式超媒体系统的需求”所必需的。
开发 REST 的动机是为 Web 的工作机制创建一个架构模型,以将其用作 Web 协议标准的指导框架。这项工作是互联网工程任务组(IETF)和万维网联盟(W3C)的一部分,旨在为 Web 定义 HTTP、URI 和 HTML 的架构标准。——Roy Fielding
REST 确实影响了 Web 平台的某些特性。例如,REST 对资源缓存能力的约束直接导致在 HTTP/1.1 中添加了 Cache-Control、Age、Etag 和 Vary 标头字段。REST 甚至影响了 URI 标准中“资源”一词的使用。
我们了解了这些背景后再来审视那些 REST 约束,现在一切好像都显而易见了:
Web 是用于分布式应用程序的客户端 – 服务器模型的实现。
Web 的主要传输协议 HTTP 本质上是无状态的。
HTTP 提供了描述服务器资源的缓存能力的语义,而在客户端保持应用程序状态是 Web 浏览器的固有工作方式。
URI 标准直接对应具有唯一标识符的 REST 资源概念。资源由媒体类型表征,这些媒体类型使用 HTTP 的 Content-Type 标头声明,从而使 HTTP 消息具有自描述性。超文本标记语言(HTML)是专门为 Web 设计的超媒体类型。HTML 具有用于驱动应用程序状态更改的嵌入式超媒体控件(链接和表单),可以满足 REST 统一界面的其他所有要求。
服务端 Web 基础架构的分层——由面向服务的“微服务”架构支持的 API 网关、内容交付网络和其他负载均衡解决方案,等等——如今都是非常标准的东西。
可选的按需编码约束描述了我们今天所谓的渐进增强功能:使用在客户端设备上下载并执行的 JavaScript 程序对静态 HTML 文档进行任意扩展。
甚至连 REST 这个古怪的名称——表征状态转移——也能看出含义了。正如 Fielding 所描述的那样:
Fielding 撰写论文的意图是将万维网分解为“一组核心的原理、属性和约束”,以触及“其作为基于网络的应用程序的本质”。这里的想法是那些约束可以在今后应用于其他基于网络的软件应用程序。用他的话来说:
万维网可以说是世界上最大的分布式应用程序。了解 Web 基础的关键架构原理可以帮助理解其技术层面的成功原因,并可能推动其他分布式应用程序的改进,尤其是那些适用于相同或相似交互方法的应用程序。
Fielding 的态度很明确:“REST 专门针对分布式信息系统设计”。如果你自己去看 Fielding 的论文,会发现它完全说的是网页、Web 浏览器和 Web 服务器的机制——也就是万维网的基础架构,而万维网就是分布式信息系统的一个特定实现。
4REST!=Web 服务 API
事情是这样的:就算某个应用程序或服务是通过 RESTful 的分布式信息系统(例如万维网)交付的,也并不意味着这个应用程序就能自动变成 RESTful 的。
万维网是运行其他应用和服务的网络系统,这个平台是 RESTful 的。但是,通过 Web 交付的大多数站点、应用和服务本身都不是 RESTful 的。
REST 并不是要覆盖 Web 协议标准的所有可能用法。有些 HTTP 应用程序和 URI 与分布式超媒体系统的应用程序模型并不匹配。——Roy Fielding
REST 约束特别适合在 Web 上运作的数字服务使用的编程接口上下文。尽管 Fielding 承认 REST 约束可以用在 Web 服务上(“某些媒体类型适用于自动化处理”),但这并不是他的论文的核心内容。
然而,自 Fielding 论文发表以来的 20 多年里,越来越多的 Web 服务 API 被文档描述为“REST API”。例子包括微软的一些 API、谷歌的一些 API,以及 GitHub、Atlassian、PayPal、Stripe、Twilio、WordPress 和数百种应用程序的主要编程接口。
所有这些都不是 RESTful 的。它们都没有使用超媒体作为驱动应用程序状态的手段。当然,它们都没有将可执行代码发送给客户端。而且无论如何,它们只是 API,而不是分布式信息系统。
就像许多流行词一样,REST 的定义随着时间的流逝越来越被扭曲了。
REST 架构的错误描述业已广泛传播,以至于“REST”私底下被人理解为“HTTP”的同义词。更确切地说,“RESTful”被人用来描述任何使用 HTTP 作为应用程序协议的应用程序,也就是充分利用 HTTP 的原生特性和消息传递语义,而不仅仅是将其用作传输协议的应用。
甚至 Wikipedia 条目也将表征状态转移定义为一种使用一个“HTTP 子集”并展示一个“预定义操作集”的 API 风格。两种说法都不准确。REST 与传输协议无关(实际上它并不限于 HTTP),而且真正的超媒体驱动的服务不需要预定义其操作(因为它们可以在运行时动态发现)。
“这是一个 HTTP API,不是 REST。这里没有超媒体。”——Hadi Hariri),《银弹症候群》
仔细检查会发现,大多数所谓的 REST API 仅仅是 HTTP API。它们只是遵循了 HTTP 最佳实践和约定。例如,建议使用 HTTP 动词(例如 GET、PUT 和 POST)来提示对资源执行的操作类型的是 HTTP 规范,而不是 REST 约束。使用这些方法的不是 RESTful。这只是 HTTP 语义的好用法。
甚至 Richardson 成熟度模型(一个非正式的但被广泛引用的 RESTful API 分类系统)也将 REST 的原则与 HTTP 的语义混为一谈。
在 2008 年的一篇博文中,Fielding 谴责了在其技术文档中使用术语“REST API”作为“HTTP API”的别名的组织。他澄清说:
……如果应用程序状态的引擎(以及 API)不是由超文本驱动的,则它不能是 RESTful 的,也不能是 REST API。
5你好,hypermedia API
要让一个 Web 服务接近真正的 RESTful,它对客户端的响应消息的负载就不能由任何旧的任意数据对象组成。负载必须类似于 HTML:一种超媒体格式,定义客户端应如何处理编码的表征,并带有客户端执行进一步操作所需的所有超媒体控件。但是,当然,尽管 HTML 主要是供人类消费的文档格式,但超媒体 API 仍需要等效的媒体类型,以供其他计算机程序自动消费。
在过去十年左右的时间里,业内进行了一些勇敢的尝试,试图开发这种媒体类型。该领域最有希望的候选人是 JSON-LD 和 Hydra。我将在以后的博文中详细介绍它们,和其他针对超媒体 API 的新兴解决方案。
在本博文中,我会只概述一个理论上的超媒体 API 的工作机制。我之所以说是理论上的,是因为截至 2021 年,超媒体 API 尚未走出学术界,成为现实成果。
未来的超媒体 API 将由自动化代理消费,其消费方式与当今人类与网站交互的方式完全相同。人们可以按照网站本身提供的说明,访问网站的主页并单击链接和填写表格来发现网站的所有可用资源和功能。于是,自动化代理也可以访问 Web 服务 API 的根 URL,并遵循服务器响应消息中描述的链接和控件,来发现 Web 服务的所有可用资源和操作并与之交互。
这一原则被称为“跟着感觉走”。只要提供与站点或服务“带内(in-band)”交互(也就是编码在服务器的响应消息中)所需的所有文档,就可以实现它。
一个 REST API 接入时,除了初始 URI(书签)和适用于目标受众的标准媒体类型集之外,应该没有其他任何前置知识……从这时起,所有应用程序状态转换都必须由客户端在收到的表征中展示的,服务器提供的选项中做出的选择来驱动……——Roy Fielding
此类 API 的实现可能会对我们对客户端应用程序编程的方式产生巨大影响。客户端应用程序无需使用与之交互的服务端 API 的任何特定领域知识来做硬编码。相反,它们将在运行时动态发现所有可用资源和操作。为一个超媒体 API 开发的客户端应用程序可以很容易地分叉和修改为另一个由超媒体驱动的 Web 服务。能够使用通用语法消费任何超媒体 API 的“智能客户端”可能成为现实。
由于客户端应用程序不会被写入静态接口,因此 API 本身可以自由地动态更改形态,以响应用户输入、算法甚至是人工智能。而且,设计渐进式发展的 API(与重大更改和版本控制相关的问题更少)会容易很多。
看起来激动人心。但就目前而言,超媒体 API 的开发仍然局限在一个小众学术领域,并且要将这些解决方案带入主流还有很多工作要做。(我将在另一篇博客文章中再来研究这一主题。)
6“HTTP API”和“hypermedia API”
我们赶快让“REST API”这个术语走入历史吧。REST 的含义实在是太混乱了,想纠正它根本没戏。
尽管 REST 可以很好地表达万维网的基本架构原理,但是它不能很好地适用恰好通过这个网络交付的各个服务的设计理念。
我们不会再说什么 RESTful 网站了,对吗?那为什么我们要谈论 RESTful API 呢?
我们今天所说的“REST API”应该重新分类为“HTTP API”或“hypermedia API”。
HTTP API 是围绕 HTTP 设计的。这些 Web 服务紧密遵循 HTTP 规范中列出的 HTTP 请求和响应消息组成的最佳实践。它们充分利用了 HTTP 的原生特性来进行缓存控制、内容协商和身份验证。它们的端点比 RPC 风格更面向资源。至于媒体类型,它们更偏好简单的、通用的纯文本编码格式,例如 JSON 和 XML。它们将被版本化,并且它们的静态接口可能会在 OpenAPI 或其他流行的 IDL 约定中记录。更一般而言,HTTP API 并不适合轻量级解决方案、最新的代码生成和其他自动化工具链。因此,它们是交付旨在集成到第三方应用程序中的商业服务的理想选择。
重要的是,HTTP API 不仅将 HTTP 用作数据传输机制,而且还将其用作成熟的应用程序协议。它们广泛使用了 HTTP 的原生特性和消息传递语义。这就是将 HTTP API 与 SOAP、OData、GraphQL、gRPC 和其他无数使用 HTTP 进行传输,但以其他方式向 API 使用者隐藏 HTTP 详细信息的 Web 服务协议区分开来的原因所在。
Hypermedia API 是 Web 服务的新兴类别,它带有一个链接的数据模型,并使用已有的词汇描述其所有资源和操作。它们可能会创造特定领域的术语,但只是为了填补通用词汇表中的空白。由于它们的接口完全在带内定义,因此它们不需要常规的静态 API 文档。这些 API 中最出色的成员将由新兴的“智能客户端”应用程序消费,这些应用程序未编码为任何静态客户端 – 服务器合约,并且行为有点像 Web 浏览器。
超媒体 API 也可以是 HTTP API,但这不是必需的。
截至 2021 年,虽然存在一些 bug 多多的演示应用,但真正的,由超媒体驱动的智能客户端的开发仍局限在纯粹的学术尝试层面。这种事物正处于 Web 标准开发的前沿。如果超媒体 API 进入主流,它并不会取代现有的 API 设计约定,但它们将催生全新类别的数据服务。我们拭目以待。
至于 REST,在计算机科学领域,这应该被视为一个相当特殊的概念。它的设计用例确实非常狭窄。
REST 适用于跨越多个组织的基于网络的长周期应用程序。——Roy Fielding
因此,我们不应该完全放弃“REST”一词,但是把它作为“API”的形容词是没有任何意义的。即使在不断努力实现真正的 RESTful API 的背景下,“hypermedia”也是一种更好、更具表现力的分类。