REST如何变成了REST的反义词?

Carson Gross

敲黑板

你错了

我对人们将任何基于HTTP的接口称为REST API感到沮丧。今天的例子是SocialSite REST API。那是RPC。它尖叫着RPC。展示的耦合如此之多,应该给它一个X评级。

需要做什么才能使REST架构风格在超文本是一个约束的概念上变得清晰?换句话说,如果应用状态(以及API)的引擎不是由超文本驱动的,那么它就不能是RESTful的,也不能是REST API。句号。是否有某个损坏的手册需要修复?

–Roy Fielding,REST术语的创造者

   REST API必须是超文本驱动的

REST必须是计算机编程历史上被最广泛误用的技术术语。

我想不出还有什么能与之相比。

今天,当有人使用术语REST时,他们几乎总是在讨论使用HTTP的基于JSON的API。

当你看到提到REST的招聘启事或公司讨论REST指南时,他们很少会提到超文本或超媒体:相反,他们会提到JSON、GraphQL(!)之类的东西。

只有少数固执的人抱怨:但这些JSON API不是RESTful的!

在这篇文章中,我想给你一个简短、不完整且大部分错误的REST历史,以及我们如何走到一个REST的含义几乎完美地与其最初对比的东西相反的地方:RPC。

REST从何而来?

术语REST,即表述性状态转移的缩写,来自Fielding博士论文的第5章。Fielding描述了(当时新生的)万维网的网络架构,并将其与其他可能的网络架构(特别是RPC风格的网络架构)进行了对比。

重要的是要理解,在他写作时(1999-2000年),还没有JSON API:他描述的是当时存在的Web,人们“上网冲浪”时通过HTTP交换HTML。JSON尚未被创建,JSON的广泛采用还要再等十年。

REST描述了一种网络架构,它是根据API的约束定义的,这些约束需要满足才能被认为是RESTful API。语言是学术性的,这导致了围绕该主题的混淆,但它足够清晰,大多数开发者应该能够理解。

REST的关键:统一接口与HATEOAS

REST包含许多约束和概念,但有一个关键思想,我认为是REST与其他可能的网络架构对比时最具区分性的特征。

这被称为统一接口约束,更具体地说,是超媒体作为应用状态引擎(HATEOAS)的概念,或者如Fielding更喜欢称呼的,超媒体约束。

为了理解这个统一接口约束,让我们考虑两个HTTP响应,返回关于银行账户的信息,第一个是HTML(一种超文本),第二个是JSON:

HTML响应

HTTP/1.1 200 OK

<html>
    <body>
        <div>账号:12345</div>
        <div>余额:100.00美元</div>
        <div>链接:
            <a href="/accounts/12345/deposits">存款</a>
            <a href="/accounts/12345/withdrawals">取款</a>
            <a href="/accounts/12345/transfers">转账</a>
            <a href="/accounts/12345/close-requests">关闭请求</a>
        </div>
    </body>
</html>

JSON响应

HTTP/1.1 200 OK

{
    "account_number": 12345,
    "balance": {
        "currency": "usd",
        "value": 100.00
     },
     "status": "good"
}

这两个响应之间的关键区别,以及为什么HTML响应是RESTful的,而JSON响应不是,在于:

HTML响应是完全自描述的。

接收到这个响应的适当超媒体客户端不知道银行账户是什么,余额是什么等等。它只知道如何渲染超媒体HTML。

客户端不知道与此数据关联的API端点,除了HTML本身中可发现的URL和超媒体控件(链接和表单)。如果资源的状态发生变化,使得该资源上允许的操作发生变化(例如,如果账户透支),那么HTML响应将改变以显示新的可用操作集合。

客户端将呈现这个新的HTML,完全不知道“透支”是什么意思,甚至不知道银行账户是什么。

正是以这种方式,超文本成为应用状态的引擎:HTML响应“携带”了继续与系统交互所需的所有API信息。

现在,将这与第二个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为“RESTless”。

“REST”如何变成了“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标准出来了,也许这是正确的东西?

也许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的主导格式时,很难相信,但它实际上花了一段时间才流行起来。直到2008年,关于API开发的讨论主要还是围绕XML,而不是JSON。

规范化REST API

2008年,Martin Fowler发表了一篇文章,推广了Richardson成熟度模型,一个模型来确定给定API的RESTful程度。

该模型提出了四个“级别”,第一级是Plain Old XML,或POX的沼泽。

Richardson成熟度模型

从那里,API可以被认为更“成熟”为一个REST API,因为它采用了以下理念:

级别3是统一接口出现的地方,这就是为什么这个级别被认为是最成熟的,真正“REST的荣耀”

“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。

这就是我们如何走到这一步的一句话版本。

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的!

于是,我开始在互联网上无聊地唠叨:

REST的现状

最终大多数人厌倦了尝试向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的变更这样的事情,这些事情如果他们真的在创建RESTful API就不会发生。

最终,在2010年代末,人们受够了:REST,即使在其RESTless形式中,也跟不上日益复杂的SPA应用的需求。这些应用变得越来越像厚客户端,而厚客户端问题需要厚客户端解决方案,而不是变异的超媒体客户端解决方案。

GraphQL发布时,大坝真的决堤了。

GraphQL不能再不RESTful了:你绝对必须有文档才能理解如何使用使用GraphQL的API。客户端和服务器极其紧密耦合。其中没有原生超媒体控件。它提供模式,在许多方面感觉很像更新和精简版的XML-RPC。

在这里我想说:没关系!

在很多情况下,人们真的非常喜欢GraphQL,如果你正在构建一个厚客户端风格的应用,这很有道理:

这个问题的简短答案是HATEOAS不适合大多数现代API用例。这就是为什么近20年后,HATEOAS仍未在开发者中广泛采用。另一方面,GraphQL像野火一样蔓延,因为它解决了现实世界的问题。

GraphQL和REST级别3(HATEOAS)

所以GraphQL不是REST,它不声称是REST,它不想成为REST。

但截至今天,绝大多数开发者和公司,即使他们兴奋地为他们的API添加GraphQL功能,仍然使用术语REST来描述他们在构建的东西。

好吧,我们能对这种情况做些什么?

不幸的是,voidfunc可能是对的:

你可以尽情敲黑板,这场战斗很久以前就输了。REST只是人们用来指代HTTP+JSON RPC的常用术语。

我们将继续称显然非RESTful的JSON API为REST,因为现在每个人都这样称呼它们。

尽管我越来越激烈地敲黑板,50年后,Global Omni Corp.仍将招聘工作来开发其RESTful JSON API的swagger文档的第138版。

Roy Fielding不赞成

情况无望,但并不严重。

无论如何,这里有机会向新一代Web开发者解释REST,特别是统一接口,他们可能从未在其原始上下文中听说过这些概念,并认为REST === JSON API。

人们感觉到有些不对劲,也许REST,真正的、实际的REST,不是RESTless,可能是答案的一部分

至少,REST背后的思想作为一般软件工程知识是值得了解的。

这里还有一个更大的元观点:即使是一群相对聪明的人(早期Web开发者),在互联网的帮助下,并且有一个相当清晰(尽管有时学术)的REST术语规范,也无法在二十年的时间里保持其含义与原始含义一致。

如果我们能如此明显地弄错这一点,我们还可能弄错什么?

</>