設(shè)計一套良好 REST API

設(shè)計一套良好 REST API

硅谷的apigee公司給出一份對REST API的設(shè)計指導(dǎo)原則堪伍,可以說這家公司在api開發(fā),管理的成績有目共睹拆魏。其提供的指導(dǎo)原則气筋,可以說結(jié)合了其自身實際開發(fā)經(jīng)驗,諸多大型平臺的實際運營經(jīng)驗和標(biāo)準(zhǔn)http規(guī)范连锯。非常值得一讀归苍。

首先,你需要對REST API有一個基本的概念認(rèn)知运怖,然后再深入閱讀:

1. 基于業(yè)務(wù)領(lǐng)域的數(shù)據(jù)建模拼弃,而非基于功能建模。

例如摇展,取得所有的dog

GET /api/dogs

取得一個特定的dog

GET /api/dogs/{id}

取得特定名字的dogs

GET /api/dogs/?name=xxx

創(chuàng)建一個dog

POST /api/dogs

更改一個dog

PUT /api/dogs/{id}

刪除一個dog

DELETE /api/dogs/{id}

標(biāo)準(zhǔn)的HTTP的方法已經(jīng)提供了一套約定俗成的操作語義吻氧。

而一個基于功能建模的api,通常會是下面的樣子:

/getAllDogs/getDogsByNames/getAllBabyDogs/createDogs/createThreeDogs/saveDogs

以上這些我經(jīng)常在新手的代碼里看到。這樣做的代碼盯孙,沒錯鲁森,可以運行,但是不‘標(biāo)準(zhǔn)’ why振惰?基于功能建模的api歌溉,首先會造成學(xué)習(xí)曲線的增長,不容易上手报账,也往往意味著需要記憶大量的url(swagger會解決一部分這個問題研底,但不是全部)。

使用HTTP的標(biāo)準(zhǔn)方法作為操作數(shù)據(jù)的基本語義透罢,勝在其標(biāo)準(zhǔn)的普適性榜晦。這一點上,大的平臺Github羽圃,Heroku等乾胶,做的是最好的。

2. 設(shè)計數(shù)據(jù)的表現(xiàn)形式

毫無疑問朽寞,使用JSON识窿,今天JSON已經(jīng)是事實上的web數(shù)據(jù)標(biāo)準(zhǔn),簡單易懂脑融。但是JSON也有其缺點喻频,比如如何表達Date和Timestamp,一個簡單的做法是用string來表達肘迎,根據(jù)上下文來判斷如何解釋甥温。盡量保持JSON數(shù)據(jù)的簡潔性。

并且使用link去表達資源之間的聯(lián)系:

例如:

{"id": "12345678","kind": "Dog""name": "Lassie","furColor": "brown","ownerID": "98765432"}

更好的方法:

{"id": "12345678","kind": "Dog""name": "Lassie","furColor": "brown","ownerID": "98765432","ownerLink": "https://dogtracker.com/persons/98765432"}

更簡潔的表示:

{"id": "12345678","kind": "Dog""name": "Lassie","furColor": "brown","owner": "https://dogtracker.com/persons/98765432"}

這樣做的好處是客戶端不需要自己重新構(gòu)建URL去獲取owner妓布,更方便使用姻蚓。Google Drive API 和 Github 均使用這種方式。但這樣做也不是沒有壞處匣沼,最容易想到的是狰挡,如果url改變了怎么辦?production释涛,staging加叁,testing用的是不一樣的domain喲。一個方法是:使用相對路徑唇撬,而不是絕對路徑殉农。但這也不能解決全部問題。

3. 設(shè)計URL的表現(xiàn)形式

兩大原則:規(guī)范的(regular)局荚,可預(yù)測的(predictable)超凳。

只使用名詞

要有切入點

要合適的選擇id形式

要表達資源間的聯(lián)系

要支持查詢

要支持返回部分資源

處理更復(fù)雜的計算邏輯

只使用名詞:在URL中使用名詞愈污,避免動詞,一旦使用動詞轮傍,意味著你是在對功能建模暂雹,而非數(shù)據(jù)。

要有切入點:原則上來講创夜,一個api應(yīng)該有一個root path '/', 其返回一個url map杭跪,包括了所有的resouces所對應(yīng)的url。這樣客戶端更容易去發(fā)現(xiàn)和使用api驰吓。

要合適的選擇id形式:例如api/dogs/{id} 中的{id}如何表達涧尿,是類似于‘/dogs/1’,還是‘/dogs/haha’? 一般來說取決于后端的數(shù)據(jù)庫檬贰,大多數(shù)情況下姑廉,使用RDBMS,像mysql之類的主鍵自增功能翁涤,我比較傾向于使用自增主鍵的整數(shù)直接作為entity的id桥言,避免很多問題,如果使用MongoDB的話葵礼,不妨試一試用字符串作為entity的id号阿,可讀性會提高,但是如何維護一個全局唯一的字符主鍵鸳粉,你得三思扔涧。

要表達資源間的聯(lián)系:比如 GET /persons/1/dogs 返回所有屬于person 1的狗。

這種模式可以表述為:

/{relationship-name}[/{resource-id}]/…/{relationship-name}[/{resource-id}]

要支持查詢:

GET /persons;1/dogs GET /persons;name=blabla/dogs

這種模式可以表述為:

/{relationship-name}[;{selector}]/…/{relationship-name}[;{selector}]

更復(fù)雜的查詢條件:

GET /dogs?color=red&state=running&location=park

注意這里的三個查詢子條件之間的關(guān)系是‘與(and)’,如果要表達是‘或(or)’的邏輯届谈,那就得設(shè)計更復(fù)雜的query解釋機制扰柠。但其實實際使用中,表達‘或’的查詢條件很少使用疼约。

要支持返回部分資源:

/dogs?fields=name,color,location

返回的resource中,只包含name,color,location三種信息蝙泼。

處理更復(fù)雜的計算邏輯: 有很多例子程剥,比如貨幣轉(zhuǎn)換,大多數(shù)開發(fā)人員給出的方案是:

/convert/100/EUR/CNY 或者 /convert?quantity=100&unit=EUR&in=CNY

切記:URL是用來表述資源resource汤踏,而不是表述計算的過程织鲸。在URL中使用名詞,避免動詞溪胶。

改進的方案1:

GET /monetary-amount/100/EUR HTTP/1.1Host: Currency-Converter.comAccept-Currency:CNY //在http的header中添加Accept-Currency來指明貨幣的種類搂擦。

改進的方案2:

POST /currency-converter HTTP/1.1Host: Currency-Converter.comContent-Length: 69{"amount": 100,"inputCurrency": "EUR","outputCurrency": "CNY"}

兩個方案都可行,但是方案2有兩個注意的地方:POST返回的結(jié)果可能無法再server端緩存;你是在構(gòu)建一個計算的過程哗脖,而非資源的表述瀑踢,如何理解扳还?就像數(shù)據(jù)庫操作中的‘store procedure’,你可以使用橱夭,并且功能強大氨距,但是接口變得復(fù)雜,邏輯變得耦合棘劣。

4. 反思-設(shè)計數(shù)據(jù)的表現(xiàn)形式俏让。

添加self link

集合數(shù)據(jù)

數(shù)據(jù)分頁

數(shù)據(jù)格式

添加self link:self link提供了一個上下文環(huán)境,客戶端可以更容易理解當(dāng)前的resource的位置

{"user": { "html_url": "octocat (The Octocat)", "type": "User", "url": "https://api.github.com/users/octocat"}}

集合數(shù)據(jù):

方案1:集合collection也是一種resource茬暇,也具有self和kind屬性首昔,這樣所有的單獨entity和collection都具有更加統(tǒng)一的規(guī)范

{"self": "https://dogtracker.com/dogs","kind": "Collection","contents": [{"self": "https://dogtracker.com/dogs/12344","kind": "Dog","name": "Fido","furColor": "white"},{"self": "https://dogtracker.com/dogs/12345","kind": "Dog","name": "Rover","furColor": "brown"}]}

方案2:看起來更加簡潔,但是客戶端可能需要去添加額外的邏輯去處理collection

[{"self": "https://dogtracker.com/dogs/12344","kind": "Dog","name": "Fido","furColor": "white"},{"self": "https://dogtracker.com/dogs/12345","kind": "Dog","name": "Rover","furColor": "brown"}]

還有一種做法是糙俗,針對一個collection勒奇,使用自定義的media type header(比如‘Collection+JSON’)這個方法可行,但是會讓客戶端的處理邏輯復(fù)雜臼节。

數(shù)據(jù)分頁:當(dāng)數(shù)據(jù)返回的集合變大時撬陵,顯然不可能一次性把所有數(shù)據(jù)都返回給客戶端,最好能分批的返回网缝,比如:

GET https://dogtracker.com/dogs?limit=25,offset=0返回{"self": "https://dogtracker.com/dogs?limit=25,offset=0","kind": "Page","pageOf": "https://dogtracker.com/dogs","next": "https://dogtracker.com/dogs?limit=25,offset=25","contents": [...】}

在返回的數(shù)據(jù)中巨税,加入‘pageOf’來指明查詢的起點,‘next’指明下一頁的url粉臊,當(dāng)返回第二頁的時候草添,還需加入‘previous’來指明上一頁。

數(shù)據(jù)格式:現(xiàn)在大多數(shù)的api幾乎只支持JOSN格式的數(shù)據(jù)來作為input和output扼仲,如果要支持更多的數(shù)據(jù)格式远寸,那么應(yīng)該要支持Http Accept Header。

能否用HTML作為輸出的格式屠凶?可以驰后,但是這樣就喪失的rest API的靈活性。現(xiàn)代的web應(yīng)用矗愧,大多使用REST API + SPA的設(shè)計灶芝,SPA端使用Angular等框架,自己渲染HTML唉韭,REST API只提供數(shù)據(jù)服務(wù)夜涕,前端后端通過JSON數(shù)據(jù)來交流,從而實現(xiàn)了前后端的徹底解耦属愤。

如果選擇JSON作為唯一的數(shù)據(jù)格式女器,那么最好支持Http的patch方法,現(xiàn)在有兩種patch的模式:JSON Patch和JSON Merge Patch住诸,選擇一個來用于資源的更新操作〖莸ǎ現(xiàn)在也有很多API只提供PUT來更新資源涣澡,這意味著每次請求都必須發(fā)送整個resource enrity,勢必會消耗更多的payload俏拱,但是實現(xiàn)起來更容易暑塑。

5. 錯誤處理

總的原則:使用標(biāo)準(zhǔn)http的status code來表示錯誤的類型。具體的錯誤內(nèi)容锅必,也要被返回事格。

6. 認(rèn)證和授權(quán)

人生苦短,使用OAuth2搞隐。最起碼也要使用基于token的鑒權(quán)模式驹愚。

7. SDK

可以推出SDK來作為你的REST API的一個補充,就像AWS那樣劣纲,針對每一個服務(wù)逢捺,都有相應(yīng)的編程語言的SDK。這樣更方便第三方的開發(fā)人員使用你的api癞季。多見于SaaS平臺劫瞳。但是小型的平臺,得考慮維護的成本绷柒。

8. Versioning 多版本

REST API的版本控制問題是一個非常有爭議的話題志于,網(wǎng)上的提議有很多,在這里我們不是簡單的給定具體的方法废睦,而是提供幾種可行的想法伺绽,具體的實施還需自己拿捏:

不(顯式)支持多版本

使用Http Accept Header

第一種,什么都不做嗜湃,不支持多版本的api奈应。這個想法的背后依據(jù)是,根據(jù)調(diào)研發(fā)現(xiàn)购披,大多數(shù)的中小型規(guī)模的平臺服務(wù)杖挣,客戶規(guī)模都在一個可控的范圍,api的升級不會很頻繁刚陡,你只需通知你的客戶惩妇,在某個時間點api會更新,然后再server端做一些兼容性的數(shù)據(jù)遷移橘荠,比如增加或刪除某個數(shù)據(jù)庫中的表的某個列的名字。大多數(shù)情況下郎逃,支持多版本api費力不討好哥童,測試和部署的成本很大,收益卻很小褒翰。你要做就是保持唯一個可用api服務(wù)的兼容性贮懈,而不是提供多個版本的api讓用戶使用匀泊。

第二種,如果你一定要支持versioning朵你,那么就在http的accept header中添加version信息各聘,不要在url中使用version信息,千萬不要用/api/v1/xxx抡医。

實際的工作中躲因,對于剛?cè)肼毜男∨栌眩诮榻BREST API的時候忌傻,我會推薦他們讀這個大脉。

具體的可以參考:Zalando RESTful API and Event Scheme Guidelines其中的規(guī)范涵蓋了很多細節(jié)內(nèi)容,細節(jié)的規(guī)范越多水孩,代碼風(fēng)格才越統(tǒng)一镰矿,團隊溝通效率才越高。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俘种,一起剝皮案震驚了整個濱河市秤标,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宙刘,老刑警劉巖苍姜,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異荐类,居然都是意外死亡怖现,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門玉罐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屈嗤,“玉大人,你說我怎么就攤上這事吊输∪暮牛” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵季蚂,是天一觀的道長茫船。 經(jīng)常有香客問我,道長扭屁,這世上最難降的妖魔是什么算谈? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮料滥,結(jié)果婚禮上然眼,老公的妹妹穿的比我還像新娘。我一直安慰自己葵腹,他們只是感情好高每,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布屿岂。 她就那樣靜靜地躺著,像睡著了一般鲸匿。 火紅的嫁衣襯著肌膚如雪爷怀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天带欢,我揣著相機與錄音运授,去河邊找鬼。 笑死洪囤,一個胖子當(dāng)著我的面吹牛徒坡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瘤缩,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼喇完,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了剥啤?” 一聲冷哼從身側(cè)響起锦溪,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎府怯,沒想到半個月后刻诊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡牺丙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年则涯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冲簿。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡粟判,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出峦剔,到底是詐尸還是另有隱情档礁,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布吝沫,位于F島的核電站呻澜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏惨险。R本人自食惡果不足惜羹幸,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辫愉。 院中可真熱鬧栅受,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冀墨。三九已至闸衫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诽嘉,已是汗流浹背蔚出。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留虫腋,地道東北人骄酗。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像悦冀,于是被迫代替她去往敵國和親趋翻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容