186

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

例如,我有一本漫画书的资源。ComicBook具有各种属性,例如author, issue number,date等。

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

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

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

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

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

4

2 回答 2

68

@ray,精彩的讨论

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

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

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

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

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

本教程提供了一个很好的交叉引用资源示例。 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 表示发布到资源集合中。

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

  • 允许延迟封面创建
  • 允许创建具有所需封面的漫画书
  • 允许交叉引用封面
  • 允许多个封面
  • 创建草稿漫画书
  • 创建漫画书封面草稿
  • 发布草稿漫画书

GET /comic-books
=> 200 OK,获取所有漫画书。

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

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

GET /covers
=> 200 OK,获取所有封面。

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

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

POST /draft-comic-books
title=foo
author=boo
publisher=goo
published=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/盖子)。

POST /draft-comic-books/3/covers
cover_type=front
cover_data=(binary)
=> 302 找到,位置:/draft-comic-books/3/covers,重定向到漫画草稿的新封面 (/draft-comic -书/3/封面/1)。

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

POST /published-comic-books
title=foo
author=boo
publisher=goo
published=2011-01-01
cover_type=front
cover_data=(binary)
=> 302 Found,位置:/comic-books/3,重定向到已出版的漫画书(id: 3) 带盖。

于 2011-09-21T13:18:05.437 回答
49

将封面视为资源绝对符合 REST 的精神,尤其是 HATEOAS。所以是的,一个GET请求http://example.com/comic-books/1将为您提供第 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 上,您可以返回 URIs ..

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

于 2011-08-18T09:09:20.140 回答