相關文章:RESTful API URI 設計的一些總結。
問題場景:刪除一個資源(Resources),URI 該如何設計?
應用示例:刪除名稱為 iPhone 6 的產品。
是不是感覺很簡單呢?根據應用示例,我們用代碼實現下:
public class ProductsController : ApiController
{
[HttpDelete]
[Route("api/products")]
public async Task<HttpResponseMessage> DeleteByProductName(string productName)
{
...
}
}
客戶端調用:
delete /api/products?productName=iPhone 6
網上有關 RESTful API URI 的一些文章,都是一些示例性質的,也就是說并不能真正運用到實際應用中,比如刪除一個資源的 URI,它會告訴你應該這樣設計比較好:delete /api/products/{productId}
,productId 是產品的唯一標識,刪除資源必須通過唯一標識?示例應用中可以這樣,但在實際應用中的場景,往往比示例復雜幾十上幾百倍,比如我們可以把上面的示例復雜點:
- 刪除某一用戶下,名稱為 iPhone 6 和系統為 iOS 8 的產品
針對上面的應用示例,我們該如何設計 URI 呢?難道還要堅守“刪除資源必須通過唯一標識”?如果真是這樣,我們的解決方案就“簡單”多了,對,沒錯,先查詢再刪除,就這么簡單,但總感覺哪里不對,心里有個聲音(不應該這樣啊,肯定有其他解決方案)。另外,Delete 操作包含 ?productName=iPhone 6
,這種方式是不是也感覺很怪,為什么呢?我們一般在設計 MVC URL 或 WebAPI URI 的時候,? 后面一般跟的是查詢條件,既然是查詢條件,那配合 Delete 操作,就非常“怪”了,我們看一下 GitHub API 中的一個示例:
"user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
?q= 一般用于 URI 的 GET 操作,那能不能用于其他操作呢?我們先來看下 Query 的權威定義:
The query component contains non-hierarchical data that, along with data in the path component (Section 3.3), serves to identify a resource within the scope of the URI's scheme and naming authority (if any). The query component is indicated by the first question mark ("?") character and terminated by a number sign ("#") character or by the end of the URI.
文字雖短,但信息量還是非常大的,主要是講兩個內容:Query 是什么?Query 的作用是什么?
non-hierarchical 是非分層結構的意思,也就是 URI 中的 /(前后結構),Query 并不包含非分層結構數據,簡單一句話,? 之后并且 # 之前,這部分內容就是 Query,比如 user_search_url 中的 q={query}{&page,per_page,sort,order}
,那 Query 的作用是什么呢?標識資源(identify a resource),需要注意的是,上面那段話,絲毫沒有提到 Get 的字眼,其實,URI 和 HTTP Method 沒有半毛錢關系,更不是一一對應關系,這是我自己之前一個很深的誤解,要糾正過來。
好,Query 的疑問解開了,我們接下來設計上面應用實例的 URI,大致兩種方案:
delete /api/users/1/products?productName=iPhone 6&&system=iOS 8
delete /api/products?userId=1&&productName=iPhone 6&&system=iOS 8
這兩種 URI 設計的不同點就是:第一個 URI 把 User 放在 Path 中,第二個 URI 把 User 放在 Query 中。到底哪種設計方案好呢?關于這個疑問,其實,我也沒有確定的想法,然后在網上搜索了大量資料,但都是一些籠統的概念講解,并沒有針對問題的具體分析,偶然看到阮一峰的這篇博文《RESTful API 設計指南》,內容也是一大堆概念和簡單示例,但有一個哥們的評論吸引了我的注意(遇到知音了),我直接復制下:
現在正在做一個基于RESTful API 架構的接口。但是遇到了一些令自己很困擾的問題:
假如每一個設備(devices)是擁有多張設備圖片(images)的,那么按照RESTful,我們的URI 設計如下:
現在的設計:
- GET /devices/ID/images:獲取某個設備的所有圖片
- GET /devices/ID/images/ID:獲取某個設備的某張圖片(我們不提供改方法,有人認為沒有意義的)
- POST /devices/ID/images:給指定設備添加一張圖片
- PUT /Images/ID:修改某張圖片(有疑問)
- DELETE /images/ID : 刪除某張圖片(有疑問)
我覺得合理的設計是:
- PUT /devices/ID/Images/ID:修改某個設備的某張圖片
- DELETE /devices/ID/images/ID : 刪除某個設備的某張圖片
看到,對于修改和刪除某張圖片,我是有疑問的,現在的刪除是直接通過圖片ID進行刪除(不管這張圖片屬于哪一個設備的)。其實真的符合RESTful標準嗎? 他們這樣設計的理由是:由于圖片本身就有自己的圖片ID,為什么直接通過ID來刪除呢,還非得指定某個設備的圖片,然后再刪除。
RESTful 強調資源的唯一性,images/ID其實就是唯一的圖片資源,而/devices/ID/images/ID也是設備的唯一圖片,那么,我都不知道應該采用哪一個URI的設計。
看到上面是不是有點熟悉的趕腳呢,和我們的應用示例其實存在一樣的疑問,但很遺憾,沒有人回復這個哥們,我想回復他,但自己都沒搞明白,拿什么回復呢?好,既然發現了同病相憐的人,那就更有勇氣去搜索了,后來在茫茫的 Google 搜索中,又偶然發現這個帖子:Designing a REST api by URI vs query string.
這位哥們的疑問是,祖父母(外祖父母)下面是父母,父母下面是孩子,那如果去查詢孩子,該如何設計 URI 呢?來看這位哥們的三種 URI 設計:
GET /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}
GET /myservice/api/v1/parents/{parentID}/children?search={text}
GET /myservice/api/v1/children?search={text}&grandparentID={id}&parentID=${id}
關于這個疑問,大神們做了詳細的回答,每個回答都可以寫成一篇文章了,我大概看了幾篇,也是云里霧里的,大家如果有英文好的,可以翻譯下這個帖子,我覺得是很有價值的。
下面說一下我自己的體會,首先,URI 中的層級結構,并不一定適用,比如上面那個示例,現在的情況是三級,但實際上是多極的,祖父母上面還有祖祖父母等等,所以,如果非要用層級結構來體現 URI,也并不是可取的,其次,要明確你要請求的資源到底是什么?祖父母、父母、還是孩子?如果是孩子,那么不管是哪種 URI 設計,層級結構中最后的那個詞一定是 Childrens,這是肯定的,那如果存在層級結構關系,是設計成 Query?還是 Path 呢?我們看一下某一段回復:
I believe I have already thoroughly beaten this to death, but query strings are not for "filtering" resources. They are for identifying your resource from non-hierarchical data. If you have drilled down your hierarchy with your path by going /person//children/ and you are wishing to identify a specific child or a specific set of children, you would use some attribute that applies to children you are identifying and include it inside the query.
再強調下,Query 并不是過濾資源,而是 Identify 資源(非層級結構數據),他這樣設計的 URI:/person/{id}/children/
,我覺得這是一個好的設計,族譜的結構可以再抽象出來,人都有孩子,不管是祖父母,還是父母,這個層級關系是確定的,由確定某一個人,再確定他的孩子,這樣更加明確具體,至于之外的標識,都可以放在 Query 中,用來具體標識這個層級結構下的資源,所以,有時候可能不是你的 URI 問題,而是你的 URI 設計問題。
回過頭看我們一開始設計的兩種 URI,其實我自己覺得都是可以的,用戶作為層級結構可以,作為標識資源(Query)也可以,畢竟它們其實沒有特別強的層級結構關系,如果有一些層級關系,對于最末端的資源,標識資源中可以進行明確它,比如刪圖片那哥們的 ImageID,這個就可以直接標識某一個具體的圖片,也可以不用 Device 來標識了,這個問題,我自己現在覺得,并沒有唯一性,關鍵看具體的應用場景,和對 RESTful API URI 的理解,但不管怎樣,設計出來的 URI,一定要簡潔,并讓別人看得懂。
我、刪圖片的那位哥們、還有族譜的那位哥們,所拋出的問題,雖然具體的應用場景不一樣,但其實本質上都是一樣的,我覺得像這類問題,可以深入探究下,這篇博文就到這。
再補充 REST 相關的一些文章:
文章列表