资源不同版本的 RESTful URL

Posted

技术标签:

【中文标题】资源不同版本的 RESTful URL【英文标题】:RESTful URLs for distinct versions of a resource 【发布时间】:2013-02-27 22:09:59 【问题描述】:

我很难从概念上理解有关版本化资源 URL 的正确内容。

假设我有一个应用程序,它以与版本控制系统非常相似的方式跟踪食谱,例如 RCS 之类的老派。每个版本都可以是一段时间的工作副本,然后从中创建一个新版本。每个版本都有与之关联的 cmets,并且不共享 cmets。我可以随时回顾历史并查看配方的演变过程,但每个实例始终被视为同一配方的一个版本。我试图找出最合适的方式来构建引用这些 URL 的 URL,但我无法理解子资源和时间资源等之间的一些差异。

我见过的两种主要方式是:

> 1) query parameters
> -- /recipes/ultimate-thing                -> List of available versions of Ultimate Thing
> -- /recipes/ultimate-thing?version=2      -> Version 2 of Ultimate Thing
> -- /recipes/ultimate-thing?version=latest -> Current working version of Ultimate Thing

2a) Nested resources with versions considered subresources
-- /recipes/ultimate-thing/versions/      -> List of available versions of Ultimate Thing
-- /recipes/ultimate-thing/versions/2     -> Version 2 of Ultimate Thing
-- /recipes/ultimate-thing                -> Current working version of Ultimate Thing

2b) Nested resources with the list at the resource
-- /recipes/ultimate-thing                 -> List of available versions of Ultimate Thing
-- /recipes/ultimate-thing/versions/2      -> Version 2 of Ultimate Thing
-- /recipes/ultimate-thing/versions/latest -> Current working version of Ultimate Thing

我觉得每次我试图说服自己采用一种方法时,我都觉得我缺乏理解来为该方法构建适当的理由。

1 似乎是很多 Rails 人的首选,但我不确定如何明确何时要创建新版本的配方,因为 POST 到特定资源 (/ recipes/ultimate-thing) 通常想要创建具有该名称的资源,如果它不存在,是否适合返回类似 404 的内容而不是创建它并且只发布到 /recipes 创建新食谱,然后允许 POST 到 /recipes/ultimate-thing 创建新版本?另外,如何表示 cmets?如果没有版本,我可以执行类似 /recipes/ultimate-thing/cmets 之类的操作,但 /recipes/utlimate-thing/cmets?version=latest 看起来很难看,而 2b 看起来确实如此更明确的“终极事物配方的最新版本的评论”,2a 似乎最干净。

事实上,我通常最喜欢 2a,但我无法确定版本是食谱的子资源,因为没有食谱,版本 食谱,但该食谱的所有版本应该在逻辑上组合在一起。

我还在 *** 上看到了 2b,但它似乎过于冗长,因为您一直都需要像 /recipes/ultimate-thing/version/latest/cmets 这样的东西。尽管如果我将版本视为带有可注释说明的成分集合,而将配方视为具有相似意图的版本集合,那么这确实是最有意义的。

我真的很喜欢 2a,但是我所缺少的方法是否有一些东西使它成为一个坏主意?我是否遗漏了 1 如何工作的东西,这使得它似乎受到很多人的青睐更有意义?

或者总是做像 /recipes/ultimate-thing/latest/recipes/ultimate-thing/2/cmets 这样的事情会更有意义吗?这是否最不明确地表明这些是同一配方的不同修订?

我不确定这是否是一个非常合适的论坛,我只是想讨论一下,这样我就明白为什么一种方法会比另一种更好。

【问题讨论】:

微软提供 API 版本控制官方指南:github.com/Microsoft/api-guidelines/blob/master/… @ChrisMarisic 不幸的是不相关。我没有对我的 API 进行版本控制,我提供了一个 API 来访问已版本控制的资源。 API和模型在版本之间是相同的,但资源的内容会发生变化。从某种意义上说,它完全是一种不同的资源,但从逻辑上讲,它们都是原始资源的顺序变化而相关的。 【参考方案1】:

哇。好问题。我也发现 REST 的最大挑战之一是它是一种风格,而不是严格的规范。这种风格似乎还没有被很好地理解,也没有任何东西可以强制它。

简答:样式 #1:/recipes/cake?version=2

这种风格似乎最适合我。只是在快速谷歌搜索之后,我发现查询参数被认为是 URI 的一部分。因此,在 URI 中放置一个标识属性似乎是合适的。

样式 2 和 3 似乎违反了路径段表示 ID 和子资源的其他 REST 约定。因此,样式 /recipes/cake/2 看起来像 ID 为 2 的“蛋糕”资源。/recipes/cake/version/2 em> 使“版本”看起来是一个子资源,而实际上它是父资源的一个属性。

【讨论】:

虽然这似乎是最合理的解决方案,将其添加为参数,但真正的解决方案应该是使用版本作为资源获取的另一个元数据,类似于 MIME 类型。为什么要通过 Accept header 协商内容,而不是通过另一个 header 协商版本? @DiegoSevilla 我公开承认我的建议是基于遵守惯例的意见。 REST 的许多方面(动词、状态代码)都由 HTTP 规范明确定义,并且无法解释。我没有在规范中看到任何定义 URI 标准的内容(我也只是略过它).....所以我的问题是 - 您建议使用 HTTP 标头(a)是约定还是(b) HTTP 规范建议的东西。 @DiegoSevilla - 只需重新阅读您的问题“为什么通过 Accept 标头协商内容而不通过另一个标头协商版本”。我的回答是 Accept 标头可以确定资源的表示。将版本放在 URI 中标识资源本身。是的,我假设不同的版本在概念上是不同的资源。 似乎使用任何类型的标头来指定将表示哪个版本的资源会破坏通过简单超链接引用任何特定版本的能力。如果我决定我真的很喜欢我尝试过 3 次之前用过的蛋糕食谱,并想把它展示给我妈妈看……标题会让我陷入困境。我是否回应了你的建议? @EJK 我认为这个例子确实有帮助。我认为阻碍我的是我将食谱视为实际的东西,但它们实际上并不存在,除了作为一个版本......实际上它只是一个命名的版本集合,所以当我引用 /recipes/cake 时,它​​是对我的蛋糕食谱版本列表的引用。如果我想要一个特定的蛋糕,我会按 ?version=2 或 ?date=2013-02-01 过滤那天我做蛋糕的时间,这很棒。也就是说,我仍然不是 /recipes/cake/cmets?version=2 的忠实粉丝,但它现在对我来说确实更有意义。我会考虑的。谢谢!【参考方案2】:

我去年年底刚刚实现了这种模式,并遵循了 2a 和 2b 的组合:

2c) Nested resources with versions considered subsets
-- /recipes/ultimate-thing/versions/      -> List of available versions of Ultimate Thing
-- /recipes/ultimate-thing/versions/2     -> Version 2 of Ultimate Thing
-- /recipes/ultimate-thing/versions/latest-> Redirect to current working version of Ultimate Thing

不要将较长的 URI 视为“子资源”或“属性”,就好像 URI 标识了一个事物。将 URI 视为标识数据更为自然,而且,由于 HTTP URI 是分层的,较长的 URI 指的是 数据子集。集合中的项目是数据:集合中所有数据的子集。 “事物”的“属性”是数据:关于该事物的所有数据的子集。 “子资源”是关于“事物”的数据,它不独立于其父级而存在,因此是有关父级的所有数据的子集。

在您的情况下,您不必进行过多的心理柔道,即可将recipes/cherry-pie/ 视为有关樱桃派是什么以及如何制作的所有数据,而不是特定版本的说明。这包括身份信息和其他非特定于版本的数据,还包括指向versions/ 的链接,以及指向某人的历史和有趣视频的链接。 URI recipes/cherry-pie/versions/ 是所有樱桃派数据的子集,它是版本化的;一般来说,它只不过是指向recipes/cherry-pie/versions/version/ 形式的每个URI 的链接集合。我还将在versions/ 中包含一个到recipes/cherry-pie/versions/latest/ 的文档链接,它会临时重定向到recipes/cherry-pie/versions/182/ 或任何最新版本。

这对客户来说非常自然。许多人在他们认为资源爆炸的情况下绊倒,并认为可怜的客户可能跟不上。但是,如果您按照自然用户工作流边界划分资源,则不会有问题。一些客户会想要浏览一组食谱;有些会深入到单个配方(无论版本如何);有些人会向下钻取,想知道有多少个版本,哪个是最新的,并从中进行选择;有些会向下钻取并检索单个版本。如果您想显示单个配方或所有配方的所有版本,您可能做错了什么(在极少数情况下您不是,请设计另一个资源,例如 recipes/byname/pie/quickview,它收集所有数据和大量缓存)。

【讨论】:

这似乎是一种非常有趣的 REST API 思考方式。我需要考虑一下我与@EJK 进行的其他讨论。许多处理版本化模型的框架会建议 Style #1,其中每个版本都是带有版本 ID 字段的整个模型,但从逻辑上讲,我认为食谱是一个实体,它收集成分和说明列表的版本和跟踪历史,因为它们是精炼的,因为这就是它在版本控制或 rolodex 中的工作方式。 我使用类似的方案,但我有最新版本的“资源”、特定版本的资源/时间戳和版本历史记录的资源/版本。参见例如***.com/questions/60173782/…【参考方案3】:

这完全取决于您如何看待资源版本。这些是否仅用于 wiki 中的历史跟踪,或者版本是单独的资源。在第一种情况下,EJK answer 似乎是一个很好的解决方案。对于第二种情况,为资源版本/变体创建新的唯一标识符可能是另一种解决方案。 例如:

3) Resources with versions
-- /recipes/                          -> List of available recipes versions
-- /recipes/ultimate-thing-version-2  -> Version 2 of Ultimate Thing
-- /recipes/ultimate-thing            -> The "offical" version of Ultimate Thing

如果资源是彼此的变体,这是更优选的解决方案:

3) List of recipes
-- /recipes/                           -> List of available recipes and variations
-- /recipes/ultimate-thing-with-sugar  -> The Ultimate Thing recipe with sugar
-- /recipes/ultimate-thing-with-honey  -> The Ultimate Thing recipe with honey

【讨论】:

【参考方案4】:

我有一个用例,其中每个版本的创建时间 至关重要。 API 必须回答的常见问题有:

    /recipes/ultimate-thing 的最新版本是什么? /recipes/ultimate-thing 的最新版本是 2019-01-01 的哪个版本? /recipes/ultimate-thing 的哪些版本是在 2019 年 1 月 1 日到 2019 年 1 月 31 日之间创建的?

编号 3. 在我看来是查询参数的明显案例:

-- /recipes/ultimate-thing?versions_since=2019-01-01&versions_until=2019-01-31

对 2 使用相同的方法可能不会有什么坏处。:

-- /recipes/ultimate-thing?version=2019-01-01

那么让我们保持一致,对 1. 使用相同的方法:

-- /recipes/ultimate-thing?version=latest

从这个角度来看,您的选项 1) - 使用查询参数 - 似乎是最自然的。

【讨论】:

嗯,这对我来说有点奇怪。除非每天只能创建一个版本,否则您将如何在某一天深入到较早的版本?包括带有日期的时间戳是可行的,但它是一个非常严格和复杂的标识符。

以上是关于资源不同版本的 RESTful URL的主要内容,如果未能解决你的问题,请参考以下文章

restful 规范

RESTful理解

restful十条规范

RESTful规范和DRF

RESTful

REST&RESTFUL?