那个REST API真的是RPC吗?Roy Fielding似乎这样认为。

106
我之前对REST的许多理解都是错误的,而且这种情况并不少见。本问题有一个很长的导入部分,但似乎必要,因为信息有点分散。如果您已经熟悉此主题,则实际问题将在最后一个段落中提出。
从罗伊·菲尔丁(Roy Fielding)的《REST API必须是超文本驱动的》第一段开始,很明显他认为他的工作被广泛误解:
“我越来越沮丧了,因为有很多人称任何基于HTTP的接口都是REST API。今天的例子就是SocialSite REST API。那是RPC。它尖叫着RPC。展示出来的耦合度太高了,应该被给予X评级。”
Fielding列出了几个REST API的属性。其中一些似乎与常见做法和SO等其他论坛上的常见建议相违背。例如:
  • REST API应该不需要任何先前的知识,除了初始URI(书签)和适合预期受众理解的一组标准化媒体类型(即,可能使用该API的任何客户端都能理解)。...

  • REST API不得定义固定的资源名称或层级结构(客户端和服务器之间的明显耦合)。...

  • REST API几乎应该在定义用于表示资源和驱动应用程序状态的媒体类型或在定义扩展关系名称和/或现有标准媒体类型的超文本启用标记方面花费所有的描述性精力。...

"超文本"的概念发挥着核心作用——比URI结构或HTTP动词的含义更为重要。其中一个评论中定义了“超文本”的概念:

当我[Fielding]说超文本时,我的意思是指信息和控件的同时呈现,以便信息成为用户(或自动机)通过获取选择和选择操作的权利。超媒体仅是将文本的含义扩展到包括媒体流中的时间锚点;大多数研究人员已经放弃了这种区别。

超文本并不需要在浏览器上使用HTML。机器可以在他们理解数据格式和关系类型时跟随链接。

我猜这时候,前两点似乎表明像下面这样的Foo资源的API文档会导致客户端和服务器之间的紧密耦合,并且在RESTful系统中没有位置。

GET   /foos/{id}  # read a Foo
POST  /foos/{id}  # create a Foo
PUT   /foos/{id}  # update a Foo

相反,代理应该被迫通过例如针对/foos发出GET请求来发现所有Foos的URI。(这些URI可能会遵循上面的模式,但这不是重点。)响应使用能够传达如何访问每个项以及可以执行什么操作的媒体类型,从而引起上述第三点。因此,API文档应专注于解释如何解释响应中包含的超文本。
此外,每次请求Foo资源的URI时,响应都包含了代理发现如何继续的所有信息,例如通过它们的URI访问相关和父资源,或在创建/删除资源后采取行动。
整个系统的关键在于响应包含在媒体类型中的超文本本身传达了代理继续的选项。这与浏览器为人类工作的方式类似。
但这只是我此刻的最佳猜测。
Fielding发布了一篇后续文章,回应了他的讨论过于抽象、缺乏示例和术语过多的批评:

那么,对于具有实际思维的REST专家来说,有两个简单的问题:您如何解释Fielding所说的内容,并在记录/实现REST API时如何将其付诸实践?

编辑:这个问题是一个例子,说明如果您不知道自己正在谈论的内容的名称,学习起来会有多困难。在这种情况下,名称为“超媒体作为应用程序状态引擎”(HATEOAS)。


28
约翰,Rich只是在解释他的思想转变。这并没有主观或争议性。请投票以保持开放——这是我在SO上看到的标记为“rest”的较好问题之一。 - Keith Gaughan
4
"Keith应该在他的博客中解释心态的变化,而不是在Stack Overflow上进行。" - John Saunders
14
他没有解释他心态的变化,他是在询问他的理解是否准确。 - aehlke
5
很好的总结。我从这个问题中学到的比大多数回答都要多。 - Martin Konecny
9个回答

22

我认为你的解释基本上涵盖了大部分内容。URI是不透明的标识符,通常不应超出书签URI而被传播,后者用于用户代理访问应用程序。

至于文档记录,这个问题已经被解决了很多次。您应该记录您的媒体类型,以及它包含的超链接控件(链接和表单),如果需要的话,可以记录交互模型(请参见AtomPub)。

如果您记录URI或如何构建它们,则行不通。


这还是真的吗?有一些API响应规范(如Ionspec)已经将这些URI作为响应的一部分有意地包含了进去。 - Sean Pianka
是的,他们有。此时问题在于确定这些记录的URI是否只是应用程序的入口点,这些入口点保证会保留(其中一些不常见但非常有用),还是因为人们需要代码生成,这些URI被直接嵌入到代码中,阻止服务器让客户端知道如何进行操作。如果客户端认为它已经知道了由于那个契约,你就不是在超媒体中,而是进入了现代的OpenAPI SOAP模型,具有与18年前相同的问题。 - SerialSeb
真实的是,在过去的11年中,许多API文档语言已经出现了,但基本原理并没有改变。我相信发现这些链接或至少URI模板发现的价值在于构建可重用的通用客户端代码,可以动态地使用它们,从而允许服务器端的许多实现重用相同的客户端代码。URI嵌入继续使这种情况更加困难,但如果您使用这些格式,则倾向于将生成的客户端与这些规范紧密耦合,因此您已经失去了该功能。 - SerialSeb
如果客户认为它知道由于那个合同,你不是在超媒体中,而是进入了现代的OpenAPI SOAP模型,具有与18年前相同的问题。你能解释一下你的意思吗? - Sean Pianka

8
你的解释对我来说是正确的。我确信Fielding的约束可以在实践中应用。
我真的很想看到有人发布一些如何记录REST接口的好例子。有很多糟糕的例子,有一些有效的例子可以指引用户将非常有价值。

2
哇,那个“资源模型”页面让我感动得落泪。希望这能开启一个趋势。 - Darrel Miller
真遗憾,这基本上是网络上唯一的此类API示例!更糟糕的是,我没有找到任何遵循该原则的客户端代码的好例子。 - jkp
1
@DarrelMiller 但是这些Media Types不是太“具体”了吗?在我看来,他们的API确实只使用了一个MIME:application/json,而Resource Model确实是关系。我是否误解了REST的这一方面?我还阅读了你在Stack Overflow的一个回答,似乎指出应该避免那些“一个属性”的合同... - edsioufi
2
@RichApodaca 您的链接已经挂了。http://web.archive.org/web/20170409132237/https://kenai.com/projects/suncloudapis/pages/Home - forresthopkinsa

6
我一直在寻找一个遵循HATEOAS的API的好例子,但很难找到(我发现SunCloud API和AtomPub对于“普通”的API情况都很难应用)。所以我尝试在我的博客上制作一个逼真的例子,遵循Roy Fielding关于什么是正确的REST实现的建议。尽管它在原则上非常简单(只是在处理API时与网页相比有些混淆),但我发现编写这个例子非常困难。我明白Roy所关注的问题并且同意,只是需要转变思维方式才能正确地实现API。
请看:使用Rest的API示例

4

大多数人误解的是(至少我认为)在REST世界中,你不需要记录你的“REST接口”,你要记录的是媒体类型,而不是你的服务器或服务。


4
唯一的例外是,在如何构建URI方面提供指导时,可以在超文本响应中发送一个URI模板,由客户端自动替换字段,使用超文本中的其他字段。但这通常并不能节省太多带宽,因为gzip压缩会很好地处理URI的重复部分,不必担心这个问题。
有关REST和相关HATEOAS的一些好讨论: 在RESTFul API中使用HATEOAS的优势 如何喝一杯咖啡

4
我认为随着REST发布已有多年,技术人员已经理解了资源的概念以及何时符合REST架构风格。根据Richardson成熟度模型,API的RESTful程度被分为四个级别(0-3),其中3级表示完全符合Roy Fielding的REST原则。
0级是指只有一个入口URI,类似于SOAP。
1级表示API可以区分不同的资源,并具有多个入口点,但仍然带有SOAP的味道。
2级是指使用HTTP动词 - GET,POST,DELETE等。这是REST真正发挥作用的级别。
在第3级中,您开始使用超媒体控件将API变得真正符合REST规范。
进一步阅读建议链接:
- 什么是Richardson成熟度模型? - Martin Fowler的博客:Richardson成熟度模型

4

对于那些感兴趣的人,我发现在Sun Cloud API中有一个关于HATEOAS实践的详细例子。


3
链接已失效。档案 - Vanity Slug

1

完全正确。此外,我还要指出,只要模式来自服务器接收到的文档(OpenSearch是一个合适的例子),URI模板在RESTful应用程序中是完全可以使用的。对于URI模板,您需要记录它们在哪里使用以及模板中预期的占位符,但不需要记录模板本身。与Wahnfrieden所说的略有不同,这并不是一个例外。

例如,在我的工作中,我们有一个内部域管理系统,服务文档指定了两个URI模板:一个用于生成域资源的最佳猜测URI,另一个用于构建查询域可用性的URI。仍然可以通过分页浏览域集合来确定给定域的URI,但考虑到它管理的大量域名,客户端无法实现这一点,因此为他们提供猜测域资源URI的方法从客户端的角度和服务器的带宽方面来看都是巨大的优势。

回到您的问题:我们的规范文档公开了资源、各种方法对这些资源的影响、使用的表示媒体类型及其模式,以及这些表示中URI指向的资源类型。

我们还包括非规范性(信息性)文档,其中附有免责声明,不要过多解读文档中提到的URI,这些文档提供了典型客户端-服务器交互的示例。这将相当抽象的规范性文档具体化。

1
在您的API中提供URI模板是可以的,但请不要将其称为REST,因为它并不是。这会导致大量耦合,而这正是REST旨在避免的。但正如您所说,REST并不适用于每个应用程序。因此,请不要假装每个应用程序都是REST。 - aehlke
1
实际上,我同意。我相信那就是我说的。然而,我真的看不出为什么要提供 URI 模板外带的好处。 - Keith Gaughan

0
假设调用GET /foos/createForm以获取表单字段值,这些值在我们创建POST /foos时必须提供。现在根据Fielding的建议,应该在GET /foos/createForm的响应中提到用于创建foos的特定URL,作为提交操作链接,对吗?那么将操作映射到众所周知的Http动词的好处是什么,“约定优于代码/配置”的事情被否定了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接