REST 复合体/复合体/嵌套资源

我正在试图理解在基于 REST 的 API 中处理概念的最佳方法。不包含其他资源的平面资源没有问题。我遇到麻烦的地方是复杂的资源。

例如,我有一本漫画书的资源。ComicBook具有 authorissue numberdate等各种属性。

一本漫画书也有一系列 1..n封面。这些封面是复杂的物体。它们包含了很多关于封面的信息: 艺术家,日期,甚至是封面的64进制编码图像。

对于 ComicBook上的 GET,我可以只退回漫画,和所有的封面,包括他们的基础64’的图像。这可能不是什么大不了的获得一个单一的漫画。但是假设我正在构建一个客户端应用程序,它希望在一个表中列出系统中的所有漫画。
该表将包含来自 ComicBook资源的一些属性,但是我们当然不希望显示表中的所有封面。返回1000本漫画书,每一本都有多个封面,将导致大量数据通过网络传输,而这些数据对于最终用户来说是不必要的。

我的直觉是使 Cover资源,并有 ComicBook包含涵盖。所以现在 Cover是一个 URI。GET漫画书工程现在,而不是巨大的 Cover资源,我们发送回一个 URI 为每个封面和客户端可以检索封面资源,因为他们需要他们。

现在我在创作新漫画方面遇到了问题。当然,我希望在创建 Comic时至少创建一个封面,事实上,这可能是一个业务规则。
所以现在我被困住了,我要么强迫客户强制执行业务规则,首先提交一个 Cover,获得该封面的 URI,然后 POSTing 一个包含该 URI 在列表中的 ComicBook,或者我的 POSTComicBook上接受一个不同的外观资源,而不是它吐出来。POSTGET的传入资源是深拷贝,其中传出的 GET包含对依赖资源的引用。

在任何情况下,Cover资源可能都是必要的,因为作为一个客户,我确信在某些情况下我希望解决覆盖方向的问题。因此,无论依赖资源的大小如何,问题都以一般形式存在。一般来说,如何处理复杂的资源而不强迫客户端“知道”这些资源是如何组成的?

55748 次浏览

将封面作为资源处理肯定符合 REST 的精神,特别是 HATEOAS。因此,对 http://example.com/comic-books/1GET请求将为您提供图书1的表示形式,其属性包括一组用于封面的 URI。目前为止还不错。

你的问题是如何处理漫画创作。如果你的商业规则是一本书应该有 0或更多封面,那么你就没有问题:

POST http://example.com/comic-books

使用无封面漫画数据将创建一个新的漫画书,并返回服务器生成的 id (让我们假设它返回为8) ,现在你可以添加封面到它像这样:

POST http://example.com/comic-books/8/covers

与实体机构的封面。

现在您有一个很好的问题,那就是如果您的业务规则说总是必须至少有一个封面,那么会发生什么情况。下面是一些选择,第一个是你在问题中提到的:

  1. 首先强制创建一个封面,现在基本上使封面成为一个非依赖性资源,或者您将初始封面放置在创建漫画书的 POST 的实体主体中。正如您所说的,这意味着您 POST 创建的表示将不同于您 GET 的表示。

  2. 定义一个主要的、初始的、首选的或其他指定的封面的概念。这可能是一种建模技巧,如果您这样做了,就像是为了适应某种技术而调整您的对象模型(您的概念模型或业务模型)。不是个好主意。

你应该权衡一下这两种选择和简单地允许没有封面的漫画。

这三个选择中,你应该选择哪一个?不太了解你的情况,但回答将军1。.不依赖资源的问题,我会说:

  • 如果你可以选择0。.N 代表 RESTful 服务层,很好。如果至少需要一个业务约束,那么 RESTful SOA 之间的一个层可以处理进一步的业务约束。(不确定这看起来如何,但可能值得探索... ..。最终用户通常不会看到 SOA。)

  • 如果您只是必须建模一个1。.然后问问自己封面是否只是可共享的资源,换句话说,它们可能存在于漫画书之外的东西上。现在它们不依赖于资源,您可以先创建它们,然后在您的 POST 中提供 URI 来创建漫画书。

  • 如果你需要1。.N 和封面保持依赖,只要放松你的本能,保持在 POST 和获得相同的表示,或使他们相同。

最后一项是这样解释的:

<comic-book>
<name>...</name>
<edition>...</edition>
<cover-image>...BASE64...</cover-image>
<cover-image>...BASE64...</cover-image>
<cover>...URI...</cover>
<cover>...URI...</cover>
</comic-book>

当您发布您允许现有的尿路感染,如果你有他们(借自其他书籍) ,但也放在一个或多个初始图像。如果您正在创建一本书,而您的实体没有初始封面图像,则返回409或类似的响应。在 GET 上可以返回 URI。.

所以基本上您允许 POST 和 GET 表示“是相同的”,但是您只是选择不在 GET 上“使用”封面图像,也不在 POST 上使用封面图像。希望你能理解。

@ Ray 很棒的讨论

不要忘记,仅仅因为它是 REST,并不意味着资源必须从 POST 中一成不变地设置。

您选择在资源的任何给定表示中包含哪些内容取决于您自己。

单独引用封面的情况仅仅是创建一个父资源(漫画书) ,其子资源(封面)可以交叉引用。例如,您可能还希望分别提供对作者、出版商、字符或类别的引用。您可能希望创建这些资源单独或之前的漫画书引用他们作为儿童资源。或者,您可能希望在创建父资源时创建新的子资源。

您的具体情况的封面是稍微复杂的,因为封面确实需要一本漫画书,反之亦然。

但是,如果将电子邮件消息视为资源,将 from 地址视为子资源,显然仍然可以分别引用 from 地址。例如,从地址获取所有信息。或者,使用前一个 from 地址创建新邮件。如果电子邮件是 REST,您可以很容易地看到许多相互参照的资源可用:/收到的消息,/草稿消息,/从-地址,/到-地址,/地址,/主题,/附件,/文件夹,/标签,/类别,/标签,等等。

本教程提供了交叉引用资源的一个很好的例子。 Http://www.peej.co.uk/articles/restfully-delicious.html

这是自动生成数据的最常见模式。例如,您不发布新资源的 URI、 ID 或创建日期,因为这些都是由服务器生成的。但是,在返回新资源时,您可以检索 URI、 ID 或创建日期。

二进制数据的示例。例如,您希望将二进制数据作为子资源发布。当您获得父资源时,您可以将这些子资源表示为相同的二进制数据,或者表示为二进制数据的 URI。

表单和参数已经不同于资源的 HTML 表示。发布一个导致 URL 的二进制/文件参数并不是一件牵强的事情。

当您获得一个新资源的表单(/漫画书/新资源) ,或者获得编辑资源的表单(/漫画书/0/编辑)时,您要求获得特定于表单的资源表示形式。如果您使用内容类型“ application/x-www-form-urlencode”或“ multipart/form-data”将其发布到资源集合中,那么就是要求服务器保存该类型表示。服务器可以通过保存的 HTML 表示或其他方式进行响应。

出于 API 或类似目的,您可能还希望允许将 HTML、 XML 或 JSON 表示发布到资源集合中。

它也可以表示您的资源和工作流程,如您所述,考虑到封面后发布的漫画书,但要求漫画书有一个封面。例子如下。

  • 允许延迟封面创建
  • 允许漫画书创作与要求的封面
  • 允许交叉引用封面
  • 可以有多个掩护
  • 创作草稿漫画书
  • 创建草稿漫画书封面
  • 出版漫画草稿

获取/漫画书
= > 200 OK,把所有的漫画书都拿来。

获取/漫画书/0
= > 200 OK,买一本有封面的漫画书(id: 0)。

获取/漫画书/0/封面
= > 200 OK,找到漫画书的封面(id: 0)。

获取/覆盖
= > 200好的,把所有东西都盖上。

获取/覆盖/1
= > 200 OK,用漫画书(/漫画书/0)做封面(id: 1)。

获取/漫画书/新
= > 200 OK,获取创建漫画书的格式(格式: POST/草稿漫画书)。

后期/草稿-漫画书
Title = foo
作者 = Boo
出版商 = 黏糊糊的
Published = 2011-01-01出版 = 2011-01-01
= > 302发现,位置:/草稿-漫画书/3,重定向到草稿漫画书(id: 3)封面(二进制)。

获取/草稿-漫画书/3
= > 200好吧,拿封面的连环画草稿(id: 3)。

获取/草稿-漫画书/3/封面
= > 200 OK,找到漫画草稿的封面。

获得/草稿-漫画书/3/封面/新
= > 200 OK,获取形式为草稿漫画创建封面(/草稿漫画书/3)(形式: POST/草稿漫画书/3/封面)。

后/草稿-漫画书/3/封面
盖子 = 前面
Cover _ data = (二进制)
= > 302发现,地点:/初稿-漫画书/3/封面,重定向到新的封面为初稿漫画书(/初稿-漫画书/3/封面/1)。

获取/草稿-漫画书/3/出版
= > 200 OK,获得出版草稿漫画书的格式(id: 3)(格式: POST/已出版漫画书)。

POST/出版的漫画书
Title = foo
作者 = Boo
出版商 = 黏糊糊的
Published = 2011-01-01出版 = 2011-01-01
盖子 = 前面
Cover _ data = (二进制)
= > 302 Found,Location:/comicalbooks/3,Redirect to publications comicalbook (id: 3) with cover.