軟件開發(fā)從單機軟件到互聯(lián)網(wǎng)化,從互聯(lián)網(wǎng)單體應(yīng)用到系統(tǒng)拆分志电、前后端分離,再到現(xiàn)在的微服務(wù)蛔趴。軟件架構(gòu)越來越龐大復(fù)雜挑辆,相關(guān)聯(lián)的領(lǐng)域劃分更細致、職責(zé)更專一孝情,這樣各部分之間的交互就很頻繁鱼蝉。RESTful作為一種架構(gòu)風(fēng)格、一種接口設(shè)計規(guī)范箫荡,簡單魁亦、標(biāo)準、易擴展讓其得到了越來越多的應(yīng)用菲茬。
什么是RESTful?
REST 全稱 Representational State Transfer吉挣,翻譯為資源表現(xiàn)層狀態(tài)轉(zhuǎn)化派撕,資源指服務(wù)中的實體或信息;表現(xiàn)層指信息的展現(xiàn)形式睬魂,如json终吼、xml、html等氯哮;狀態(tài)轉(zhuǎn)化指接口訪問過程中數(shù)據(jù)和狀態(tài)的變化际跪,作為http無狀態(tài)服務(wù),業(yè)務(wù)的實現(xiàn)必然要涉及到數(shù)據(jù)和狀態(tài)的變化喉钢。
RESTful的本質(zhì)是一種軟件架構(gòu)風(fēng)格姆打,核心在于面向資源設(shè)計接口,即將所有的接口描述為對資源的操作肠虽,目的在于降低開發(fā)的復(fù)雜性幔戏,提高系統(tǒng)的可伸縮性。設(shè)計的準則主要有以下三點:
- 網(wǎng)絡(luò)上所有的事物都可以被抽象為資源
- 每個資源都有唯一的資源標(biāo)識税课,對資源的操作不會改變這些標(biāo)識
- 所有的操作都是無狀態(tài)的
采用URI標(biāo)識資源
RESTful Web API采用面向資源的架構(gòu)闲延,所以在設(shè)計之初首先需要考慮的是有哪些資源可供操作。資源是一個很寬泛的概念韩玩,任何寄宿于Web可供操作的事物均可視為資源垒玲。資源可以體現(xiàn)為經(jīng)過持久化處理保存到磁盤上的某個文件或者數(shù)據(jù)庫中某個表的某條記錄,也可以是Web應(yīng)用接受到請求后采用某種算法計算得出的結(jié)果找颓。資源可以體現(xiàn)為一個具體的物理對象合愈,它也可以是一個抽象的流程。
一個資源必須具有一個或者多個標(biāo)識击狮,既然我們設(shè)計的Web API佛析,那么很自然地應(yīng)該采用URI來作為資源的標(biāo)識。作為資源標(biāo)識的URI最好具有可讀性帘不,因為具有可讀性的URI更容易被使用说莫,使用者一看就知道被標(biāo)識的是何種資源,比如如下一些URI就具有很好的可讀性寞焙。
http://www.artech.com/employees/c001(編號C001的員工)
http://www.artech.com/sales/2013/12/31(2013年12月31日的銷售額)
http://www.artech.com/orders/2013/q4(2013年第4季度簽訂的訂單)
除了必要的標(biāo)志性和可選的可讀性之外,標(biāo)識資源的URI應(yīng)該具有可尋址性互婿。也就是說捣郊,URI不僅僅指明了被標(biāo)識資源所在的位置,而且通過這個URI可以直接獲取目標(biāo)資源慈参。URI除了可以標(biāo)識某個獨立的資源外(比如http://www.artech.com/employees/c001
)呛牲,還可以標(biāo)識一組資源的集合或者資源的容器(比如http://www.artech.com/orders/2013/q4
)。當(dāng)然驮配,一組同類資源的集合或者存放一組同類資源的容器本身也可以視為另一種類型的復(fù)合型(Composite)資源娘扩,所以URI總是標(biāo)識某個資源這種說法是沒有問題的着茸。
使用HTTP方便表示對資源的動作
由于RESTful Web API采用了同一的接口,所以其成員體現(xiàn)為針對同一資源的操作琐旁。對于Web來說涮阔,針對資源的操作通過HTTP方法來體現(xiàn)。我們應(yīng)該將兩者統(tǒng)一起來灰殴,是Web API分別針對CRUD的操作只能接受具有對應(yīng)HTTP方法的請求敬特。
接下來介紹7個常用的HTTP方法。首先GET牺陶、HEAD和OPTIONS這三個HTTP方法旨在發(fā)送請求以或者所需的信息伟阔。對于GET,相應(yīng)所有人對它已經(jīng)非常熟悉了掰伸,它用于獲取所需的資源皱炉,服務(wù)器一般講對應(yīng)的資源置于響應(yīng)的主體部分返回給客戶端。
HEAD和OPTIONS相對少見狮鸭。從資源操作的語義來講娃承,一個針對某個目標(biāo)資源發(fā)送的HEAD請求一般不是為了獲取目標(biāo)資源本身的內(nèi)容,而是得到描述目標(biāo)資源的元數(shù)據(jù)信息怕篷。服務(wù)器一般將對應(yīng)資源的元數(shù)據(jù)置于響應(yīng)的報頭集合返回給客戶端历筝,這樣的響應(yīng)一般不具有主體部分。OPTIONS請求旨在發(fā)送一種探測請求以確定針對某個目標(biāo)地址的請求必須具有怎樣的約束(比如應(yīng)該采用怎樣的HTTP方法以及自定義的請求報頭)廊谓,然后根據(jù)其約束發(fā)送真正的請求梳猪。比如針對跨域資源的預(yù)檢(Preflight)請求采用的HTTP方法就是OPTIONS。
至于其它4中HTTP方法(POST蒸痹、PUT春弥、PATCH和DELETE),它們旨在針對目標(biāo)資源作添加叠荠、修改和刪除操作匿沛。對于DELETE,它的語義很明確榛鼎,就是刪除一個已經(jīng)存在的資源逃呼。我們著重推薦其它三個旨在完成資源的添加和修改的HTTP方法作一個簡單的介紹。
通過發(fā)送POST和PUT請求均可以添加一個新的資源者娱,但是兩者的不同之處在于:對于前者抡笼,請求著一般不能確定標(biāo)識添加資源最終采用的URI,即服務(wù)端最終為成功添加的資源指定URI黄鳍;對于后者推姻,最終標(biāo)識添加資源的URI是可以由請求者控制的。也正是因為這個原因框沟,如果發(fā)送PUT請求藏古,我們一般直接將標(biāo)識添加資源的URI作為請求的URI增炭;對于POST請求來說,其URI一般是標(biāo)識添加資源存放容器的URI拧晕。
比如我們分別發(fā)送PUT和POST請求以添加一個員工隙姿,標(biāo)識員工的URI由其員工ID來決定。如果員工ID由客戶端來指定防症,我們可以發(fā)送PUT請求孟辑;如果員工ID由服務(wù)端生成,我們一般發(fā)送POST請求蔫敲。具體的請求與下面提供的代碼片斷類似饲嗽,可以看出它們的URI也是不一樣的。
PUT http://www.artech.com/employees/300357 HTTP/1.1
<employee>
<id>300357</id>
<name>張三</name>
<gender>男<gender>
<birthdate>1981-08-24</birthdate>
<department>3041</department>
</employee>
POST http://www.artech.com/employees HTTP/1.1
<employee>
<name>張三</name>
<gender>男<gender>
<birthdate>1981-08-24</birthdate>
<department>3041</department>
</employee>
除了進行資源的添加奈嘿,PUT請求還能用于資源的修改貌虾。由于請求包含提交資源的標(biāo)識(可以放在URI中,也可以置于保存在主體部分的資源內(nèi)容中)裙犹,所以服務(wù)端能夠定位到對應(yīng)的資源予以修改尽狠。對于POST和PUT,也存在一種一刀切的說法:POST用于添加叶圃,PUT用于修改袄膏。我個人比較認可的是:如果PUT提供的資源不存在,則做添加操作掺冠,否則做修改沉馆。
對于發(fā)送PUT請求以修改某個存在的資源,服務(wù)器一般會將提供資源將原有資源整體覆蓋掉德崭。如果需要進行局部修改斥黑,我們推薦請求采用PATCH方法,因為從語義上講Patch就是打補丁的意思眉厨。
無狀態(tài)性
RESTful只維護資源的狀態(tài)锌奴,而不需要維護客戶端的狀態(tài)。對于它來說憾股,每次請求都是全新的鹿蜀,它只需要針對本次請求作相應(yīng)的操作,不需要將本次請求的相關(guān)信息記錄下來以便用于后續(xù)來自相同客戶端請求的處理荔燎。
對于上面我們介紹的RESTful的這些個特性耻姥,它們都是要求我們?yōu)榱藵M足這些特征做點什么,唯有這個無狀態(tài)卻是要求我們不要做什么有咨,因為HTTP本身就是無狀態(tài)的。舉個例子蒸健,一個網(wǎng)頁通過調(diào)用Web API分頁獲取符合查詢條件的記錄座享。一般情況下婉商,頁面導(dǎo)航均具有上一頁和下一頁鏈接用于呈現(xiàn)當(dāng)前頁的前一頁和后一頁的記錄。那么現(xiàn)在有兩種實現(xiàn)方式返回上下頁的記錄渣叛。
- Web API不僅僅會定義根據(jù)具體頁碼的數(shù)據(jù)查詢定義相關(guān)的操作丈秩,還會針對上一頁和下一頁這樣的請求定義單獨的操作。它自身會根據(jù)客戶端的Session ID對每次數(shù)據(jù)返回的頁面在本地進行保存淳衙,以便能夠知道上一頁和下一頁具體是哪一頁蘑秽。
- Web API只會定義根據(jù)具體頁碼的數(shù)據(jù)查詢定義相關(guān)的操作,當(dāng)前返回數(shù)據(jù)的頁碼由客戶端來維護。
第一種貌似很智能,其實就是一種畫蛇添足的作法藻治,因為它破壞了Web API的無狀態(tài)性喇辽。設(shè)計無狀態(tài)的Web API不僅僅使Web API自身顯得簡單而精煉,還因減除了針對客戶端的親和度使我們可以有效地實施負載均衡贤斜,因為只有這樣集群中的每一臺服務(wù)器對于每個客戶端才是等效的。
RESTful遇到的困境
RESTful的設(shè)計風(fēng)格可以極大提高接口API的易用性,目前越來越多的web服務(wù)采用RESTful風(fēng)格的API對外提供服務(wù)肥印,例如:ElasticSearch、MongoDB绝葡、Docker API等等深碱。作為一名后臺開發(fā)工程師,我在工作中也是以RESTful風(fēng)格指導(dǎo)自己設(shè)計對外提供的接口API藏畅,在此過程中也踩過一些坑敷硅。
一個特別棘手的問題是RESTful風(fēng)格的API在框架層面不好做監(jiān)控。通常情況下監(jiān)控為了不侵入業(yè)務(wù)代碼墓赴,框架層面記錄每個HTTP請求的URI竞膳、方法、返回狀態(tài)碼等信息诫硕,然后上報至一個統(tǒng)一的監(jiān)控中心處理分析數(shù)據(jù)坦辟。以查詢雇員信息的API為例,GET /employees/c001
和GET /employees/c002
分別代表查詢c001和c002的信息章办,業(yè)務(wù)層面上使用了同一份查詢邏輯锉走,但是監(jiān)控數(shù)據(jù)卻顯示在兩個不同的URI下。
目前我們的解決方案是在業(yè)務(wù)層面為每個API配置一條監(jiān)控的URI藕届,這樣GET /employees/c001
和GET /employees/c002
這兩種請求的監(jiān)控都會現(xiàn)在查詢雇員信息API下挪蹭,這樣可以更容易反應(yīng)服務(wù)執(zhí)行狀態(tài)。但是導(dǎo)致的結(jié)果就是工作量的增加休偶,本來在框架層面可以統(tǒng)一處理的邏輯卻要侵入至各個業(yè)務(wù)邏輯梁厉。
參考資料
[1] 淺談RESTful接口設(shè)計和開發(fā) http://www.reibang.com/p/d674b6153326
[2] 我所理解的RESTful Web API [設(shè)計篇] https://www.cnblogs.com/artech/p/restful-web-api-02.html