目前主流的通訊協(xié)議主要有RPC、http/1.1、http/2等,而http中最主流的無疑就是restful了,由于工作的原因,經(jīng)常需要和不同的外部服務(wù)商進(jìn)行系統(tǒng)集成,給出的文檔都說是基于restful規(guī)范設(shè)計(jì),遺憾的是,在我看來,幾乎沒有看到過真正可以稱之為restful架構(gòu)的api設(shè)計(jì)。今天就來談?wù)勅绾卧O(shè)計(jì)一個(gè)規(guī)范、優(yōu)雅、可讀性高的restful api
restful其實(shí)本身并不是一個(gè)新鮮的東西,最早是在2000年由Roy Thomas Fielding博士在他的博士論文中提出。說起這位老兄,來頭可不小,他是http 1.0和1.1版本協(xié)議的主要設(shè)計(jì)者,apache基金會的第一任主席,可以說是現(xiàn)代互聯(lián)網(wǎng)體系的奠基者。Fielding將他對互聯(lián)網(wǎng)軟件的架構(gòu)原則,定名為REST,即表述層狀態(tài)轉(zhuǎn)移(Representational State Transfer)。這是一套在互聯(lián)網(wǎng)體系下,調(diào)用者如何與被調(diào)用者(資源實(shí)體)進(jìn)行互動的規(guī)范設(shè)計(jì)。
當(dāng)時(shí)的互聯(lián)網(wǎng)其實(shí)還是處于剛萌芽的狀態(tài),這個(gè)設(shè)計(jì)思想過于超前,所以早些年一直處于不溫不火的狀態(tài)。直到近年來,互聯(lián)網(wǎng)業(yè)務(wù)高速發(fā)展,系統(tǒng)架構(gòu)越來越復(fù)雜,移動互聯(lián)網(wǎng)的興起,前后端分離架構(gòu)的流行,人們發(fā)現(xiàn)原來這套用于超文本傳輸?shù)膮f(xié)議是如此適合用于設(shè)計(jì)基于互聯(lián)網(wǎng)的api接口,基于http動詞以及標(biāo)準(zhǔn)的http status返回信息,能夠非常好的描述api的特性,并且可讀性非常好。更重要的是,由于http是事實(shí)上的互聯(lián)網(wǎng)通訊標(biāo)準(zhǔn)協(xié)議,基于rest設(shè)計(jì)的api接口,就好像你出國用英語和別人交流,完全不存在溝通障礙。
關(guān)于restful設(shè)計(jì)的最佳實(shí)踐,這里還是推薦阮一峰老師的RESTful API 設(shè)計(jì)指南,個(gè)人覺得是國內(nèi)范圍里講的最好的了。rest架構(gòu),從個(gè)人角度理解,核心做了兩件事情
- 資源定位
- 資源操作
其實(shí)從REST的定義中就能看出來,表述層對應(yīng)的就是描述資源的位置(資源定位),狀態(tài)轉(zhuǎn)移就是對資源的狀態(tài)進(jìn)行變更操作(增刪改查)
下面舉個(gè)實(shí)際的例子:
假設(shè)我們數(shù)據(jù)庫里有一張User表,我們根據(jù)表建好了領(lǐng)域?qū)ο竽P蚒ser,按照restful規(guī)范設(shè)計(jì)的接口應(yīng)該是這樣的:
- 新增用戶
[POST] /users
- 修改用戶
[PUT] /users/id
- 刪除用戶
[DELETE] /users/id
- 查找全部用戶
[GET] /users
看到這里可能有同學(xué)就要問了,干嘛非得這么設(shè)計(jì),還要用什么http動詞,delete、put神馬的我都沒用過,平時(shí)都是get、post走天下,也用的好好的呀。新增用戶不能用/addUser
嗎?刪除用戶不能用/deleteUser
嗎?感覺也很清楚?。ㄊ聦?shí)上很多公司的所謂的restful接口文檔都是這么定義的)
好,現(xiàn)在讓我們回到前面,復(fù)習(xí)一下rest的定義,第一條叫做資源定位,如果還不理解,那讓我們再想想U(xiǎn)RL的定義,叫做統(tǒng)一資源定位符,也就是說url是用來表示資源在互聯(lián)網(wǎng)上的位置的,所以說在url中不應(yīng)該包含動詞,只能包含名詞。對資源的操作應(yīng)該體現(xiàn)在http method上面,如果這樣理解還比較抽象的話,這里不妨再打一個(gè)比方,比如在jane的網(wǎng)站有一張小汽車的圖片,地址是http://jane.com/img/car.jpg
,現(xiàn)在jane想設(shè)計(jì)一個(gè)api接口,實(shí)現(xiàn)對這張圖片的刪除操作,這個(gè)api應(yīng)該怎么設(shè)計(jì)?根據(jù)rest的設(shè)計(jì)規(guī)范,很容易得出是
[DELETE] http://jane.com/img/car
非常的清晰明了(這里暫時(shí)先不考慮調(diào)用方是否有權(quán)限刪除服務(wù)器上的資源)
注意這里為了講述原理沒有加資源的后綴.jpg,引用阮一峰老師的話
嚴(yán)格地說,有些網(wǎng)址最后的".html"后綴名是不必要的,因?yàn)檫@個(gè)后綴名表示格式,屬于"表現(xiàn)層"范疇,而URI應(yīng)該只代表"資源"的位置。它的具體表現(xiàn)形式,應(yīng)該在HTTP請求的頭信息中用Accept和Content-Type字段指定,這兩個(gè)字段才是對"表現(xiàn)層"的描述。
在這個(gè)例子里,我們可以通過在http header里指定content-type為image/jpeg
來申明這個(gè)資源是一張jpg格式的圖片
接下來,如果要查詢(獲?。┻@張圖片呢?自然就是
[GET] http://jane.com/img/car
再進(jìn)一步,我們再改造一下這個(gè)api,加上.jpg后綴,這個(gè)api就變成了
[GET] http://jane.com/img/car.jpg
看到這里大家應(yīng)該都很熟悉了,這就是我們每天上網(wǎng)要進(jìn)行無數(shù)次操作的api,就是這么設(shè)計(jì)出來的
我們繼續(xù)擴(kuò)展一下,現(xiàn)在我們要獲取的不是靜態(tài)圖片資源了,而是一輛小汽車的相關(guān)信息,并且需要對車庫里的汽車進(jìn)行增刪改查的維護(hù)操作。如果用上面講的那種一般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
看出問題來了嗎?一個(gè)嚴(yán)重的問題是url丟失了資源的位置,更重要的是,你可以叫deleteCar
,也可以叫eraseCar
,還可以叫removeCar
,具體什么含義只有設(shè)計(jì)這個(gè)api的人才能說清楚。而如果用http method,那就肯定是DELETE
這個(gè)方法,所有看這個(gè)api的人都知道你提供的是一個(gè)刪除這個(gè)資源的方法,這就叫做語義化,能用最少的話把一個(gè)意思表達(dá)清楚,這本身就是一種優(yōu)雅的設(shè)計(jì)方式。使用rest設(shè)計(jì)上述api,結(jié)果如下
[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的這輛小汽車在網(wǎng)上的唯一位置,本質(zhì)上和http://jane.com/img/car
所代表的含義是一樣的。
使用rest能帶來的額外的好處,是你可以做很方便的權(quán)限控制。因?yàn)?code>POST、PUT
、DELETE
、GET
等都是標(biāo)準(zhǔn)的http方法,你可以很輕松的在nginx這樣的7層代理或者防火墻上設(shè)置策略,禁止某些資源的修改及刪除操作,而這顯然是自定義的url所達(dá)不到的。
除了HTTP METHOD
,rest另外一套重要的規(guī)范就是HTTP STATUS
,這套狀態(tài)碼規(guī)范定義了常規(guī)的api操作所可能產(chǎn)生的各種可能結(jié)果的描述,遵循這套規(guī)范,會使得你的api變得更加可讀,同時(shí)也便于各種網(wǎng)絡(luò)、基礎(chǔ)設(shè)施進(jìn)行交易狀態(tài)監(jiān)控。經(jīng)常會用到的status code整理如下:
200 OK - [GET]:服務(wù)器成功返回用戶請求的數(shù)據(jù),該操作是冪等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數(shù)據(jù)成功。
202 Accepted - [*]:表示一個(gè)請求已經(jīng)進(jìn)入后臺排隊(duì)(異步任務(wù))
204 NO CONTENT - [DELETE]:用戶刪除數(shù)據(jù)成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發(fā)出的請求有錯(cuò)誤,服務(wù)器沒有進(jìn)行新建或修改數(shù)據(jù)的操作,該操作是冪等的。
401 Unauthorized - [*]:表示用戶沒有權(quán)限(令牌、用戶名、密碼錯(cuò)誤)。
403 Forbidden - [*] 表示用戶得到授權(quán)(與401錯(cuò)誤相對),但是訪問是被禁止的。
404 NOT FOUND - [*]:用戶發(fā)出的請求針對的是不存在的記錄,服務(wù)器沒有進(jìn)行操作,該操作是冪等的。
406 Not Acceptable - [GET]:用戶請求的格式不可得(比如用戶請求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 當(dāng)創(chuàng)建一個(gè)對象時(shí),發(fā)生一個(gè)驗(yàn)證錯(cuò)誤。
500 INTERNAL SERVER ERROR - [*]:服務(wù)器發(fā)生錯(cuò)誤,用戶將無法判斷發(fā)出的請求是否成功。
事情到了這里似乎一切都很美好,可惜人生不如意十之八九,api設(shè)計(jì)也不可能一帆風(fēng)順??傆幸恍﹫鼍笆荂RUD所抽象不了的,舉個(gè)簡單的例子,用戶登陸,如何去匹配CRUD模型?這里我的建議是,先把你的操作對象或者行為抽象為資源,然后就簡單了,無非就是對這個(gè)資源的CRUD。
針對用戶登陸這個(gè)場景,我們可以把用戶在遠(yuǎn)程服務(wù)器的會話信息抽象為一個(gè)資源,這樣的話,登陸其實(shí)就是在遠(yuǎn)程服務(wù)器增加了一個(gè)會話資源,不難想到,登出就是在遠(yuǎn)程服務(wù)器刪除了一個(gè)會話資源,所以api可以這樣設(shè)計(jì)
[POST] /login
[DELETE] /logout
如果是發(fā)送短信呢?似乎更難。。。這里再次請出阮一峰老師
如果某些動作是HTTP動詞表示不了的,你就應(yīng)該把動作做成一種資源。比如網(wǎng)上匯款,從賬戶1向賬戶2匯款500元,正確的寫法是把動詞transfer改成名詞transaction,資源不能是動詞,但是可以是一種服務(wù)
這樣的話你把發(fā)送短信理解成一種服務(wù),api可以這樣設(shè)計(jì)
[POST] /smsService
body:{"mobile":"13813888888","text":"hello world"}
最后建議大家去看一下github的api文檔,可以說是restful架構(gòu)最完整的實(shí)現(xiàn)了,看完后一定會對restful規(guī)范有著更深入的理解。