作為一名優(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í)間專注在其它更重要的事情上泻拦。