淺淡RESTful API設計規範

目前主流的通訊協議主要有RPC、http/1.1、http/2等,而http中最主流的無疑就是restful了,由於工作的原因,經常需要和不同的外部服務商進行系統集成,給出的文檔都說是基於restful規範設計,遺憾的是,在我看來,幾乎沒有看到過真正可以稱之爲restful架構的api設計。今天就來談談如何設計一個規範、優雅、可讀性高的restful api。

關於restful設計的最佳實踐,這裏還是推薦阮一峯老師的RESTful API 設計指南,個人覺得是國內範圍裏講的最好的了。rest架構,從個人角度理解,核心做了兩件事情:

  • 資源定位
  • 資源操作

其實從REST的定義中就能看出來,表述層對應的就是描述資源的位置(資源定位),狀態轉移就是對資源的狀態進行變更操作(增刪改查)。
RESTful HTTP動詞

下面舉個實際的例子:假設我們數據庫裏有一張User表,我們根據表建好了領域對象模型User,按照restful規範設計的接口應該是這樣的:

  • 新增用戶,[POST] /users

  • 修改用戶,[PUT] /users/id

  • 刪除用戶,[DELETE] /users/id

  • 查找全部用戶,[GET] /users

看到這裏可能有同學就要問了,幹嘛非得這麼設計,還要用什麼http動詞,delete、put神馬的我都沒用過,平時都是get、post走天下,也用的好好的呀。新增用戶不能用/addUser嗎?刪除用戶不能用/deleteUser嗎?感覺也很清楚啊(事實上很多公司的所謂的restful接口文檔都是這麼定義的)好,現在讓我們回到前面,複習一下rest的定義,第一條叫做資源定位,如果還不理解,那讓我們再想想URL的定義,叫做統一資源定位符,也就是說url是用來表示資源在互聯網上的位置的,所以說在url中應該包含動詞,只能包含名詞,對資源的操作應該體現在http method上面。

如果這樣理解還比較抽象的話,這裏不妨再打一個比方,比如在jane的網站有一張小汽車的圖片,地址是http://jane.com/img/car.jpg,現在jane想設計一個api接口,實現對這張圖片的刪除操作,這個api應該怎麼設計?根據rest的設計規範,很容易得出是

  • [DELETE] http://jane.com/img/car

非常的清晰明瞭(這裏暫時先不考慮調用方是否有權限刪除服務器上的資源)
注意這裏爲了講述原理沒有加資源的後綴.jpg,引用阮一峯老師的話

嚴格地說,有些網址最後的".html"後綴名是不必要的,因爲這個後綴名錶示格式,屬於"表現層"範疇,而URI應該只代表"資源"的位置。它的具體表現形式,應該在HTTP請求的頭信息中用Accept和Content-Type字段指定,這兩個字段纔是對"表現層"的描述。

在這個例子裏,我們可以通過在http header裏指定content-type爲image/jpeg來申明這個資源是一張jpg格式的圖片
接下來,如果要查詢(獲取)這張圖片呢?自然就是

  • [GET] http://jane.com/img/car

再進一步,我們再改造一下這個api,加上.jpg後綴,這個api就變成了

  • [GET] http://jane.com/img/car.jpg

看到這裏大家應該都很熟悉了,這就是我們每天上網要進行無數次操作的api,就是這麼設計出來的。

我們繼續擴展一下,現在我們要獲取的不是靜態圖片資源了,而是一輛小汽車的相關信息,並且需要對車庫裏的汽車進行增刪改查的維護操作。如果用上面講的那種一般http的寫法,可能會寫出類似下面這樣的api(只用GET和POST方法)

[POST] http://jane.com/garage/addCar
body:{"brand":"ford","model":"focus","price":"120000"}

[POST] http://jane.com/garage/udpateCar?id=123
body:{"brand":"ford","model":"focus","price":"130000"}

[GET] http://jane.com/garage/queryCarList

[GET] http://jane.com/garage/queryCarSingle?id=123

[GET] http://jane.com/garage/deleteCar?id=123

看出問題來了嗎?一個嚴重的問題是url丟失了資源的位置,更重要的是,你可以叫deleteCar,也可以叫eraseCar,還可以叫removeCar,具體什麼含義只有設計這個api的人才能說清楚。而如果用http method,那就肯定是DELETE這個方法,所有看這個api的人都知道你提供的是一個刪除這個資源的方法,這就叫做語義化,能用最少的話把一個意思表達清楚,這本身就是一種優雅的設計方式。使用rest設計上述api,結果如下:

[POST] http://jane.com/garage/cars
body:{"brand":"ford","model":"focus","price":"120000"}

[PUT] http://jane.com/garage/cars/123
body:{"brand":"ford","model":"focus","price":"130000"}

[GET] http://jane.com/garage/cars

[GET] http://jane.com/garage/cars/123

[DELETE] http://jane.com/garage/cars/123

這裏http://jane.com/garage/cars/123代表了id爲123的這輛小汽車在網上的唯一位置,本質上和http://jane.com/img/car所代表的含義是一樣的。使用rest能帶來的額外的好處,是你可以做很方便的權限控制。因爲POST、PUT、DELETE、GET等都是標準的http方法,你可以很輕鬆的在nginx這樣的7層代理或者防火牆上設置策略,禁止某些資源的修改及刪除操作,而這顯然是自定義的url所達不到的。

除了HTTP METHOD,rest另外一套重要的規範就是HTTP STATUS,這套狀態碼規範定義了常規的api操作所可能產生的各種可能結果的描述,遵循這套規範,會使得你的api變得更加可讀,同時也便於各種網絡、基礎設施進行交易狀態監控。經常會用到的status code整理如下:

200 OK - [GET]:服務器成功返回用戶請求的數據,該操作是冪等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功。
202 Accepted - [*]:表示一個請求已經進入後臺排隊(異步任務)
204 NO CONTENT - [DELETE]:用戶刪除數據成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操作,該操作是冪等的。
401 Unauthorized - [*]:表示用戶沒有權限(令牌、用戶名、密碼錯誤)。
403 Forbidden - [*] 表示用戶得到授權(與401錯誤相對),但是訪問是被禁止的。
404 NOT FOUND - [*]:用戶發出的請求針對的是不存在的記錄,服務器沒有進行操作,該操作是冪等的。
406 Not Acceptable - [GET]:用戶請求的格式不可得(比如用戶請求JSON格式,但是隻有XML格式)。
410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 當創建一個對象時,發生一個驗證錯誤。
500 INTERNAL SERVER ERROR - [*]:服務器發生錯誤,用戶將無法判斷髮出的請求是否成功。

事情到了這裏似乎一切都很美好,可惜人生不如意十之八九,api設計也不可能一帆風順。總有一些場景是CRUD所抽象不了的,舉個簡單的例子,用戶登陸,如何去匹配CRUD模型?這裏我的建議是,先把你的操作對象或者行爲抽象爲資源,然後就簡單了,無非就是對這個資源的CRUD。
針對用戶登陸這個場景,我們可以把用戶在遠程服務器的會話信息抽象爲一個資源,這樣的話,登陸其實就是在遠程服務器增加了一個會話資源,不難想到,登出就是在遠程服務器刪除了一個會話資源,所以api可以這樣設計

[POST] /login
[DELETE] /logout

如果是發送短信呢?似乎更難。。。這裏再次請出阮一峯老師

如果某些動作是HTTP動詞表示不了的,你就應該把動作做成一種資源。比如網上匯款,從賬戶1向賬戶2匯款500元,正確的寫法是把動詞transfer改成名詞transaction,資源不能是動詞,但是可以是一種服務

這樣的話你把發送短信理解成一種服務,api可以這樣設計

[POST]  /smsService
body:{"mobile":"13813888888","text":"hello world"}

最後建議大家去看一下github的api文檔,可以說是restful架構最完整的實現了,看完後一定會對restful規範有着更深入的理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章