Restful API 設(shè)計最佳實踐
Restful API成熟度
在Richardson Maturity Model模型中,將RESTful分為4個等級:

4個等級分別是:
第一級(Level 0)的 Web 服務(wù)僅使用 HTTP 作為傳輸方式,實際上只是遠(yuǎn)程方法調(diào)用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬于此類。

第二級(Level 1)的 Web 服務(wù)引入了資源的概念。每個資源有對應(yīng)的標(biāo)識符和表達(dá)。

第三級(Level 2)的 Web 服務(wù)使用不同的?HTTP 方法來進行不同的操作,并且使用HTTP 狀態(tài)碼來表示不同的結(jié)果。如 HTTP GET 方法來獲取資源,HTTP DELETE 方法來刪除資源。

第四級(Level 3)的 Web 服務(wù)使用?HATEOAS。在資源的表達(dá)中包含了鏈接信息。客戶端可以根據(jù)鏈接來發(fā)現(xiàn)可以執(zhí)行的動作。
實踐1:一類資源兩個URL
一個URL表示該類型資源集合,另一個URL用來表示特定的資源元素。
1#?資源集合:
2/epics
3#?資源元素:
4/epics/5
實踐2:使用一致的復(fù)數(shù)名詞
避免混用復(fù)數(shù)和單數(shù)形式,只應(yīng)該使用統(tǒng)一的復(fù)數(shù)名詞來表達(dá)資源。
反例:
1GET?/story
2GET?/story/3正例:
1GET?/stories
2GET?/stories/3實踐3:使用名詞而不是動詞
使用Http方法來表達(dá)動作(增、刪、改、查):
增(POST:非冪等性): 使用POST方法創(chuàng)建新的資源。
刪(DELETE:冪等性): 使用DELETE方法刪除存在的資源。
改(PUT:冪等性): 使用PUT或PATCH方法來更新已存在的資源。
查: 使用GET方法讀取資源。(GET:冪等性)
反例:
1/getAllEpics
2/getAllFinishedEpics
3/createEpic
4/updateEpic正例:
1GET?/epics
2GET?/epics?state=finished
3POST?/epics
4PUT?/epics/5實踐4:將實際數(shù)據(jù)包裝在data字段中
GET /epics在數(shù)據(jù)字段中返回epic資源列表:
1{
2??"data":?[
3????{?"id":?1,?"name":?"epic1"?}
4????,?{?"id":?2,?"name":?"epic2"?}
5??]
6}GET /epic/1在數(shù)據(jù)字段中返回id為1的epic對象:
1{
2??"data":?{?
3????"id":?1,?
4????"name":?"epic1"
5??}
6}PUT,POST和PATCH請求的有效負(fù)荷還應(yīng)包含實際對象的數(shù)據(jù)字段。
優(yōu)點:
還有空間擴展元數(shù)據(jù)
一致性
兼容JSON API標(biāo)準(zhǔn)
實踐5:對可選及復(fù)雜參數(shù)使用查詢字符串(?)
反例:
1GET?/employees
2GET?/externalEmployees
3GET?/internalEmployees
4GET?/internalAndSeniorEmployees保持URL簡單短小。堅持使用基本URL,將復(fù)雜或可選參數(shù)移動到查詢字符串。
1GET?/employees?state=internal&title=senior
2GET?/employees?id=1,2另外還可以使用JSON API方式過濾:
1GET?/employees?filter[state]=internal&filter[title]=senior
2GET?/employees?filter[id]=1,2實踐6:使用HTTP狀態(tài)碼
RESTful Web服務(wù)應(yīng)使用合適的HTTP狀態(tài)碼來響應(yīng)客戶端的請求。
2xx - 成功 - 一切正常。
4xx - 客戶端錯誤 - 如果客戶端的故障(例如:客戶端發(fā)送無效請求或未經(jīng)授權(quán))
5xx - 服務(wù)器錯誤 - 服務(wù)端的故障(嘗試處理請求時的錯誤,如數(shù)據(jù)庫故障,依賴服務(wù)不可用,編碼錯誤或不應(yīng)發(fā)生的狀態(tài))
請注意,使用所有過多的HTTP狀態(tài)碼可能會讓API用戶感到困惑。所以應(yīng)該保持使用精簡的HTTP狀態(tài)碼集。常用狀態(tài)碼如下:
2xx:成功,操作被成功接收并處理
200:請求成功。一般用于GET與POST請求
201:已創(chuàng)建。成功請求并創(chuàng)建了新的資源
3xx:重定向,需要進一步的操作以完成請求
301:永久移動。請求的資源已被永久的移動到新URI,返回信息會包括新的URI,瀏覽器會自動定向到新URI。今后任何新的請求都應(yīng)使用新的URI代替
304:未修改。所請求的資源未修改,服務(wù)器返回此狀態(tài)碼時,不會返回任何資源。客戶端通常會緩存訪問過的資源,通過提供一個頭信息指出客戶端希望只返回在指定日期之后修改的資源
4xx:客戶端錯誤,請求包含語法錯誤或無法完成請求
400:客戶端請求的語法錯誤,服務(wù)器無法理解
401:請求要求用戶的身份認(rèn)證
403:服務(wù)器理解請求客戶端的請求,但是拒絕執(zhí)行此請求
404:服務(wù)器無法根據(jù)客戶端的請求找到資源(網(wǎng)頁)。通過此代碼,網(wǎng)站設(shè)計人員可設(shè)置”您所請求的資源無法找到”的個性頁面
410:客戶端請求的資源已經(jīng)不存在。410不同于404,如果資源以前有現(xiàn)在被永久刪除了可使用410代碼,網(wǎng)站設(shè)計人員可通過301代碼指定資源的新位置
5xx:服務(wù)器錯誤,服務(wù)器在處理請求的過程中發(fā)生了錯誤
500:服務(wù)器內(nèi)部錯誤,無法完成請求
不要過度使用404。狀態(tài)碼的使用要盡量精確。如果資源可用,但禁止用戶訪問,則返回403。如果資源曾經(jīng)存在但現(xiàn)已被刪除或停用,請使用410。
實踐7:提供有用的錯誤消息
除了提供恰當(dāng)?shù)腍TTP狀態(tài)代碼外,還應(yīng)該在HTTP響應(yīng)正文中提供有用且詳細(xì)的錯誤描述。如下所示:
請求:
1GET?/epics?state=unknow響應(yīng):
1//?400?Bad?Request
2{
3??"errors":?[
4????{
5??????"status":?400,
6??????"detail":?"Invalid?state.?Valid?values?are?'biz'?or?'tech'",
7??????"code":?352,
8??????"links":?{
9????????"about":?"http://www.jira.com/rest/errorcode/352"
10??????}
11????}
12??]
13}實踐8:使用HATEOAS
HATEOAS 是 Hypermedia As The Engine Of Application State 的縮寫,從字面上理解是 “超媒體即是應(yīng)用狀態(tài)引擎” 。其原則就是客戶端與服務(wù)器的交互完全由超媒體動態(tài)提供,客戶端無需事先了解如何與數(shù)據(jù)或者服務(wù)器交互。相反的,在一些RPC服務(wù)或者Redis,Mysql等軟件,需要事先了解接口定義或者特定的交互語法。舉例如下:
客戶想要訪問epic的用戶故事清單。因此,他必須知道他可以通過將查詢參數(shù)stories附加到員工URL(例如/epics/21/stories)來訪問用戶故事清單。這種字符串拼接易錯,脆弱且難以維護。如果更改了在REST API中訪問salary語句的方式(例如,現(xiàn)在使用“storyStatements”或“userStories”),則所有客戶端都將中斷。
更好的做法是在響應(yīng)中提供客戶可以跟進的鏈接。例如,對GET /epic的響應(yīng)可能如下所示:
1{
2??"data":?[
3????{
4??????"id":1,
5??????"name":"epic1",
6??????"links":?[
7????????{
8??????????"story":?"http://www.domain.com/epics/21/stories"
9????????}
10??????]
11????}
12??]
13}
優(yōu)點:
如果API被更改,客戶端依舊會獲取有效的URL(只要保證在URL更改時更新鏈接)。
API變得更具自描述性,客戶端不必經(jīng)常查找文檔。
實踐9:恰當(dāng)?shù)卦O(shè)計關(guān)系
假設(shè)每個story都有一個epic和幾個sub task。在API中設(shè)計關(guān)系基本上有三種常用選項:鏈接,側(cè)載和嵌入。
它們都是有效的,正確的選擇取決于用例。基本上,應(yīng)根據(jù)客戶端的訪問模式以及可容忍的請求數(shù)量和有效負(fù)載大小來設(shè)計關(guān)系。
鏈接
1{
2??"data":?[
3????{?
4??????"id":?1,?
5??????"name":?"用戶故事1",
6??????"relationships":?{
7????????"epic":?"http://www.domain.com/story/1/epic",
8????????"subTasks":?[?
9??????????"http://www.domain.com/subTasks/12",
10??????????"http://www.domain.com/subTasks/13"
11????????]
12????????//or?"subTasks":?"http://www.domain.com/story/1/subTasks"
13??????}
14????}
15??]
16}
有效負(fù)載小。
許多請求。
客戶端必須將數(shù)據(jù)拼接在一起才能獲得所有數(shù)據(jù)。
側(cè)載
1{
2??"data":?[
3????{?
4??????"id":?1,?
5??????"name":?"用戶故事1",
6??????"relationships":?{
7????????"epic":??5?,?
8????????"subTask":?[?12,?13?]
9??????}
10????}
11??],
12??"included":?{
13????"epic":?{
14??????"id":?5,?
15??????"name":?"epic5"
16????},
17????"subTasks":?[
18??????{?"id":?12,?"name":?"子任務(wù)12"?}
19??????,?{?"id":?13,?"name":?"子任務(wù)13"?}
20????]
21??}
22}
客戶端還可以通過諸如GET /stories?include=epic,subTasks之類的查詢參數(shù)來控制側(cè)載實體。
一次請求。
定制的有效載荷大小。沒有重復(fù)(例如,即使被許多用戶故事引用,也只用提供一次epic)
客戶端仍然必須將數(shù)據(jù)拼接在一起以便解決關(guān)系,這可能非常麻煩。
嵌入
1{
2??"data":?[
3????{?
4??????"id":?1,?
5??????"name":?"用戶故事1",
6??????"epic":?{
7????????"id":?5,?
8????????"name":?"epic5"
9??????},
10??????"subTask":?[
11????????{?"id":?12,?"name":?"子任務(wù)12"?}
12????????,?{?"id":?13,?"name":?"子任務(wù)13"?}
13??????]
14????}
15??]
16}
對客戶來說最方便。是可以直接通過關(guān)系來獲取實際數(shù)據(jù)。
如果客戶端不需要關(guān)系,白白加載關(guān)系。
有效負(fù)載大小和重復(fù)增加。可能多次嵌入引用的實體。
實踐10:使用小駝峰命名法來命名屬性
1{?
2?????"epic.dateOfCreated":?2019-05-16?
3}
1//?反例
2epic.created_date?//?違反JavaScript規(guī)范
3epic.DateOfCreated?//?建議用于構(gòu)造方法
4
5//?正例
6epic.dateOfCreated
實踐11:使用動詞進行操作
有時對API調(diào)用的響應(yīng)不涉及資源(如計算,轉(zhuǎn)義或變換)。例:
1//?讀取
2GET?/translate?from=de_DE&to=en_US&text=Hallo
3GET?/calculate?para2=23¶2=432
4
5//?觸發(fā)更改服務(wù)器端狀態(tài)的操作
6POST?/restartServer
7//?無消息體
8
9POST?/banUserFromChannel
10{?"user":?"123",?"channel":?"serious-chat-channel"?}通過動詞來表達(dá)RPC風(fēng)格API,它比嘗試RESTful風(fēng)格來進行操作更簡單,更直觀(例如PATCH / server with {“restart”:true})。REST風(fēng)格非常適合與領(lǐng)域模型交互,RPC適合于操作。更多信息請查看“Understanding RPC Vs REST For HTTP APIs”。
實踐12:分頁
兩種流行的分頁方法是:
基于偏移的分頁
基于鍵集的分頁,又稱繼續(xù)令牌,也稱為光標(biāo)(推薦)
基于偏移的分頁
一般方法是使用參數(shù)offset和limit來進行分頁:
1#?返回30至45的epics
2/epics?offset=30&limit=15
如果未填參數(shù),則可使用默認(rèn)值(offset=0, limit=100 ):
1#?返回0至100的epics
2/epics還可以在響應(yīng)數(shù)據(jù)中,提供前一頁和后一頁的鏈接:
請求:
1#?返回30至45的epics
2/epics?offset=30&limit=15響應(yīng):
1{
2??"pagination":?{
3????"offset":?20,
4????"limit":?10,
5????"total":?3465,
6??},
7??"data":?[
8????//...
9??],
10??"links":?{
11????"next":?"http://www.domain.com/epics?offset=30&limit=10",
12????"prev":?"http://www.domain.com/epics?offset=10&limit=10"
13??}
14}基于偏移量的分頁實現(xiàn)很簡單,但是有兩個缺點:
查詢慢。數(shù)據(jù)量大時SQL偏移子句執(zhí)行會很慢。
不安全。分頁期間的變更。
基于鍵集的分頁,又稱繼續(xù)令牌,也稱為光標(biāo)(推薦)
簡單來說就是使用索引列來進行分頁。假設(shè)epic有一個索引列data_created,我們就可以使用data_created來分頁。
1GET?/epics?pageSize=100????????????????
2#?客戶端接受最靠前的100條epic信息,使用`data_created`字段排序
3#?該分頁最老epic的`dataCreated`?字段值為?1504224000000?(=?Sep?1,?2017?12:00:00?AM)
4
5GET?/epics?pageSize=100&createdSince=1504224000000
6#?客戶端請求1504224000000之后的100個epics數(shù)據(jù)。
7#?該分頁最前面的epic創(chuàng)建于1506816000000.該分頁方式解決了基于偏移的分頁的許多缺點,但對調(diào)用方來說不太方便。
更好的方式是通過向日期添加附加信息(如id)來創(chuàng)建所謂的 continuation token,以提高可靠性和效率。此外,應(yīng)該向該令牌的有效負(fù)載中提供專用字段,以便客戶端不用必須通過查看元素才能搞清楚。甚至還可以進一步提供下一頁鏈接。
因此?GET /epics?pageSize=100請求將返回如下:
1{
2??"pagination":?{
3????"continuationToken":?"1504224000000_10",
4??},
5??"data":?[
6????//?...
7????//?last?element:
8????{?"id":?10,?"dateCreated":?1504224000000?}
9??],
10??"links":?{
11????"next":?"http://www.domain.com/epics?pageSize=100&continue=1504224000000_10"
12??}
13}
下一頁鏈接使API真正成為RESTful風(fēng)格,因為客戶端只需通過這些鏈接(HATEOAS)即可查看集合。無需手動構(gòu)建URL。此外,服務(wù)端可以簡單地更改URL結(jié)構(gòu)而不會破壞客戶端,保證接口的演進性。
實踐13:確保API的可演進性
避免破壞性變更
保持向后兼容。只要客戶端能接受就通過添加字段的方式。
復(fù)制和棄用。要更改現(xiàn)有字段(重命名或更改結(jié)構(gòu)),可在該字段旁邊添加新字段,并在接口手冊中棄用該字段。一段時間后,刪除舊字段。
利用超媒體和HATEOAS。只要客戶端使用響應(yīng)中的鏈接來訪問(且不會手動創(chuàng)建URL),即可以安全地更改URL而不會破壞客戶端。
使用新名稱創(chuàng)建新資源。如果新業(yè)務(wù)需求導(dǎo)致全新的領(lǐng)域模型和工作流,則可以創(chuàng)建新資源。
保持業(yè)務(wù)邏輯在服務(wù)側(cè)
不要讓服務(wù)成為轉(zhuǎn)儲數(shù)據(jù)訪問層,它通過直接公開數(shù)據(jù)庫模型(低級API)來提供CRUD功能。這造成了高耦合。
因此,我們應(yīng)該構(gòu)建高層次/基于工作流的API而不是低級API。
實踐14:版本化
API實在無法演進,則必須提供不同版本的API。版本控制允許在不破壞客戶端的情況下,在新版本中發(fā)布不兼容和重大更改的API。
有兩種最流行的版本控制方法:
通過URLs版本化
通過Accept HTTP Header進行版本控制(內(nèi)容協(xié)商)
通過URLs版本化
只需將API的版本號放在每個資源的URL中即可。
1/v1/epics優(yōu)點:
對API開發(fā)人員非常簡單。
對客戶端訪問也非常簡單。
可以復(fù)制和粘貼URL。
缺點:
非RESTful。(該方式會令URL發(fā)生變化)
破壞URLs。客戶端必須維護和更新URL。
由于其簡單性,該方式被各大廠商廣泛使用,例如:Facebook, Twitter, Google/YouTube, Bing, Dropbox, Tumblr以及Disqus等。
通過Accept HTTP Header進行版本控制(內(nèi)容協(xié)商)
更RESTFul的方式是利用通過Accept HTTP請求頭的內(nèi)容協(xié)商。
1GET?/epics
2Accept:?application/vnd.myapi.v2+json優(yōu)點:
URLs保持不變
RESTFul方式
HATEOAS友好
缺點:
稍微難以使用。客戶必須注意標(biāo)題。
無法再復(fù)制和粘貼網(wǎng)址。
source:?//kaelzhang81.github.io/2019/05/24/Restful-API設(shè)計最佳實踐

喜歡,在看
