问题描述
我正在努力寻找解决基于 REST 的 API 中的概念的最佳方法.不包含其他资源的平面资源没有问题.我遇到麻烦的是复杂的资源.
I'm trying to wrap my head around the best way to address concepts in a REST based API. Flat resources that don't contain other resources are no problem. Where I'm running into trouble are the complex resources.
例如,我有一本漫画书的资源.ComicBook
上有各种属性,如author
、issue number
、date
等.
For instance, I have a resource for a comic book. ComicBook
has all sorts of properties on it like author
, issue number
, date
, etc.
一本漫画书也有一个 1..n
封面列表.这些封面是复杂的对象.它们包含许多关于封面的信息:艺术家、日期,甚至是封面的 Base 64 编码图像.
A comic book also has a list of 1..n
covers. These covers are complex objects. They contain a lot of information about the cover: the artist, a date, and even a base 64 encoded image of the cover.
对于 ComicBook
上的 GET
,我可以返回漫画和所有封面,包括它们的 base64 图像.对于获得一部漫画来说,这可能不是什么大问题.但是假设我正在构建一个客户端应用程序,想要在一个表中列出系统中的所有漫画.
该表格将包含来自 ComicBook
资源的一些属性,但我们当然不想显示表格中的所有封面.返回 1000 本漫画书,每本漫画书都有多个封面,这将导致大量数据通过网络传输,在这种情况下,最终用户不需要这些数据.
For a GET
on ComicBook
I could just return the comic, and all of the covers including their base64'ed images. That's probably not a big deal for getting a single comic. But suppose I am building a client app that wants to list all of the comics in the system in a table.
The table will contain a few properties from the ComicBook
resource, but we're certainly not going to want to display all the covers in the table. Returning 1000 comic books, each with multiple covers would result in a ridiculously large amount of data coming across the wire, data that isn't necessary to the end user in that case.
我的直觉是让 Cover
成为资源并让 ComicBook
包含封面.所以现在 Cover
是一个 URI.漫画书上的 GET
现在可以使用了,而不是巨大的 Cover
资源,我们为每个封面发回一个 URI,客户可以根据需要检索 Cover 资源.
My instinct is to make Cover
a resource and have ComicBook
contain covers. So now Cover
is a URI. GET
on comic book works now, instead of the huge Cover
resource we send back a URI for each cover and clients can retrieve the Cover resources as they require them.
现在我在创作新漫画时遇到了问题.当然,当我创建Comic
时,我至少要创建一个封面,实际上这可能是一项业务规则.
所以现在我被卡住了,我要么通过首先提交一个 Cover
,获取该封面的 URI,然后 POST
ing 一个 来强制客户执行业务规则ComicBook
在列表中带有该 URI,或者我在 ComicBook
上的 POST
接受与它吐出的外观不同的资源.POST
和 GET
的传入资源是深层副本,其中传出的 GET
包含对依赖资源的引用.
Now I have a problem with creating new comics. Surely I'm going to want to create at least one cover when I create a Comic
, in fact that's probably a business rule.
So now I'm stuck, I either force the clients to enforce business rules by first submitting a Cover
, getting the URI for that cover, then POST
ing a ComicBook
with that URI in the list, or my POST
on ComicBook
takes in a different looking resource than it spits out. The incoming resources for POST
and GET
are deep copies, where the outgoing GET
s contain references to dependent resources.
Cover
资源在任何情况下都可能是必要的,因为我确定作为客户,我希望在某些情况下解决封面方向.所以无论依赖资源的大小如何,问题都以一般形式存在.一般来说,您如何处理复杂的资源而不强迫客户知道"这些资源是如何组成的?
The Cover
resource is probably necessary in any case because I'm sure as a client I'd want to address covers direction in some cases. So the problem exists in a general form regardless of the size of the dependent resource. In general how do you handle complex resources without forcing the client to just "know" how those resources are composed?
推荐答案
@ray,精彩讨论
@jgerman,不要忘记,仅仅因为它是 REST,并不意味着资源必须从 POST 开始.
@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.
但是,如果您将电子邮件视为资源,而将发件人地址视为子资源,显然您仍然可以单独引用发件人地址.例如,从地址中获取所有内容.或者,使用以前的发件人地址创建新邮件.如果电子邮件是 REST,您可以很容易地看到许多交叉引用的资源可用:/received-messages、/draft-messages、/from-addresses、/to-addresses、/addresses、/subjects、/attachments、/folders、/tags、/categories、/labels 等
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.
本教程提供了一个很好的交叉引用资源示例.http://www.peej.co.uk/articles/restfully-delicious.html
This tutorial provides a great example of cross-referenced resources.http://www.peej.co.uk/articles/restfully-delicious.html
这是自动生成数据的最常见模式.例如,您不要发布新资源的 URI、ID 或创建日期,因为这些是由服务器生成的.但是,您可以在取回新资源时检索 URI、ID 或创建日期.
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.
您的二进制数据示例.例如,您希望将二进制数据作为子资源发布.当您获得父资源时,您可以将这些子资源表示为相同的二进制数据,或者表示为表示二进制数据的 URI.
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.
表格和参数已经不同于资源的 HTML 表示.发布一个生成 URL 的二进制/文件参数并不是一件容易的事.
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.
当您获取新资源的表单 (/comic-books/new) 或获取编辑资源的表单 (/comic-books/0/edit) 时,您要求的是特定于表单的表示资源.如果您将其发布到内容类型为application/x-www-form-urlencoded"或multipart/form-data"的资源集合,则您要求服务器保存该类型表示.服务器可以使用已保存的 HTML 表示或其他形式进行响应.
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.
出于 API 或类似目的,您可能还希望允许将 HTML、XML 或 JSON 表示发布到资源集合.
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.
- 允许延迟创建封面
- 允许创建带有所需封面的漫画书
- 允许交叉引用封面
- 允许多个封面
- 创建漫画书草稿
- 创建漫画书封面草稿
- 发布漫画草稿
获取/漫画书
=> 200 OK,获取所有漫画书.
GET /comic-books
=> 200 OK, Get all comic books.
GET/comic-books/0
=> 200 OK, 获取带有封面 (/covers/1,/covers/2) 的漫画书 (id: 0).
GET /comic-books/0
=> 200 OK, Get comic book (id: 0) with covers (/covers/1, /covers/2).
GET/comic-books/0/covers
=> 200 OK,获取漫画书封面(id:0).
GET /comic-books/0/covers
=> 200 OK, Get covers for comic book (id: 0).
获取/covers
=> 200 OK,获取所有封面.
GET /covers
=> 200 OK, Get all covers.
获取/covers/1
=> 200 OK,用漫画书 (/comic-books/0) 获取封面 (id: 1).
GET /covers/1
=> 200 OK, Get cover (id: 1) with comic book (/comic-books/0).
GET/comic-books/new
=> 200 OK,获取创建漫画书的表单(表单:POST/draft-comic-books).
GET /comic-books/new
=> 200 OK, Get form to create comic book (form: POST /draft-comic-books).
POST/draft-comic-books
标题=foo
作者=嘘
出版商=goo
已发布=2011-01-01
=> 302 Found,位置:/draft-comic-books/3,重定向到带有封面(二进制)的草稿漫画书(id:3).
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).
获取/draft-comic-books/3
=> 200 OK,获取带有封面的漫画草稿(ID:3).
GET /draft-comic-books/3
=> 200 OK, Get draft comic book (id: 3) with covers.
获取/draft-comic-books/3/covers
=> 200 OK,获取草稿漫画书的封面 (/draft-comic-book/3).
GET /draft-comic-books/3/covers
=> 200 OK, Get covers for draft comic book (/draft-comic-book/3).
获取/draft-comic-books/3/covers/new
=> 200 OK,获取为草稿漫画书 (/draft-comic-book/3) 创建封面的表单(表单:POST/draft-comic-books/3/covers).
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_data=(二进制)
=> 302 发现,位置:/draft-comic-books/3/covers,重定向到草稿漫画书的新封面 (/draft-comic-book/3/covers/1).
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).
获取/draft-comic-books/3/publish
=> 200 OK,获取发布漫画草稿的表单(id:3)(表单:POST/published-comic-books).
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
标题=foo
作者=嘘
出版商=goo
已发布=2011-01-01
封面类型=正面
cover_data=(二进制)
=> 302 Found, Location:/comic-books/3, 重定向到已出版的漫画书 (id: 3) 和封面.
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.
这篇关于REST 复杂/复合/嵌套资源的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!