【问题标题】:REST Complex/Composite/Nested Resources [closed]REST 复杂/复合/嵌套资源 [关闭]
【发布时间】:2011-10-29 14:15:56
【问题描述】:

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

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

一本漫画书也有一个1..n 封面列表。这些封面是复杂的对象。它们包含很多关于封面的信息:艺术家、日期,甚至是封面的 base 64 编码图像。

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

我的直觉是让Cover 成为资源并让ComicBook 包含封面。所以现在Cover 是一个URI。 GET 现在可以在漫画书上使用,而不是巨大的 Cover 资源,我们为每个封面发送回一个 URI,客户可以根据需要检索封面资源。

现在我在创作新漫画时遇到了问题。当我创建 Comic 时,我肯定会想要创建至少一个封面,事实上这可能是一个业务规则。
所以现在我被困住了,我要么通过首先提交Cover,获取该封面的URI,然后POST在列表中带有该URI的ComicBook来强制客户执行业务规则,要么我的@ ComicBook 上的 987654340@ 接收的资源与它吐出的资源不同。 POSTGET 的传入资源是深层副本,其中传出 GETs 包含对依赖资源的引用。

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

【问题讨论】:

标签: api rest resources nested-resources


【解决方案1】:

@ray,精彩的讨论

@jgerman,不要忘记,仅仅因为它是 REST,并不意味着资源必须从 POST 中固定下来。

您选择在任何给定的资源表示中包含什么内容取决于您。

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

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

但是,如果您将电子邮件消息视为一种资源,而将发件人地址视为子资源,您显然仍然可以单独引用发件人地址。例如,从地址中获取所有信息。或者,使用以前的发件人地址创建一条新消息。如果电子邮件是 REST,您可以很容易地看到可以使用许多交叉引用的资源:/received-messages、/draft-messages、/from-addresses、/to-addresses、/addresses、/subjects、/attachments、/folders 、/tags、/categories、/labels 等。

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

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

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

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

当您获取新资源的表单 (/comic-books/new) 或获取编辑资源的表单 (/comic-books/0/edit) 时,您要求的是特定于表单的表示资源。如果您将其发布到内容类型为“application/x-www-form-urlencoded”或“multipart/form-data”的资源集合,则您要求服务器保存该类型表示。服务器可以使用已保存的 HTML 表示或其他内容进行响应。

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

还可以按照您的描述来表示您的资源和工作流程,考虑到在漫画书之后发布的封面,但要求漫画书有封面。示例如下。

  • 允许延迟创建封面
  • 允许创建带有所需封面的漫画书
  • 允许交叉引用封面
  • 允许多个封面
  • 制作漫画草稿
  • 制作漫画书封面草稿
  • 出版漫画草稿

获取/漫画书
=> 200 OK,获取所有漫画书。

获取 /comic-books/0
=> 200 OK,获取带有封面(/covers/1,/covers/2)的漫画书(id:0)。

GET /comic-books/0/covers
=> 200 OK,获取漫画书的封面(id:0)。

获取/封面
=> 200 OK,得到所有的封面。

获取 /covers/1
=> 200 OK,用漫画书 (/comic-books/0) 获取封面 (id: 1)。

获取 /comic-books/new
=> 200 OK,获取创建漫画书的表单(表单:POST /draft-comic-books)。

发布 /draft-comic-books
标题=foo
作者=嘘
发布者=goo
已发布=2011-01-01
=> 302 找到,位置:/draft-comic-books/3,重定向到带有封面(二进制)的草稿漫画书(id:3)。

GET /draft-comic-books/3
=> 200 OK,获取带有封面的草稿漫画书(id:3)。

GET /draft-comic-books/3/covers
=> 200 OK,获取草稿漫画的封面 (/draft-comic-book/3)。

GET /draft-comic-books/3/covers/new
=> 200 OK,获取表单为草稿漫画书 (/draft-comic-book/3) 创建封面(表单:POST /draft-comic-books/3/covers)。

发布 /draft-comic-books/3/covers
封面类型=正面
封面数据=(二进制)
=> 302 找到,位置:/draft-comic-books/3/covers,重定向到草稿漫画书的新封面 (/draft-comic-book/3/covers/1)。

GET /draft-comic-books/3/publish
=> 200 OK,获取发布漫画草稿的表单(id:3)(表单:POST /published-comic-books)。

发布/已出版的漫画书
标题=foo
作者=嘘
发布者=goo
已发布=2011-01-01
封面类型=正面
封面数据=(二进制)
=> 302 找到,位置:/comic-books/3,重定向到已出版的漫画书(id:3),带有封面。

【讨论】:

  • 我是这方面的新手,并试图快速学习它。我发现这非常有帮助。但是,在我今天阅读的其他博客等中,使用 GET 来执行操作(特别是非幂等操作)将不受欢迎。所以不应该是 POST /draft-comic-books/3/publish 吗?
  • @GaryMcGill 在他的示例中,/draft-comic-books/3/publish 仅返回 HTML 表单(不修改任何数据)。
  • @Olivier 是正确的。发布这个词是用来表示表单的作用。但是,因为您希望将动词限制在 HTTP 方法中,所以您应该发布到已出版漫画书的资源。 ...如果这是一个网站,您可能需要一个 URI 来让表单发布某些内容。 ...虽然,如果发布操作只是漫画书页面上的一个按钮,那么该单按钮表单可以直接发布到 /published-comic-books URI。
  • @Alex,在 POST 请求中,我将改为返回 201 Created,并将新资源的 URL 作为响应标头中的 Location。
  • @Stephane,重定向只是让控制器的一切变得更简单。即使对于 API,让创建控制器返回新内容的位置,然后让显示控制器处理新内容的显示也更简单。虽然,对于 API 的客户端来说,只获取内容而不需要重定向会更好/更简单。
【解决方案2】:

将封面视为资源绝对符合 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 的实体正文中。正如您所说,这意味着您发布创建的表示将不同于您获取的表示。

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

您应该权衡这两个选择与简单地允许无封面漫画。

您应该选择三个选项中的哪一个?不太了解您的情况,但回答一般 1..N 依赖资源问题,我会说:

  • 如果您的 RESTful 服务层可以使用 0..N,那就太好了。如果至少需要一个,那么您的 RESTful SOA 之间的一层也许可以处理进一步的业务约束。 (不知道它看起来如何,但可能值得探索......最终用户通常不会看到 SOA。)

  • 如果您只是必须为 1..N 约束建模,那么问问自己封面是否只是可共享的资源,换句话说,它们可能存在于漫画书以外的东西上。现在它们不再是依赖资源,您可以先创建它们并在创建漫画书的 POST 中提供 URI。

  • 如果您需要 1..N 并且封面仍然依赖,只需放松本能,保持 POST 和 GET 中的表示相同,或者使它们相同。

最后一项解释如下:

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

当您发布时,如果您有现有的 uris(从其他书籍借来),则您允许它们,但也放入一个或多个初始图像。如果您正在创建一本书并且您的实体没有初始封面图片,请返回 409 或类似响应。在 GET 上,您可以返回 URI。

因此,基本上您允许 POST 和 GET 表示“相同”,但您只是选择不在 GET 上“使用”封面图像,也不在 POST 上“使用”封面。希望这是有道理的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-11
    • 1970-01-01
    • 2023-03-18
    • 2019-12-23
    • 2013-07-25
    • 2014-12-15
    • 2021-09-02
    相关资源
    最近更新 更多