我对那些将任何基于 HTTP 的接口都称为 REST API 的人感到失望。今天的示例是一个 SocialSite REST API。这就是一个 RPC,它尖叫着。它展示的耦合太多了,应该给它一个 X 评级。
需要做些什么才能使 REST 架构风格清楚地认识到超文本是一种约束呢?换句话说,如果应用程序的状态引擎(以及 API)不是由超文本驱动的,那么它就不是 RESTful 的,也就不是 REST API。是否有一些破损的手册需要修复呢?
——Roy Fielding,术语 REST 的创造者
REST肯定是计算机编程史上最被广泛滥用的技术术语了。我想不出还有能与之媲美的了。
今天,当人们使用 REST 这个术语时,他们几乎总是在讨论基于 JSON 的 HTTP API。比如当你看到一篇关于 REST 的招聘启事,或者一家公司在讨论REST准则时,他们很少会提到超文本或超媒体,相反他们会提到 JSON、GraphQL(!)等等。只有少数顽固的人才会抱怨:但这些 JSON API 不是 RESTful 的!
在本文中,我将会介绍 REST简短、不完整,且大多是错误的历史,以及我们如何走到如今的境地:REST 的含义几乎完全被颠倒过来了,变成了与 RPC 相同的含义,而最初两者的含义是可以拿来对比的。
REST 从何而来?
REST 一词是表征状态转移(REpresentational State Transfer)的缩写,来自Fielding博士论文的第5章。Fielding 描述了万维网(当时是全新的)的网络架构,并将其与其他可能的网络架构,特别是 RPC 风格的网络架构进行了对比。
重要的是,在他写这篇论文的时候(1999-2000)还没有 JSON API。他描述的是当时的网络:人们“浏览网页”时,HTML 通过 HTTP 交换。此时,JSON 还没有被创建出来,直至十年后 JSON 才被广泛采用。
REST 描述了一种网络架构,它是根据 API 上的约束来定义的,只有满足这些约束才能被视为 RESTful API。这个语言是学术性的,这导致人们对该主题有了困惑,但它足够清楚,大多数开发人员都应该能够理解它。
REST 的关键:统一接口 &HATEOAS
REST 中有许多约束和概念。但我认为,与其他可能的网络架构相比,REST 有一个关键的思想,也是 REST 最具定义性和最显著的特征,那就是被称为统一接口(Uniform Interface)的约束。更具体地说,在这个概念中,超媒体作为应用状态引擎(Hypermedia As The Engine of Application State,HATEOAS),用 Fielding 话说就是超媒体约束。
为了理解这个统一接口约束,让我们考虑两个返回银行账户信息的 HTTP 响应。第一个是 HTML 的(超文本),第二个是 JSON 的:
HTML 响应
JSON 响应
这两个响应的关键区别,以及 HTML 响应是 RESTful 的、而 JSON 响应不是的原因就是:HTML 响应完全是自描述的。
收到该响应的超媒体客户端不知道银行帐户是什么、余额是什么等信息,它只知道如何通过 HTML 呈现超媒体。
除了通过 HTML 本身可发现的 URL 和超媒体控件(链接和表单),客户端对与该数据相关的 API 端点一无所知。如果资源的状态发生了变化,使得在该资源上允许的可用操作发生了变化(例如,如果帐户透支),那么 HTML 响应也将发生变化,并显示新的可用操作集。客户端将呈现这个新的 HTML,完全不知道“透支”是什么意思,甚至根本不知道银行账户是什么。
正是通过这种方式,超文本成为了应用程序的状态引擎:HTML 响应“携带”了所有必要的 API 信息,以便在其内部直接与系统交互。
现在,将其与第二个 JSON 响应进行对比。
在 JSON 响应的案例中,消息不是自描述的。相反,客户端必须要知道如何解释 status
字段以显示合适的用户界面。此外,客户端必须要根据“带外”的信息(即 URL、参数等信息等,这些信息来自响应之外的另一个信息源,如 swagger API 文档)以了解该帐户上可用的操作。
JSON 响应不是自描述的,并且也不会对超媒体中的资源状态进行编码。因此,它不符合 REST 统一接口约束,因此它不是 RESTful 的。
发明者:RESTful API 必须是超媒体驱动的
在《Rest API必须是超媒体驱动》的一文中,Fielding 表示:
输入 REST API 时,除了初始的 URI(书签)和一组适合目标受众的标准化媒体类型(即任何可能使用该 API 的客户端都能理解的)之外,应该没有任何先验知识。从那时起,所有应用程序状态转换都必须由客户端选择服务端所提供的选项来驱动,这些选项存在于接收到的表示中,或者暗含于用户对这些表示的操作。
因此,在 RESTful 系统中,你应该能够通过单个 URL 进入系统,并且从那时起,系统内的所有导航和操作都应该完全通过自描述的超媒体所提供,例如 HTML 中的链接和表单。除了入口点之外,在合适的 RESTful 系统中,API 客户端不应该需要任何与 API 相关的附加信息。
这就是 RESTful 系统令人难以置信的灵活性的源头:因为所有响应都是自描述的,并且对所有当前可用的操作都进行了编码。所以不需要担心利用 API 进行版本控制。
事实上,你甚至不需要记录它!如果情况发生了变化,超媒体的响应也会发生变化,仅此而已。对于构建分布式系统来说,这也是一个非常灵活且创新的概念。
行业:不,RESTful API 是 JSON 的
今天,大多数 Web 开发人员和大多数公司都会将第二个示例称为 RESTful API。他们甚至可能不会将第一个响应视为 API 响应,认为它只是 HTML。(可怜的 HTML,得不到任何尊重。)
API 总是 JSON 的,或者如果你喜欢的话,它可能是 Protobuf 之类的,对吗?错!
第一个响应才是一个 API 响应,实际上,它是一个 RESTful 响应!而第二个响应是远程过程调用(RPC)风格的 API。客户端和服务端是耦合的,就像 Fielding 在 2008 年抱怨的 SocialSite API 一样:客户端需要对其正在使用的资源有更多的了解,这些知识必须来自于 JSON 响应本身之外的其他来源。
这个 API 在本质上几乎与 REST 相反。我们将这种风格的 API 称为“伪 REST”(pseduoREST)。
REST 是如何成为“伪 REST”的
现在,我们到底是如何走到这个地步的:显然不是 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 标准就出来了。也许这是正确的做法?
也许 REST 能用于 Web 服务?
一些人注意到,Web 具有 Fielding 所描述的这种不同的架构,并开始询问 REST 而不是 SOAP 是否应该成为连接被称为“Web 服务”的首选机制。事实证明,Web 是极其灵活的,并且正在不断成长为帮派杀手,因此,也许同样的网络架构,REST,在浏览器以及人们可以很好地使用的 API 上都能运行得很好。
这听起来似乎很有道理,尤其是当 XML 是 API 的格式时:XML 看起来确实非常像 HTML,不是吗?你可以想象一个 XML API 满足所有的 RESTful 约束,包括统一接口。所以人们也开始探索这条路线。
在这一切发生的同时,另一项重要的技术也正在诞生:JSON
JSON(字面上)就是从 JavaScript 到 SOAP/RPC-XML 的 Java:简单、动态和容易。现在 JSON 是大多数 Web API 的主要格式,这在当时人们很难相信它,实际上 JSON 也是花了很长一段时间才流行起来。直到 2008 年,关于 API 开发的讨论还主要是围绕 XML,而不是 JSON。
形式化 REST API
2008 年,Martin Fowler 发表了一篇文章,推广了Richardson成熟度模型(Richardson Maturity Model,RMM),该模型用于确定给定 API 的 RESTful 程度。
该模型提出了四个“层次”,第一个层次是 Plain Old XML,即 POX 沼泽。
因此,一个 API 可以被认为是更“成熟”的 REST API,需要采用以下的思想:
层级 1:资源(例如,资源感知 URL 布局,与 XML-RPC 中的不透明 URL 布局形成了对比)
层级 2:HTTP 动词(正确使用
GET
、POST
、DELETE
等)层级 3:超媒体控件(例如链接)
层级 3 是统一接口的所在,这就是为什么这个层级被认为是最成熟和真正的“REST 的荣耀”(Glory of REST)的原因。
“REST”赢了,但走歪了
不幸的是,此时对于 REST 这个术语,发生了两件事:
大家都改用 JSON 了
大家都还停留在 RMM 的层级 2
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。
我们就是这么走到这一步的。
REST 之战
尽管在 JSON API 世界中从未始终如一地实现真正的 RESTful API,但对于正在创建的 pseudoREST API 是否是“RESTful”的争论还有很多:关于 URL 布局的争论、关于 HTTP 动词是否适用于给定动作的争论、关于媒体类型的激烈争论等等。
那时候我还年轻,整个事情让我感到不透明和疏远,所以我几乎放弃了 REST 的整个思想:这是人们在互联网上争权夺利的东西。
我很少看到有人提到(或者,即使提到,我也不理解)统一接口的概念,以及它对 RESTful 系统的重要性。直到我创建了intercooler.js,一些聪明的人开始告诉我它是 RESTful 的,我才再次对这个思想产生了兴趣。
RESTful?这是一个 JSON API,前端库怎么可能是 RESTful 的呢?
所以我仔细研究了一下,以全新的眼光重新阅读了 Fielding 的论文,然后发现不仅 intercooler 是 RESTful 的,而且我处理的所有“RESTful”JSON API 都不是 RESTful!
于是,我开始了无聊至极的网络浏览:
REST 如今的状态
最终,大多数人厌倦了在 JSON API 中添加超媒体控件并放弃了。虽然这些控件在某些特定的情况下(例如分页)可以很好地运行,但它们从未实现 REST 在面向人类的互联网中所需要的实用性。
事情变成了这种中间态的“伪 REST”(pseudoREST)状态,REST 慢慢地在 RMM 的层级 1 或层级 2 巩固了它作为 JSON API 的意义。但我们始终有可能突破到层级 3,从而再创 REST 的荣耀。
然后,单页面应用程序(Single Page Applications,SPA)出现了。
当 SPA 出现时,Web 开发完全脱离了原始的底层 RESTful 架构。SPA 应用程序的整个网络架构转移到了 JSON RPC 格式。此外,由于这些应用程序的复杂性,开发人员分别专注于前端和后端。
前端开发人员显然没有做任何的 RESTful 工作:他们使用 JavaScript,构建 DOM 对象,并在需要时调用 AJAX API。与早期的 Web 相比,这更像是一种富客户端创作。
后端工程师在一定程度上仍然关心网络架构,他们继续使用“REST”术语来描述他们正在做的事情。
尽管他们做了一些事情,比如为他们的 RESTful API 发布了洋洋洒洒的文档,或者抱怨RESTful API的流失,但如果他们真地创建了 RESTful API,这些事情就不会发生了。
最后,在 2010 年代的后期,人们受够了:REST(即使是伪 REST 形式)根本无法满足日益复杂的 SPA 应用程序的需求。应用程序越来越像富客户端,富客户端问题需要富客户端的解决方案,而不是退化的超媒体客户端解决方案。
当GraphQL发布时,大坝就真的决堤了。
GraphQL 是最不 RESTful 的:你绝对需要文档才能理解如何使用使用 GraphQL API。客户端和服务端的耦合非常紧密。这其中没有原生超媒体控件,它提供了模式,并且在许多方面感觉很像是 XML-RPC 的更新和精简版本。
在这里,我想说:没关系。在很多情况下,人们真的非常喜欢 GraphQL,如果你正在构建一个富客户端风格的应用程序,那么这很有意义:
这个问题的简单答案是,HATEOAS 并不适合大多数的现代 API 用例。这就是为什么在将近 20 年之后,HATEOAS 仍然没有在开发人员中得到广泛的采用。另一方面,GraphQL 能像野火一样蔓延,是因为它解决了现实世界中的问题。
所以 GraphQL 不是 REST,它没有声称是 REST,也不想成为 REST。
但直到今天,绝大多数的开发人员和公司仍继续会使用术语 REST 来描述他们正在构建的东西,即使他们兴奋地将 GraphQL 功能添加到了他们的 API 中。
对于这种情况,我们能做些什么?
不幸的是,voidfunc可能是对的:
你可以随心所欲地敲击这个标志,但这场战斗很久之前就输了。REST 只是人们用于 HTTP+JSON RPC 的常用术语。
我们将继续把明显不是 RESTful JSON 的 API 称为 REST,因为现在大家都这么称呼。
尽管我抨击地越来越用力,但 50 年后,Global Omni Corp 仍然会为他们 RESTful JSON API 的 swagger 文档 v138 做宣传。
情况虽然无望,但也并不严重。无论如何,在这里有机会向新一代的 Web 开发人员解释 REST,特别是统一接口,这些开发人员可能在他们最初的环境中从未听说过这些概念,他们认为 REST = JSON API。
人们已经感觉到有些地方出了问题。也许 REST,真正、实际的 REST,而不是伪 REST(pseudoREST),解决这个问题的部分答案。至少 REST 背后的思想很有趣,值得了解,就像基础的软件工程知识一样。
这里还有一个更大的要点:即使是一群相对聪明的人(早期的 Web 开发人员),在互联网的帮助下,对术语 REST 有一个非常明确的(有时甚至是学术的)规范,也不能在 20 年的时间里始终保持它的含义与原始的一致。
如果我们能把这么离谱的错误弄清楚,我们还会在什么地方出错呢?
原文链接:
https://htmx.org/essays/how-did-rest-come-to-mean-the-opposite-of-rest/
评论