一、由來(lái)和簡(jiǎn)介
現(xiàn)在我們團(tuán)隊(duì)內(nèi)說(shuō)開發(fā)服務(wù)端接口赚哗,一般不做特別強(qiáng)調(diào)浇雹,都指的是 RESTful API沉御。那RESTful API是什么,怎么設(shè)計(jì)和開發(fā)呢昭灵?雖然網(wǎng)絡(luò)上已經(jīng)有大量相關(guān)的資料吠裆,但我們?cè)谧稣衅该嬖嚂r(shí),發(fā)現(xiàn)有不少同學(xué)不是很理解烂完,或是在使用時(shí)不會(huì)變通试疙、受其束縛,所以這個(gè)話題需要繼續(xù)聊聊抠蚣,介紹基本概念的同時(shí)分享一下實(shí)踐經(jīng)驗(yàn)祝旷。
軟件開發(fā)從單機(jī)軟件到互聯(lián)網(wǎng)化,從互聯(lián)網(wǎng)單體應(yīng)用到系統(tǒng)拆分嘶窄、前后端分離怀跛、服務(wù)化,接著到后來(lái)移動(dòng)互聯(lián)網(wǎng)興起柄冲,一度有“mobile first”的策略吻谋,再到現(xiàn)在的微服務(wù)∠趾幔可以說(shuō)軟件架構(gòu)越來(lái)越龐大復(fù)雜漓拾,相關(guān)聯(lián)的各部分需要?jiǎng)澐值酶?xì)、職責(zé)更專一长赞,這樣各部分之間的交互就很頻繁晦攒。RESTful作為一種架構(gòu)風(fēng)格闽撤、一種接口設(shè)計(jì)規(guī)范得哆,簡(jiǎn)單、標(biāo)準(zhǔn)哟旗、易擴(kuò)展讓其得到了越來(lái)越多的應(yīng)用贩据。
REST 全稱 “Representational State Transfer”,乍看很晦澀的名字闸餐,翻譯為“表現(xiàn)層狀態(tài)轉(zhuǎn)化”饱亮,全稱應(yīng)叫“資源表現(xiàn)層狀態(tài)轉(zhuǎn)化””∩嵘常“資源”指服務(wù)中的實(shí)體或信息近上,“表現(xiàn)層”指信息的展現(xiàn)形式,如json拂铡、xml壹无、html等葱绒,“狀態(tài)轉(zhuǎn)化”指接口訪問(wèn)過(guò)程中數(shù)據(jù)和狀態(tài)的變化,作為http無(wú)狀態(tài)服務(wù)斗锭,業(yè)務(wù)的實(shí)現(xiàn)必然要涉及到數(shù)據(jù)和狀態(tài)的變化地淀。
二、資源
在RESTful API中岖是,“資源” 使用名詞形式的URL表示帮毁,且一般為復(fù)數(shù),如“/users”豺撑,使用具有“ID”同等作用的數(shù)據(jù)限定單體烈疚,如 “/users/666”或”/users/zhangsan”,使用URL連接表示所屬或關(guān)聯(lián)關(guān)系 如 ”/users/666/projects”表示ID為666用戶的所有項(xiàng)目前硫。
接口的版本號(hào)胞得,一般也放在URL中,簡(jiǎn)單方便屹电,易于監(jiān)控阶剑, 如”/v1/users”。 也有將版本號(hào)放在http header中危号,或者有的將主版本號(hào)放URL中牧愁,小版本好放httpheader中。
三外莲、表現(xiàn)層
“表現(xiàn)層”一般采用JSON字符串猪半,作為接口數(shù)據(jù)傳輸,相對(duì)于XML偷线,JSON雖然在表現(xiàn)力上若一些磨确,但可讀性、數(shù)據(jù)簡(jiǎn)潔性上都好很多声邦,且和前端Javascript天生就是一對(duì)乏奥。接口返回最好采用統(tǒng)一的格式(如:{code: 200, msg:”O(jiān)K”,data:{}}),方便使用者亥曹。其中“code”可以表示特殊業(yè)務(wù)相關(guān)的狀態(tài)邓了,如定義code為600時(shí)表示添加的用戶名稱重復(fù),在無(wú)特殊含義時(shí)建議和http status保持一致以減少認(rèn)知上的負(fù)擔(dān)媳瞪;”msg”給出必要的簡(jiǎn)短描述骗炉,在某些場(chǎng)景考慮到安全性,可以簡(jiǎn)化甚至將“msg”留空蛇受,如一個(gè)未授權(quán)的用戶訪問(wèn)資源句葵,接口返回http status為401, “msg”不要再做出過(guò)于詳細(xì)的原因說(shuō)明;“data”可以是對(duì)象或列表,若是分頁(yè)的列表乍丈,采用統(tǒng)一的分頁(yè)數(shù)據(jù)格式說(shuō)明分頁(yè)信息——如當(dāng)前頁(yè)碼熊响、總頁(yè)數(shù)、總條數(shù)诗赌。
四汗茄、狀態(tài)轉(zhuǎn)移
“狀態(tài)轉(zhuǎn)移”可以使用基本的httpmethod和上面的URL形成一個(gè)動(dòng)賓結(jié)構(gòu),如“POST /v1/users”表示創(chuàng)建一個(gè)用戶铭若,在http body中提交用戶詳細(xì)信息洪碳,成功后接口返回 {code:200, msg:”O(jiān)K”,data:{id:666, name:”zhangsan”}} 。 一般常用的有GET叼屠、POST瞳腌、PUT、DELETE*镜雨,分別表示獲取嫂侍、創(chuàng)建、更新荚坞、刪除挑宠,而HEAD表示獲取接口元數(shù)據(jù)。對(duì)于GET颓影、PUT各淀、DELETE、HEAD要實(shí)現(xiàn)接口的 冪等性诡挂,而POST請(qǐng)求根據(jù)業(yè)務(wù)需要也可以實(shí)現(xiàn)其冪等性碎浇,根據(jù)系統(tǒng)實(shí)現(xiàn)復(fù)雜度,POST請(qǐng)求實(shí)現(xiàn)冪等性難度會(huì)更大些璃俗,特別是在微服務(wù)場(chǎng)景下奴璃。
五、RESTful規(guī)范帶來(lái)的一些 “困境”
采用一套規(guī)范的同時(shí)往往也意味著有限制城豁,會(huì)對(duì)接口設(shè)計(jì)造成一些困境苟穆。
http method有限的幾個(gè)動(dòng)詞,有時(shí)不足以表達(dá)接口含義钮蛛,如“訂閱一個(gè)項(xiàng)目”(在項(xiàng)目更新時(shí)給訂閱者發(fā)送通知)鞭缭,對(duì)資源”/v1/users/666/projects/1”加任何一個(gè)httpmethod都不足以表達(dá)剖膳。此時(shí)可以采用變通的方法:一般可以在資源路徑末尾加動(dòng)詞或?qū)⒉僮鲃?dòng)作抽象為一種實(shí)體對(duì)象魏颓,抽象出來(lái)的實(shí)體對(duì)象盡量采用常規(guī)的名稱,要易于理解,避免異想天開造輪子吱晒。如POST /v1/users/666/projects/1/subscribe 表示訂閱用戶666的ID為1的項(xiàng)目甸饱,POST /v1/users/666/projects/1/star 則將“收藏項(xiàng)目”這個(gè)動(dòng)作抽象為“給項(xiàng)目加一顆星”。
再比如對(duì)“/projects”有復(fù)雜的查詢需求,URL用任何一個(gè)“資源”路徑都不好表達(dá)叹话,且查詢參數(shù)還比較多——此時(shí)就不要再固守“查詢只能用GET”偷遗,可以設(shè)計(jì)為POST /v1/projects/search,查詢參數(shù)太多不適合當(dāng)作queryparameter 放在URL后面則可以放到http body中驼壶。
考慮到很多監(jiān)控系統(tǒng)氏豌,不會(huì)去讀取httpPOST請(qǐng)求的body內(nèi)容,在很需要做監(jiān)控的接口上热凹,甚至可以考慮將POST請(qǐng)求的body部分改為在URL的query參數(shù)泵喘,如 POST /tasks?name=xxx&template=yyyy 。對(duì)于固守規(guī)范的同學(xué)會(huì)很詫異既然用了POST 怎么還將參數(shù)放到URL后面——在實(shí)際需要很大時(shí)般妙,是可以犧牲一些設(shè)計(jì)上的“完美”的纪铺。
一般來(lái)說(shuō),每個(gè)接口職責(zé)要清晰明確碟渺,業(yè)務(wù)方根據(jù)需要去組合編排這些接口來(lái)實(shí)現(xiàn)業(yè)務(wù)鲜锚。在某些必要的場(chǎng)景也可以將多個(gè)簡(jiǎn)單接口的功能組合到一個(gè)大的接口中,以減少接口訪問(wèn)在時(shí)間和性能上的損耗苫拍。但這樣的做法不能太多芜繁,否則接口容易交叉、混亂绒极、職責(zé)不清浆洗。
總之,不要死抱著規(guī)范不放集峦,在必要的地方要做變通伏社,多看看一些好的示例學(xué)習(xí),如github的接口塔淤。
六摘昌、領(lǐng)域模型分析和RESTful API設(shè)計(jì)的關(guān)系
在得到一個(gè)業(yè)務(wù)系統(tǒng)需求后,在開發(fā)服務(wù)接口之前高蜂,我們會(huì)有一步是對(duì)其做領(lǐng)域模型分析聪黎,提煉出該業(yè)務(wù)系統(tǒng)涉及的各種實(shí)的或需的領(lǐng)域?qū)ο螅⒆R(shí)別它們之間的關(guān)系备恤。對(duì)某些業(yè)務(wù)系統(tǒng)來(lái)說(shuō)稿饰,完成這些領(lǐng)域?qū)ο蟮腃RUD操作及它們之間關(guān)系的組合變化,就能支撐大部分的業(yè)務(wù)需求露泊。那這和RESTful API 有什么關(guān)系呢喉镰?相信到這里你不難發(fā)現(xiàn),RESTful 規(guī)范中URL表示的“資源”惭笑、“資源”間的關(guān)聯(lián)關(guān)系侣姆,以及“狀態(tài)轉(zhuǎn)移”部分說(shuō)到的http method + URL的動(dòng)賓組合生真,都能比較好的繼承我們領(lǐng)域模型分析的結(jié)果,很好的過(guò)度到RESTful API的設(shè)計(jì)捺宗。當(dāng)然柱蟀,不表示領(lǐng)域模型里的所有“實(shí)體”都會(huì)對(duì)應(yīng)到RESTful API中的URL,在接口設(shè)計(jì)中結(jié)合實(shí)際情況肯定會(huì)有相應(yīng)的取舍和變化蚜厉。比如領(lǐng)域模型中分析出來(lái)的某個(gè)實(shí)體可能只是體現(xiàn)在數(shù)據(jù)存儲(chǔ)層长已,沒到接口層,或是這個(gè)所謂“實(shí)體”其實(shí)是多個(gè)概念的綜合表現(xiàn)昼牛,體現(xiàn)為多個(gè)小的實(shí)體及其關(guān)系痰哨。
通過(guò)領(lǐng)域模型分析,結(jié)合DB數(shù)據(jù)層技術(shù)上的考量匾嘱,可以將需要落庫(kù)的對(duì)象進(jìn)一步分析得到DB的ER圖設(shè)計(jì)斤斧。至此,至下往上從數(shù)據(jù)層霎烙、之上往下從接口層都清晰了后撬讽,中間層的代碼就容易組織得清晰,而一個(gè)新來(lái)的同學(xué)也比較容易在這個(gè)框架內(nèi)去理解和擴(kuò)展悬垃。
七游昼、RESTful API設(shè)計(jì)對(duì)開發(fā)的影響
在一個(gè)團(tuán)隊(duì)中,如果沒有清晰的系統(tǒng)設(shè)計(jì)尝蠕、接口設(shè)計(jì)烘豌,特別是新人,在拿到一個(gè)業(yè)務(wù)需求時(shí)看彼,容易僅僅面向當(dāng)前需求來(lái)編寫代碼廊佩,缺少一個(gè)通用的、長(zhǎng)遠(yuǎn)的考慮靖榕。如要提供一個(gè)接口給xxx系統(tǒng)“創(chuàng)建任務(wù)”标锄,接口名甚至可能被定義為/createXxxTask,接口內(nèi)部邏輯實(shí)現(xiàn)上自然也會(huì)加上針對(duì)這個(gè)業(yè)務(wù)的一堆if else茁计×匣剩可以想象在接入的業(yè)務(wù)需求多了后,會(huì)有很多同質(zhì)化的接口星压,也會(huì)存在不少大同小異的代碼重復(fù)践剂。
轉(zhuǎn)換一個(gè)角度,理解要實(shí)現(xiàn)的業(yè)務(wù)需求后娜膘,將目光由外轉(zhuǎn)到系統(tǒng)內(nèi)逊脯。根據(jù)以上提到的領(lǐng)域模型分析、RESTful API設(shè)計(jì)劲绪,我們對(duì)當(dāng)前系統(tǒng)有哪些“資源”男窟,他們組合關(guān)系是怎樣的,進(jìn)而得出系統(tǒng)目前能提供哪些能力贾富,還欠缺什么歉眷,然后再在這上面去擴(kuò)展。盡量將這個(gè)需求本質(zhì)上要操作哪些資源和關(guān)系提煉出來(lái)颤枪,設(shè)置更加通用和可擴(kuò)展的接口汗捡。
相對(duì)于天馬行空無(wú)章法地接口設(shè)計(jì),RESTful接口設(shè)計(jì)規(guī)范在給設(shè)計(jì)著開發(fā)者戴上一副腳鐐的同時(shí)也讓其舞得更美畏纲。
感興趣的可以進(jìn)一步了解 接口身份認(rèn)證扇住、HATEOAS**、各種對(duì)數(shù)據(jù)的序列化方式以及RPC 方案和RESTfulAPI 方案在技術(shù)選型上的取舍盗胀。
接口設(shè)計(jì)過(guò)程中可以使用一些工具艘蹋、文檔來(lái)沉淀設(shè)計(jì)成果,同時(shí)用于加強(qiáng)在設(shè)計(jì)票灰、開發(fā)過(guò)程中團(tuán)隊(duì)之間的交流協(xié)作 —— 這部分內(nèi)容留待下次再聊女阀。