優(yōu)秀的 REST API 設(shè)計(jì)指南

作為一名優(yōu)秀的后端程序員纠炮,你照著產(chǎn)品需求設(shè)計(jì)好了模型月趟,設(shè)計(jì)好了關(guān)聯(lián)關(guān)系,把這些模型和關(guān)系一再打磨了一番之后恢口,你想是時(shí)候把API設(shè)計(jì)出來狮斗,與前端溝通了。

這時(shí)候問題來了:一旦 API 進(jìn)入前端 APP 代碼弧蝇,或者是被你的顧客廣泛使用的話碳褒,再來大改就非常麻煩了。比如說看疗,如果 APP 版本 1.0 用了一個(gè)接口 A沙峻,這個(gè)接口 A 如果要進(jìn)行大改,那么必須將 A 維持至所有用戶升級(jí)過 APP 1.0 后两芳。

那么怎么樣避免 API 發(fā)布之后大改呢摔寨?有沒有一些提前可以注意到的設(shè)計(jì)準(zhǔn)則可以幫我們避開 API 設(shè)計(jì)中的各種坑?答案是有的怖辆。網(wǎng)上充滿了各種對(duì) API 設(shè)計(jì)的建議是复,而這篇文章里我把 REST API 的最佳實(shí)踐和常見的坑都總結(jié)出來,做成一個(gè)教程竖螃,希望可以在幫到正在設(shè)計(jì)/使用 API 的你淑廊。

REST API 是什么 - 程序員與服務(wù)之間溝通的語言


任何語言的本質(zhì)都是一套規(guī)則的合集。比如說特咆,中文里要求句子要有主謂賓季惩,而作為母語為中文的我們,一旦有人說了一句缺少主語的話腻格,我們會(huì)直覺性地感覺很奇怪画拾。

比如說,如果有人對(duì)你說“是一個(gè)神人”菜职。

你會(huì)直覺地問:究竟誰是一個(gè)神人青抛?

同樣的,在各個(gè)程序的溝通中酬核,或者各個(gè)服務(wù)的溝通中蜜另,我們也需要類似“語言”的東西,讓我們可以不需要太多的上下文愁茁,就可以前端理解后端蚕钦、后端也理解前端。

設(shè)想一下鹅很,有多少次你跟前端一起需要前后端?聯(lián)調(diào)嘶居?有多少次前端覺得你定義的 API 不夠方便直觀,一定要你多返回一個(gè)參數(shù)或者改一下端點(diǎn)路徑?

其實(shí)本質(zhì)上邮屁,你們在?聯(lián)調(diào)?時(shí)就是在嘗試設(shè)計(jì)一個(gè)“語言”整袁,以方便互相可以更容易地理解對(duì)方。

比如說佑吝,后端會(huì)要求前端說坐昙,你調(diào)用?POST /user/abc?就可以創(chuàng)建一個(gè)名為?abc?用戶了。

短線來講這當(dāng)然沒問題芋忿,你們可以幾乎任意地定義 API 端點(diǎn)炸客,任意地調(diào)整傳遞的參數(shù)。但是一旦項(xiàng)目開始變復(fù)雜戈钢,問題就開始出現(xiàn)了痹仙。

首先大家有不同的經(jīng)驗(yàn)和喜好,對(duì) API 的定義可能千差萬別殉了,所謂眾口難調(diào)

系統(tǒng)開始復(fù)雜后开仰,各個(gè)系統(tǒng)之間的 API 因?yàn)槎x的人的不同,會(huì)開始出現(xiàn)不一致薪铜,導(dǎo)致每個(gè)端口調(diào)用前需要詳細(xì)閱讀文檔(如果有的話)或者與 API 設(shè)計(jì)者無窮無盡地討論和會(huì)議

如果你的 API 是面向客戶的众弓,比如如果你是一家軟件服務(wù)公司,那么你自定義的 API 會(huì)增加客戶接入的成本

因此隔箍,要是有一套人類通用的"語言“或者”規(guī)范“谓娃,來指導(dǎo)大家定義 API 的方式,那樣該多好鞍恢?

REST API 就是這樣一種規(guī)范傻粘,它是目前整個(gè)互聯(lián)網(wǎng)應(yīng)用最廣泛的 API 規(guī)范。有意思的是帮掉,REST是由它的提出者 Roy Fielding 在他讀書期間,寫的博士論文里提出的窒典。

總結(jié)一下蟆炊,REST API 有一套 API 設(shè)計(jì)的準(zhǔn)則,它規(guī)范了 API 設(shè)計(jì)的框架瀑志,使得服務(wù)間涩搓、程序員之間有一個(gè)通用的溝通語言。

REST API 內(nèi)具體規(guī)定了什么


REST API 規(guī)范了 API 設(shè)計(jì)的兩大核心原則

API應(yīng)該作用于 Resource(資源)上

對(duì)資源的操作應(yīng)使用對(duì)應(yīng)語義的幾種操作劈猪,包括: GET, POST, PUT, PATCH, DELETE

我們來詳細(xì)解釋一下這兩點(diǎn)

什么是 REST API 里的 Resource(即資源)

這里的資源是指你的 API 用戶可操作的邏輯對(duì)象昧甘,舉個(gè)例子

如果你的 API 中允許調(diào)用者對(duì)用戶進(jìn)行操作,比如說用戶注冊战得,那么 API 類似于

POST /users

在這里充边,資源即為?users。在很多情況下,API 中的資源與你的數(shù)據(jù)模型(也就是數(shù)據(jù)庫的表)是一一對(duì)應(yīng)的浇冰。當(dāng)然也有例外情況贬媒,比如說你的數(shù)據(jù)庫中存有用戶,但是你現(xiàn)在想要讓調(diào)用者可以創(chuàng)建“管理員”肘习,那么 API 可能是

POST /admins

然而际乘,你的表中并沒有?admins?這個(gè)表,而是否是 admin 是 Users 表中的一個(gè)屬性漂佩,比如?role=admin脖含。

請注意,REST API中的資源一定需要是名詞投蝉,即一定是一個(gè)實(shí)在存在的概念比如?用戶,?帳號(hào),?車票等养葵,或一個(gè)抽象的概念比如?權(quán)限?等。如果你需要提供一個(gè)創(chuàng)建某種資源的API接口墓拜,上述則可以表述為

POST /indexes

POST /accounts

POST /docs

通常對(duì)于資源的命名港柜,我們建議統(tǒng)一命名為為英文的復(fù)數(shù)。比如說?users?而不是?user咳榜。同時(shí)請注意保持一致性夏醉,在所有地方用一樣的復(fù)數(shù)。

什么是 REST API 里的操作

一旦你定義了資源涌韩,接下來你需要定義允許調(diào)用者在這些資源上做什么操作畔柔。

比如說,以攜程搶車票網(wǎng)站為例臣樱,我們可能允許調(diào)用者進(jìn)行以下操作

GET /tickets?- 列出所有車票

GET /tickets/9839?- 列出 id 為 9839 這張車票的信息

POST /tickets?- 創(chuàng)建一張車票

PUT /tickets/9839?- 更新 9839 這張車票的信息

PATCH /tickets/9839?- 部分修改 983 這張車票的信息靶擦,比如只修改車票價(jià)格

DELETE /tickets/9839?- 刪掉 9839 這張車票

請注意,到這里為止雇毫,你應(yīng)該可以總結(jié)出來REST的大致設(shè)計(jì)思路了玄捕。它由兩部分組成,第一部分是?操作棚放,第二部分是可操作的?資源枚粘。比如上文中的?GET /tickets,操作是 GET飘蚯,可操作的資源是車票馍迄。

那么讀到這里,如果你嚴(yán)格遵循了REST的設(shè)計(jì)準(zhǔn)則局骤,以及你的調(diào)用者也了解 REST 的準(zhǔn)則的話攀圈,那么對(duì)于很多 API 調(diào)用,你們不用再參考互相寫的文檔了峦甩。如果需要調(diào)用一張車票的信息赘来,你的調(diào)用者自然會(huì)知道應(yīng)該用GET去查看一個(gè)車票資源的信息,即?GET /tickets/:ticketId。這樣就極大降低了溝通成本和出錯(cuò)成本撕捍,提升效率拿穴。

如何在 API 中表示實(shí)體(數(shù)據(jù)庫表)間關(guān)系

在后端設(shè)計(jì)中,有的資源邏輯上無法獨(dú)立存在忧风。比如說默色,在某寫項(xiàng)目的例子里,用戶的文檔是無法獨(dú)立于索引存在的狮腿。那么自然地腿宰,我們用

GET /indexes/index_abc/docs/1

來表達(dá)獲取索引?index_abc?中編號(hào)為?1?的文檔。因此缘厢,對(duì)于所有資源需要依賴于另一個(gè)資源存在時(shí)吃度,我們就按順序在端點(diǎn)中將資源列出來。索引和文檔的關(guān)系贴硫,我們有以下接口

GET /indexes/index_abc/docs/1?- 獲取index id為?index_abc?下的id為?1?的文檔

GET /indexes/index_abc/docs?- 獲取index id為?index_abc?下的所有文檔

POST /indexes/index_abc/docs?- 在index id為?index_abc?的索引中椿每,添加文檔 ...

如果一個(gè)資源可以獨(dú)立于另一個(gè)資源存在,并且你期望你的API調(diào)用者頻繁調(diào)用英遭,那么可以考慮直接提供子端點(diǎn)间护。比如說,如果一個(gè)寵物店主人和寵物信息分別都常常被同時(shí)調(diào)用挖诸,那么你可以考慮

GET /owners/? - 獲取所有主人信息

GET /owners/1/pets/ 獲取 id 為 1 的主人的所有寵物

GET /pets/ - 獲取所有寵物信息(寵物店所有寵物)

GET /pets/13 - 直接獲取 id 為 13 的寵物

REST API中如何表示一個(gè)動(dòng)作

有時(shí)候汁尺,當(dāng)我們試圖表達(dá)一些接口時(shí),會(huì)發(fā)現(xiàn)REST的準(zhǔn)則很難直接應(yīng)用多律。比如說痴突,當(dāng)你需要一個(gè)接口讓用戶登錄時(shí)

POST /users/signin

但要注意,這里的?signin?即登錄狼荞,是一個(gè)動(dòng)詞辽装。這里是采用REST準(zhǔn)則時(shí)需要考慮的地方,你有三個(gè)選擇

如果你希望嚴(yán)格地遵循 REST 原則相味,那么你需要找一個(gè)替代動(dòng)詞的名詞如迟。比如說,這里的?signin?可以替換為login攻走。或者此再,如果你是以 token 密鑰的方式登錄的話昔搂,也許可以改為?POST /users/token,即創(chuàng)建一個(gè) user token(也就是登錄了)

在某些實(shí)在困難的地方输拇,放棄嚴(yán)格的REST原則

參考一些成功的 REST API 并尋找類似的 API摘符,參考他們的命名設(shè)計(jì)

對(duì)于3,我強(qiáng)烈建議你參考 GitHub 的 API,原因不光是其極為規(guī)范逛裤,還有它覆蓋了極多的 API 調(diào)用的情景瘩绒,因此大概率你可以找到個(gè)類似的命名參考。

比如說带族,在 GitHub 上锁荔,如果讓你來設(shè)計(jì)加星這個(gè)操作,你會(huì)把端點(diǎn)被設(shè)計(jì)成什么樣蝙砌?

Github把加星端點(diǎn)設(shè)計(jì)為?PUT /gists/:id/star阳堕,把取消加星設(shè)計(jì)為?DELETE /gitsts/:id/star。這樣就完美地遵循了 REST 名詞作為資源的準(zhǔn)則择克,把動(dòng)詞"加星“完美地用?PUT/DELETE?兩個(gè)操作恬总,清晰地表達(dá)了出來。

REST API 設(shè)計(jì)常見問題和建議


上面我們描述了 REST 設(shè)計(jì)的準(zhǔn)則肚邢,而在準(zhǔn)則中并不包括其它”最佳實(shí)踐“壹堰。

這里的最佳實(shí)踐,其實(shí)并沒有什么客觀標(biāo)準(zhǔn)骡湖,只是軟件工程和架構(gòu)經(jīng)過多年的發(fā)展贱纠,REST API 的設(shè)計(jì)也從十幾年前簡單的web 應(yīng)用,到應(yīng)用到現(xiàn)在越來越復(fù)雜企業(yè)級(jí)軟件中勺鸦。因此并巍,如果你剛剛開始學(xué)習(xí) REST API 的設(shè)計(jì),參考這些實(shí)踐經(jīng)驗(yàn)將會(huì)有非常大的幫助换途,可以幫你少走不少繞路懊渡。



REST API 如何區(qū)分版本

在設(shè)計(jì) REST API 時(shí),你應(yīng)該時(shí)刻準(zhǔn)備好不斷更新 API军拟。想要把 API穩(wěn)定后再一次發(fā)布多數(shù)情況下是不實(shí)際的——老板要催進(jìn)度剃执,用戶要催功能。因此懈息,在設(shè)計(jì) API 的時(shí)候就應(yīng)該把支持 API 改動(dòng)設(shè)計(jì)到API本身中肾档。

多數(shù)情況下,在一版 API 已經(jīng)成熟的前提下辫继,可以提前發(fā)布怒见,同時(shí)開始進(jìn)行下一版的開發(fā)。而你只需要在URL中區(qū)分好 API 的版本即可姑宽。

比如說遣耍,如果在大致將 v1 開發(fā)完畢后,v1 前綴的 API 就應(yīng)該穩(wěn)定下來炮车,所有的改動(dòng)進(jìn)入 v2舵变。同時(shí)你應(yīng)該開始通知所有使用 v1 的用戶酣溃,給他們幾周到幾個(gè)月的時(shí)間,幫助他們平滑遷移到 v2

帶有版本前綴的 API 示例如下

GET /v1/indexes/

GET /v1/indexes/abc/

POST /v1/indexes/

REST API 應(yīng)該返回什么

作為一個(gè)通則纪隙,我們建議 REST API 永遠(yuǎn)返回 JSON 格式的結(jié)果赊豌。

原因有幾個(gè):

首先,JSON 作為互聯(lián)網(wǎng)上使用最廣泛的格式绵咱,在幾乎任何語言的任何框架中都有廣泛的支持碘饼。

同時(shí),由于其高度的可讀性麸拄,如果需要閱讀返回內(nèi)容派昧,JSON 會(huì)讓你的調(diào)用者閱讀起來方便很多。

最后拢切,JSON 的高壓縮率可以在需要時(shí)方便地幫你提升傳輸效率和速度蒂萎。

總結(jié)


API是程序員與程序員溝通的語言,一個(gè)優(yōu)秀的API不光可以讓你維護(hù)起來更輕松淮椰,也會(huì)讓你的調(diào)用者在使用時(shí)更得心應(yīng)手五慈。遵循 REST 準(zhǔn)則設(shè)計(jì)出來的優(yōu)秀的 API,可以減少你與調(diào)用者之間的溝通成本主穗,讓你可以用更多的時(shí)間專注在其它更重要的事情上泻拦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市忽媒,隨后出現(xiàn)的幾起案子争拐,更是在濱河造成了極大的恐慌,老刑警劉巖晦雨,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件架曹,死亡現(xiàn)場離奇詭異,居然都是意外死亡闹瞧,警方通過查閱死者的電腦和手機(jī)绑雄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奥邮,“玉大人万牺,你說我怎么就攤上這事∏⑾伲” “怎么了脚粟?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蘸朋。 經(jīng)常有香客問我珊楼,道長,這世上最難降的妖魔是什么度液? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任厕宗,我火速辦了婚禮,結(jié)果婚禮上堕担,老公的妹妹穿的比我還像新娘已慢。我一直安慰自己,他們只是感情好霹购,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布佑惠。 她就那樣靜靜地躺著,像睡著了一般齐疙。 火紅的嫁衣襯著肌膚如雪膜楷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天贞奋,我揣著相機(jī)與錄音赌厅,去河邊找鬼。 笑死轿塔,一個(gè)胖子當(dāng)著我的面吹牛特愿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播勾缭,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼揍障,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了俩由?” 一聲冷哼從身側(cè)響起毒嫡,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎幻梯,沒想到半個(gè)月后兜畸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡礼旅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年膳叨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痘系。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡菲嘴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汰翠,到底是詐尸還是另有隱情龄坪,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布复唤,位于F島的核電站健田,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏佛纫。R本人自食惡果不足惜妓局,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一总放、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧好爬,春花似錦局雄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至穆桂,卻和暖如春宫盔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背享完。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國打工灼芭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驼侠。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓姿鸿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親倒源。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苛预,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344