越来越多的人将任何基于 HTTP 的接口称为 REST API,这让我感到沮丧。今天的例子是 SocialSite REST API。那就是 RPC。它尖叫着 RPC。它展示了如此多的耦合,以至于应该被评为 X 级。
为了让 REST 架构风格明确超文本是一种约束,需要做些什么?换句话说,如果应用程序状态(以及 API)的引擎不是由超文本驱动的,那么它就不是 RESTful 的,也不能是 REST API。就这么简单。难道是某个地方的说明书出了问题,需要修复吗?
–Roy Fielding,REST 术语的创造者
REST 可能是计算机编程历史上最被误用的技术术语。
我想不出其他任何接近的东西。
如今,当有人使用 REST 术语时,他们几乎总是讨论使用 HTTP 的基于 JSON 的 API。
当你看到一个工作岗位提到 REST 或者一家公司讨论REST 指南时,他们很少会提到超文本或超媒体:相反,他们会提到 JSON、GraphQL(!) 等。
只有少数固执的人抱怨:但这些 JSON API 并不是 RESTful 的!
在这篇文章中,我想向你提供简短的、不完整的、大部分是错误的REST 历史,以及我们是如何到达一个地方,在那里它的含义几乎完美地颠倒了,变成了 REST 最初与之对比的东西:RPC。
REST 术语,是 REpresentational State Transfer 的缩写,来自Fielding 博士论文的第 5 章。Fielding 描述了(当时新出现的)万维网的网络架构,并将其与其他可能的网络架构(特别是 RPC 风格的网络架构)进行了对比。
重要的是要了解,在他写作的时候(1999-2000 年),还没有 JSON API:他描述的是当时存在的网络,人们通过 HTTP 交换 HTML,以“冲浪网络”。JSON 还没有被创建,JSON 的广泛采用还要十年时间。
REST 描述了网络架构,它是根据 API 上的约束来定义的,为了被认为是 RESTful API,这些约束必须满足。语言是学术性的,这加剧了人们对该主题的困惑,但它足够清晰,大多数开发人员应该能够理解它。
REST 在其内部包含许多约束和概念,但有一个关键的想法我认为是 REST 的决定性特征,也是它与其他可能的网络架构最显著的区别。
这被称为统一接口约束,更具体地说,在这个概念中,超媒体作为应用程序状态的引擎 (HATEOAS),或者如 Fielding 愿意称之为,超媒体约束。
为了理解这个统一接口约束,让我们考虑两个返回关于银行账户信息的 HTTP 响应,第一个是 HTML(超文本),第二个是 JSON
HTTP/1.1 200 OK
<html>
<body>
<div>Account number: 12345</div>
<div>Balance: $100.00 USD</div>
<div>Links:
<a href="/accounts/12345/deposits">deposits</a>
<a href="/accounts/12345/withdrawals">withdrawals</a>
<a href="/accounts/12345/transfers">transfers</a>
<a href="/accounts/12345/close-requests">close-requests</a>
</div>
<body>
</html>
HTTP/1.1 200 OK
{
"account_number": 12345,
"balance": {
"currency": "usd",
"value": 100.00
},
"status": "good"
}
这两个响应之间的关键区别在于,为什么HTML 响应是 RESTful 的,而JSON 响应不是,在于
HTML 响应是完全自描述的。
一个接收此响应的适当超媒体客户端不知道什么是银行账户、什么是余额等。它只知道如何呈现超媒体,即 HTML。
客户端对与该数据关联的 API 端点一无所知,除非通过 URL 和超媒体控件(链接和表单),这些控件可以在 HTML 本身内发现。如果资源的状态发生变化,导致该资源上可用的允许操作发生变化(例如,如果账户透支),那么 HTML 响应将改变以显示新的可用操作集。
客户端将呈现这个新的 HTML,完全不知道“透支”意味着什么,或者,事实上,甚至不知道什么是银行账户。
正是通过这种方式,超文本成为了应用程序状态的引擎:HTML 响应“携带”所有 API 信息,这些信息直接在自身内,以便继续与系统进行交互。
现在,将它与第二个 JSON 响应进行对比。
在这种情况下,消息不是自描述的。相反,客户端必须知道如何解释status
字段以显示相应的用户界面。此外,客户端必须根据“带外”信息,即来自响应之外的其他信息来源(例如,swagger API 文档)派生的有关 URL、参数等的知识,来了解哪些操作对账户可用。
JSON 响应不是自描述的,它没有在超媒体中编码资源的状态。因此,它没有通过 REST 的统一接口约束,因此,它不是 RESTful 的。
在REST API 必须由超媒体驱动中,Fielding 继续说道
REST API 应该在没有超出初始 URI(书签)和适合目标受众的标准化媒体类型集的任何先验知识的情况下进入(即,任何可能使用该 API 的客户端都应该理解)。从那时起,所有应用程序状态转换都必须由客户端选择服务器提供的选择来驱动,这些选择存在于接收到的表示中,或者由用户对这些表示的操作隐含。
因此,在 RESTful 系统中,你应该能够通过单个 URL 进入系统,从那时起,系统内进行的所有导航和操作都应该完全通过自描述的超媒体提供:例如,通过 HTML 中的链接和表单。除了入口点之外,在适当的 RESTful 系统中,API 客户端不应该需要任何关于你的 API 的额外信息。
这是 RESTful 系统具有惊人灵活性的来源:由于所有响应都是自描述的,并编码了所有当前可用的可用操作,因此无需担心,例如,对你的 API 进行版本控制!事实上,你甚至不需要为它编写文档!
如果事情改变了,超媒体响应也会改变,仅此而已。
这是一个为构建分布式系统而设计的极其灵活和创新的概念。
如今,大多数 Web 开发人员和大多数公司都会称第二个示例为 RESTful API。
他们甚至可能不会将第一个响应视为 API 响应。那只是 HTML!
(可怜的 HTML,得不到尊重。)
API 总是 JSON,或者如果你很花哨,可能是类似 Protobuf 的东西,对吧?
错了。
你们都错了,你们应该感到难过。
第一个响应是一个 API 响应,事实上,它是 RESTful 的那个!
第二个响应实际上是一个远程过程调用 (RPC) 风格的 API。客户端和服务器是耦合的,就像 Fielding 在 2008 年抱怨的 SocialSite API 一样:客户端需要拥有关于它正在处理的资源的额外知识,这些知识必须来自 JSON 响应本身之外的某个其他来源。
从本质上讲,这个 API 几乎是 REST 的反面。
让我们称这种风格的 API 为“RESTless”。
现在,我们究竟是如何到达一个地方,在那里显然不是 RESTful 的 API 被行业中 99.9% 的人称为 RESTful 的?
这是一个有趣的故事
Roy Fielding 在 2000 年发表了他的论文。
大约在同一时间,XML-RPC,一个明确受 RPC 启发的协议被发布,并开始作为一种使用 HTTP 构建 API 的方法而流行起来。XML-RPC 是一个更大项目的一部分,叫做SOAP,来自微软。XML-RPC 来自 RPC 风格协议的悠久传统,主要来自企业界,其中还包括许多静态类型和早期 XML 最大化。
同样出现在这一时刻的是AJAX,或异步 JavaScript 和 XML。请注意这里面的 XML。正如现在所有人都知道的那样,AJAX 允许浏览器在后台向服务器发出 HTTP 请求,并在 JavaScript 中直接处理响应,为 Web 开启了全新的编程世界。
问题是:这些请求应该是什么样子的?它们显然将是 XML。看,它就在名字里。而且这个新的 SOAP/XML-RPC 标准已经出来了,也许这就是正确的东西?
有些人注意到,Web 拥有 Fielding 描述的这种不同的架构,并开始询问 REST 而不是 SOAP 是否应该是访问开始被称为“Web 服务”的最佳机制。网络已被证明是极其灵活的,并且发展迅速,所以也许与浏览器和人类一起运作良好的相同的网络架构,REST,也能很好地用于 API。
这听起来似乎是合理的,尤其是在 XML 是 API 格式的时候:XML 确实看起来非常像 HTML,不是吗?你可以想象一个 XML API 满足所有 RESTful 约束,包括统一接口。
所以人们开始探索这条路线。
当这一切都在发生的时候,另一项重要的技术正在孕育之中:JSON
JSON 相当于 SOAP/RPC-XML 的 Java(字面上):简单、动态且易于使用。现在很难相信,当 JSON 成为大多数 Web API 的主要格式时,它实际上花了很长时间才流行起来。直到 2008 年,围绕 API 开发的讨论主要围绕 XML,而不是 JSON。
2008 年,Martin Fowler 发表了一篇文章,普及了Richardson 成熟度模型,该模型用于确定给定 API 的 RESTful 程度。
该模型提出了四个“级别”,第一个级别是简单的旧 XML,即 POX 的沼泽。
从那里,一个 API 就可以被认为是更“成熟”的 REST API,因为它采用了以下想法
GET
、POST
、DELETE
等)级别 3 是统一接口发挥作用的地方,这也是为什么该级别被认为是最成熟的,并且真正体现了“REST 的荣耀”
不幸的是,对于 REST 这个词来说,当时发生了两件事
JSON 迅速席卷了 Web 服务/API 世界,因为 SOAP/XML-RPC 的设计过于复杂。JSON 很简单,“只需工作” 并且易于阅读和理解。
随着这种改变,Web 开发世界彻底摆脱了J2EE 思维,将 SOAP/XML-RPC relegating to an enterprise-only affair.
由于 REST 方法不像 SOAP/XML-RPC 那样与 XML 紧密绑定,而且它没有对端点施加太多形式,所以 REST 成为 JSON 接管的自然场所。它也确实做到了,而且速度很快。
在这个关键的变革过程中,有一件事变得越来越清晰:大多数 JSON API 都停留在 RMM 的第 2 级。
一些 API 通过在其响应中包含超媒体控制而进入了第 3 级,但几乎所有这些 API 仍然需要发布文档,这表明“REST 的荣耀”并没有实现。
JSON 取代了响应格式应该是强有力的提示:JSON 明显不是超文本。你可以在它的基础上强加超媒体控制,但这并不自然。XML 至少看起来像 HTML,有点像,所以用它创建超媒体是可行的。
JSON 只是...数据。添加超媒体控制很笨拙、不标准,而且很少以统一接口约束描述的方式使用。
尽管存在这些困难,REST 这个词仍然存在:REST 是 SOAP 的对立面,JSON API 不是 SOAP,因此 JSON API 就是 REST。
这就是我们走到今天这一步的简短版本。
尽管 JSON API 世界从未始终如一地实现真正 RESTful 的 API,但关于创建的 RESTless API 是否“RESTful”有很多争论:关于 URL 布局的争论,关于哪个 HTTP 动词适合特定操作,关于媒体类型的口水战,等等。
我当时还年轻,整个事情让我觉得不透明、死板和疏远,所以我基本上放弃了对 REST 的所有想法:它是在互联网上被傲慢的人争论的东西。
我很少看到(或者,当我看到时,我不理解)统一接口的概念以及它对 RESTful 系统的重要性。
直到我创建了intercooler.js,并且一些聪明人开始告诉我它很 RESTful,我才重新对这个想法产生了兴趣。
RESTful?那是 JSON API 的事情,我的前端库 hack 怎么可能是 RESTful 的?
所以我研究了一下,用全新的眼光重新阅读了 Fielding 的论文,结果发现,天哪,不仅 intercooler 是 RESTful 的,而且我所处理的所有“RESTful” JSON API 根本就不是 RESTful 的!
于是,我开始让互联网无聊得要死
最终,大多数人厌倦了尝试将超媒体控制添加到 JSON API 中,并放弃了它。虽然这些控制在某些特定情况下(例如分页)工作得很好,但它们从未实现 REST 在通用、面向人类的互联网中所发现的广泛、明显的效用。(我有理论解释为什么会有这种情况。)
事情稳定在这个中间的 RESTless 状态,REST 慢慢地巩固了它作为 RMM 第 1 级或第 2 级 JSON API 的意义。但始终存在着我们突破第 3 级和 REST 光辉的可能性。
然后单页应用程序 (SPA) 出现了。
当 SPA 出现时,Web 开发完全与最初的底层 RESTful 架构脱节。SPA 应用程序的整个网络架构转移到了 JSON RPC 样式。此外,由于这些应用程序的复杂性,开发人员专门化成了前端和后端。
前端开发人员显然没有做任何 RESTful 的事情:他们使用 JavaScript,构建 DOM 对象,并在需要时调用 AJAX API。这更像是厚客户端创作,而不是早期 Web 中的任何东西。
后端工程师在一定程度上仍然关注网络架构,并且继续使用“REST”这个词来描述他们的工作。
即使他们正在做的事情,例如为他们的 RESTful API 发布 swagger 文档,或者抱怨他们的 RESTful API 的 API churn,如果他们实际上正在创建 RESTful API,这些事情就不会发生。
最后,在 2010 年代后期,人们已经忍无可忍了:REST,即使是 RESTless 的形式,也无法满足日益复杂的 SPA 应用程序的需求。这些应用程序越来越像厚客户端,而厚客户端问题需要厚客户端解决方案,而不是扭曲的超媒体客户端解决方案。
当GraphQL 发布时,大坝终于决堤了。
GraphQL 不能再不 RESTful 了:你绝对必须有文档才能理解如何使用使用 GraphQL 的 API。客户端和服务器之间有着非常紧密的耦合。其中没有本地的超媒体控制。它提供了模式,并且在许多方面,感觉就像是一个更新和简化的 XML-RPC 版本。
在这里我要说:这没问题!
在许多情况下,人们真的非常喜欢 GraphQL,而且如果你正在构建一个厚客户端风格的应用程序,这是很有意义的
这个问题的简短答案是,HATEOAS 不适合 API 的大多数现代用例。这就是为什么在近 20 年后,HATEOAS 仍然没有在开发人员中得到广泛应用的原因。另一方面,GraphQL 像野火一样蔓延,因为它解决了现实世界的问题。
所以 GraphQL 不是 REST,它没有声称是 REST,它也不想成为 REST。
但是,截至今天,绝大多数开发人员和公司,即使他们兴奋地将 GraphQL 功能添加到他们的 API 中,也继续使用 REST 这个词来描述他们正在构建的东西。
不幸的是,voidfunc 可能是对的
你可以尽情地敲打那个牌子,那场战斗很久以前就输了。REST 只是人们对 HTTP+JSON RPC 的常用术语。
我们将继续称那些明显不 RESTful 的 JSON API 为 REST,因为这是现在每个人都这样称呼它们。
尽管我越来越用力地敲打牌子,但 50 年后,Global Omni Corp. 仍然会为他们的 RESTful JSON API 的 swagger 文档的 v138 版本发布工作岗位。
无论如何,这里有一个机会可以向新一代可能从未在原始上下文中听说过这些概念,并且认为 REST === JSON API 的 Web 开发人员解释 REST,尤其是统一接口。
人们感觉到有什么不对劲,也许 REST,真正的 REST,而不是 RESTless,可能是这个问题的答案的一部分。
至少,REST 背后的理念很有趣,值得了解,就像一般的软件工程知识一样。
这里还有一个更大的元观点:即使是一群相对聪明的人(早期 Web 开发人员),在互联网的帮助下,并且对 REST 这个词有一个相当清晰(虽然有时是学术性的)规范,也无法在 20 年的时间里保持其含义与原始含义一致。
如果我们能把事情搞得这么明显地错误,那么我们还可能在哪些方面出错呢?