我对那些将任何基于 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:他描述的是当时存在的 Web,使用 HTTP 交换 HTML,人们“冲浪 Web”。JSON 当时还没有被创建,JSON 的广泛采用还要等十年。
REST 描述了一种 网络架构,它以 API 上的 约束 来定义,这些约束需要满足才能被视为 RESTful API。语言是学术性的,这导致了围绕该主题的困惑,但它足够清晰,大多数开发者应该能够理解。
REST 有许多约束和概念,但有一个关键想法,我相信这是 REST 与其他可能网络架构对比时最定义性和最区分性的特征。
这被称为 统一接口约束, 更具体地说,在该概念中,Hypermedia As The Engine of Application State (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 字段来显示适当的用户界面。此外,客户端必须基于“带外”信息知道账户上可用的操作,即从响应 之外 的另一个信息源派生的 URL、参数等信息,例如 swagger API 文档。
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 的那个!
第二个响应实际上是 Remote Procedure Call (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 的一部分,由 Microsoft 开发。XML-RPC 来自企业世界中 RPC 风格协议的悠久传统,还加入了大量的静态类型和早期的 XML 最大主义。
也在这个时候,AJAX 出现了,即 Asynchronous JavaScript and XML。请注意这里的 XML。正如大家现在所知,AJAX 允许浏览器在后台向服务器发出 HTTP 请求,并在 JavaScript 中直接处理响应,为 Web 编程打开了一个全新的世界。
问题是:这些请求应该是什么样的?显然它们应该是 XML。看,它就在名字里。而且这个新的 SOAP/XML-RPC 标准出来了,也许那就是正确的东西?
有些人注意到 Web 有 Fielding 描述的这种不同的架构,并开始询问是否 REST 而非 SOAP 应该成为访问即将被称为“Web 服务”的首选机制。Web 证明了极高的灵活性并在蓬勃发展,所以也许同样的网络架构,即 REST,对浏览器和人类工作得如此之好,也会适用于 API。
这听起来很合理,特别是当 API 的格式是 XML 时:XML 看起来 非常 像 HTML,不是吗? 你可以想象一个 XML API 满足所有 RESTful 约束,包括统一接口。
所以人们也开始探索这条路线。
与此同时,另一个重要的技术正在诞生:JSON
JSON 是(字面上)JavaScript 对 SOAP/RPC-XML 的 Java:简单、动态且易用。现在很难相信, 当 JSON 是大多数 Web API 的主导格式时,但 JSON 实际上花了一段时间才流行起来。直到 2008 年, API 开发的讨论主要围绕 XML,而不是 JSON。
2008 年,Martin Fowler 发表了一篇文章,推广了 Richardson Maturity Model, 这是一个模型,用于确定给定的 API 有多 RESTful。
该模型提出了四个“级别”,第一个级别是 Plain Old XML,或 POX 的沼泽。
从那里开始,一个 API 可以被视为更“成熟”的 REST API,因为它采用了以下想法:
GET、POST、DELETE 等)级别 3 是统一接口出现的地方,这就是为什么这个级别被视为最成熟的,真正“REST 的荣耀”
不幸的是,对 REST 术语来说,此时发生了两件事:
JSON 迅速接管了 Web 服务/API 世界,因为 SOAP/XML-RPC 过于工程化。JSON 简单, “就是能用”且易于阅读和理解。
随着这个变化,Web 开发世界彻底摆脱了 J2EE 心态,将 SOAP/XML-RPC 贬为仅限企业的东西。
由于 REST 方法不像 SOAP/XML-RPC 那样与 XML 紧密绑定,并且对端点没有施加太多正式性,REST 成为 JSON 接管的自然场所。而且它确实迅速这样做了。
在这一关键变化期间,有一件事变得越来越清楚:大多数 JSON API 停在了 RMM 的级别 2。
有些通过在响应中加入超媒体控件推进到级别 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 的东西,我的这个前端库黑客怎么可能是 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。
但是,到今天为止,绝大多数开发者和公司,即使他们在兴奋地向 他们的 API 添加 GraphQL 功能时,继续使用 REST 术语来描述他们正在构建的东西。
不幸的是,voidfunc 可能是对的:
你可以尽情敲击标志,但那场战斗很久以前就输了。REST 只是人们用来表示 HTTP+JSON RPC 的通用术语。
我们将继续将 显然 非 RESTful 的 JSON API 称为 REST,因为现在每个人都这么叫。
尽管我越来越大力地敲击标志,50 年后 Global Omni Corp. 仍然会为他们的 RESTful JSON API 的 v138 swagger 文档工作发布招聘广告。
无论如何,这里有一个机会向新一代 Web 开发者解释 REST,特别是统一接口,他们可能从未在原始上下文中听说过这些概念,并且假设 REST === JSON API。
人们感觉到有些不对劲,也许 REST,真正的、实际的 REST, 而不是 RESTless,可以成为那个答案的一部分。
至少,REST 背后的想法有趣且值得了解,作为一般的软件工程知识。
这里还有一个更大的元点:即使是一个相对聪明的群体(早期的 Web 开发者),借助 互联网的优势,并有一个相当清晰的(尽管有时学术性)REST 术语规范,也无法在二十年的时间内保持其含义与原始含义一致。
如果我们能把这个搞得这么明显错误,我们还可能错过什么?