REST 复杂/复合/嵌套资源

发布于 2024-11-30 05:19:56 字数 1436 浏览 1 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

萌辣 2024-12-07 05:19:56

@ray,精彩的讨论

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

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

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

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

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

本教程提供了交叉引用资源的一个很好的示例。
这个

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

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

表格和参数已经不同于资源的 HTML 表示形式。发布一个生成 URL 的二进制/文件参数并不困难。

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

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

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

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

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

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

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

获取/封面
=> 200 好的,获取所有封面。

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

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

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

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

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

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

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

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

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

@ray, excellent discussion

@jgerman, don't forget that just because it's REST, doesn't mean resources have to be set in stone from POST.

What you choose to include in any given representation of a resource is up to you.

Your case of the the covers referenced separately is merely the creation of a parent resource (comic book) whose child resources (covers) may be cross-referenced. For example, you might also wish to provide references to authors, publishers , characters, or categories separately. You may wish to create these resources separately or before the comic book which references them as child resources. Alternatively, you may wish to create new child resources upon creation of the parent resource.

Your specific case of the covers is slightly more complex in that a cover really does require a comic book, and visa versa.

However, if you consider an email message as a resource, and the from address as a child resource, you can obviously still reference the from address separately. For example, get all from addresses. Or, create a new message with a previous from address. If email was REST, you could easily see that many cross-referenced resources could be available: /received-messages, /draft-messages, /from-addresses, /to-addresses, /addresses, /subjects, /attachments, /folders, /tags, /categories, /labels, et al.

This tutorial provides a great example of cross-referenced resources.
http://www.peej.co.uk/articles/restfully-delicious.html

This is the most common pattern for automatically-generated data. For example, you don't post a URI, ID, or creation date for the new resource, as these are generated by the server. And yet, you can retrieve the URI, ID, or creation date when you get the new resource back.

An example in your case of binary data. For example, you want to post binary data as child resources. When you get the parent resource you can represent those child resources as the same binary data, or as URIs which represent the binary data.

Forms & parameters are already different than the HTML representations of the resources. Posting a binary/file parameter which results in a URL isn't a stretch.

When you get the form for a new resource (/comic-books/new), or get the form to edit a resource (/comic-books/0/edit), you are asking for a forms-specific representation of the resource. If you post it to the resource collection with content-type "application/x-www-form-urlencoded" or "multipart/form-data", you are asking the server to save that type representation. The server can respond with the HTML representation which was saved, or whatever.

You may want to also allow for an HTML, XML, or JSON represention to be posted to the resource collection, for purposes of an API or similar.

It is also possible to represent your resources and workflow as you describe, taking into account covers posted after the comic book, but requiring comic books to have a cover. Example as follows.

  • Allows delayed cover creation
  • Allows comic book creation with required cover
  • Allows covers to be cross-referenced
  • Allows multiple covers
  • Create draft comic book
  • Create draft comic book covers
  • Publish draft comic book

GET /comic-books
=> 200 OK, Get all comic books.

GET /comic-books/0
=> 200 OK, Get comic book (id: 0) with covers (/covers/1, /covers/2).

GET /comic-books/0/covers
=> 200 OK, Get covers for comic book (id: 0).

GET /covers
=> 200 OK, Get all covers.

GET /covers/1
=> 200 OK, Get cover (id: 1) with comic book (/comic-books/0).

GET /comic-books/new
=> 200 OK, Get form to create comic book (form: POST /draft-comic-books).

POST /draft-comic-books
title=foo
author=boo
publisher=goo
published=2011-01-01
=> 302 Found, Location: /draft-comic-books/3, Redirect to draft comic book (id: 3) with covers (binary).

GET /draft-comic-books/3
=> 200 OK, Get draft comic book (id: 3) with covers.

GET /draft-comic-books/3/covers
=> 200 OK, Get covers for draft comic book (/draft-comic-book/3).

GET /draft-comic-books/3/covers/new
=> 200 OK, Get form to create cover for draft comic book (/draft-comic-book/3) (form: POST /draft-comic-books/3/covers).

POST /draft-comic-books/3/covers
cover_type=front
cover_data=(binary)
=> 302 Found, Location: /draft-comic-books/3/covers, Redirect to new cover for draft comic book (/draft-comic-book/3/covers/1).

GET /draft-comic-books/3/publish
=> 200 OK, Get form to publish draft comic book (id: 3) (form: 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, Location: /comic-books/3, Redirect to published comic book (id: 3) with covers.

并安 2024-12-07 05:19:56

将封面视为资源绝对符合 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..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>

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

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

Treating covers as resources is definitely in the spirit of REST, particularly HATEOAS. So yes, a GET request to http://example.com/comic-books/1 would give you a representation of book 1, with properties including a set of URIs for covers. So far so good.

Your question is how to deal with comic book creation. If your business rule was that a book would have 0 or more covers, then you have no problem:

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

with coverless comic book data will create a new comic book and return the server generated id (lets say it comes back as 8), and now you can add covers to it like so:

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

with the cover in the entity body.

Now you have a good question which is what happens if your business rule says there always must be at least one cover. Here are some choices, the first of which you identified in your question:

  1. Force the creation of a cover first, now essentially making cover a non-dependent resource, or you place the initial cover in the entity body of the POST that creates the comic book. This as you say means that the representation you POST to create will differ from the representation you GET.

  2. Define the notion of a primary, or initial, or preferred, or otherwise-designated cover. This is likely a modeling hack, and if you did that it would be like tweaking your object model (your conceptual or business model) in order to fit a technology. Not a great idea.

You should weigh these two choices against simply allowing coverless comics.

Which of the three choices should you take? Not knowing too much about your situation, but answer the general 1..N dependent resource question, I would say:

  • If you can go with 0..N for your RESTful service layer, great. Perhaps a layer between your RESTful SOA can handle the further business constraint if at least one is required. (Not sure how that would look but it might be worth exploring.... end users don't usually see the SOA anyway.)

  • If you simply must model a 1..N constraint, then ask yourself whether covers might just be sharable resources, in other words, they might exist on things other than comic books. Now they are not dependent resources and you can create them first and supply URIs in your POST that creates comic books.

  • If you need 1..N and covers remain dependent, simply relax your instinct to keep the representations in POST and GET the same, or make them the same.

The last item is explained like so:

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

When you POST you allow existing uris if you have them (borrowed from other books) but also put in one or more initial images. If you are creating a book and your entity has no initial cover-image, return a 409 or similar response. On GET you can return URIs..

So basically you're allowing the POST and GET representations to "be the same" but you just choose not to "use" cover-image on GET nor cover on POST. Hope that makes sense.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文