2011-08-18 117 views
154

我试图围绕解决基于REST的API中的概念的最佳方式。不包含其他资源的平坦资源不成问题。我陷入困境的是复杂的资源。REST复杂/复合/嵌套资源

例如我有一个ComicBook的资源。 ComicBook拥有各种各样的属性,如作者,发行编号,日期等。

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

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

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

现在我遇到了创建新漫画的问题。当我创作漫画时,我打算创作至少一张封面,实际上这可能是一种商业规则。所以现在我被卡住了,我要么迫使客户强制执行业务规则,首先提交封面,获取封面的URI,然后在列表中发布带有该URI的漫画书,或者我的ComicBook上的POST采用不同的外观资源比它吐出来的要多。 POST和GET的传入资源是深度副本,其中传出GET包含对从属资源的引用。

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

+0

确实使用[RESTFUL SERVICE DISCOVERY](http://barelyenough.org/blog/2008/01/restful-service-discovery-and-description/)吗? – treecoder

+1

我试图坚持HATEAOS,在我看来,这与使用类似的东西背道而驰,但我会看看。 – jgerman

+0

同样精神的不同问题。然而,所有权与您提出的解决方案(问题中的问题)不同。 http://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources – Wes

回答

37

作为资源处理封面绝对是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..1依赖资源的问题,我会说:

  • 如果你能0..N去为你的RESTful服务层,巨大的。如果至少需要一个,那么您的RESTful SOA之间的一层可能会处理进一步的业务约束。 (不知道如何会看,但它可能是值得探讨的问题....最终用户通常不会看到SOA反正。)

  • 如果你只是必须建模1..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> 

当你发布你允许现有的URI,如果你让他们(从其他书借来的),但也存在一个或多个初始图像。如果您正在创建一本书并且您的实体没有初始封面图片,请返回409或类似的回复。在让你可以返回的URI ..

所以基本上你允许POST和GET交涉“是一样的”,但你只是选择不“用”上GET封面,形象,也不是覆盖在POST。希望这是有道理的。

58

@ray,精彩讨论

@jgerman,不要忘了,只是因为它的休息,并不意味着资源必须在石头从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表示发布到资源集合。

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

  • 允许延迟封面创作
  • 允许漫画创作与需要盖
  • 允许盖是交叉引用
  • 允许多个井盖
  • 创建漫画书草案
  • 创建漫画书草案封面
  • 发布漫画书

GET /漫画书
=> 200好的,获取所有漫画书。

GET /漫画书/ 0
=> 200行,带盖(/ cover/1,/ covers/2)获取漫画书(id:0)。

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

GET/covers
=> 200 OK,获取全部封面。

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

GET /漫画书/新
=> 200确定,获取表单以创建漫画书(表单:POST /漫画书)。

POST /草稿漫画书
名称= foo
笔者=嘘声
出版商=咕
公布= 2011-01-01
=> 302找到了,地点:/草稿漫画书/ 3,重定向到带有封面的漫画书(id:3)(二进制)。

GET /选秀漫画图书/ 3
=> 200 OK,获取草拟漫画书(ID:3)的封面。

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

POST /草案-漫画书/ 3 /覆盖
cover_type =前
cover_data =(二进制)
=> 302实测值,位置:/草案-漫画书/ 3 /盖,重定向到新漫画书封面(/ draft-comic-book/3/covers/1)。获取表格发布漫画书(id:3)(表单:POST/published-comic-books)。

POST /出版-漫画书
名称= foo
作者=嘘声
出版商=咕
出版= 2011-01-01
cover_type =前
cover_data =(二进制)
= > 302找到,位置:/漫画书/ 3,重定向到带封面的漫画书(ID:3)。

+0

我是一个总新手,并试图匆忙学习它。我发现这非常有帮助。然而,在我今天一直在阅读的其他博客中,使用GET来执行操作(特别是不是幂等操作)将会被忽略。所以不应该是POST /漫画书/ 3 /发布? –

+3

@GaryMcGill在他的例子中,/ draft-comic-books/3/publish只返回一个HTML表单(不会修改任何数据)。 –

+0

@ Olivier是正确的。 发布这个词就是为了表示表单的作用。但是,由于您希望将动词限制在HTTP方法中,因此您应该发布到发布漫画的资源。 ...如果这是一个网站,您可能需要一个表单的URI来发布一些内容。 ...尽管如果发布操作仅仅是漫画书页面上的单个按钮,该单一按钮表单可以直接发布到/ published-comic-books URI。 – Alex