2011-08-18 120 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