一種RESTful接口的約定

1 概述

1.1 撰寫目的

本文用于定義一種統(tǒng)一的RESTful接口設(shè)計(jì)方案,希望具有參考價(jià)值。本文所描述的方案比較學(xué)院派,在上一家公司提出沒有被采納,在所了解到的有限的若干家聲稱采用了RESTful風(fēng)格的公司里,發(fā)現(xiàn)他們也偏離甚遠(yuǎn)。當(dāng)然,他們這么做是有理由的,我也理解,這只是取舍問題。這篇文章其實(shí)是舊文了,2016年年底就已經(jīng)寫好,但是一直躺在電腦的硬盤里,不想白費(fèi)了當(dāng)時(shí)的功夫,因此在此公開。

1.2 為什么采用REST

目的是為了服務(wù)端與客戶端的解耦。SOA僅僅是從結(jié)構(gòu)上將前后端分離,但是實(shí)際上數(shù)據(jù)邏輯還是沒有實(shí)現(xiàn)解耦,服務(wù)端接口升級(jí)往往會(huì)影響客戶端,兩者的行為需要嚴(yán)格約定。而REST采用HTTP協(xié)議進(jìn)行約定,客戶端僅僅需要按照HTTP協(xié)議來(lái)理解服務(wù)端返回的數(shù)據(jù),雖然與業(yè)務(wù)相關(guān)的數(shù)據(jù)結(jié)構(gòu)還是需要約定,但是這確實(shí)進(jìn)一步解耦了服務(wù)端與客戶端。

另外,由于嚴(yán)格遵照HTTP協(xié)議進(jìn)行數(shù)據(jù)返回,對(duì)于安全的接口,可以在返回的Header里設(shè)置緩存策略(接口安全性的概念在下文會(huì)解釋)。

1.3 文檔結(jié)構(gòu)

第二部分將闡述關(guān)于RESTful的若干個(gè)關(guān)鍵的概念,明確第二部分闡述的幾個(gè)概念有利于設(shè)計(jì)、實(shí)現(xiàn)優(yōu)雅規(guī)范的接口。

第三部分就URL命名的問題進(jìn)行約定。

第四部分對(duì)消息實(shí)體進(jìn)行約定。

第五部分對(duì)『向RESTful接口發(fā)起請(qǐng)求』進(jìn)行闡述,約定要實(shí)現(xiàn)的方法,約定請(qǐng)求的頭部和body的格式。

第六部分對(duì)接口的響應(yīng)格式進(jìn)行約定,包括響應(yīng)消息的頭部、狀態(tài)碼、JSON實(shí)體。

第七部分對(duì)版本控制的問題進(jìn)行約定。

第八部分對(duì)RESTful接口的實(shí)現(xiàn)提出了實(shí)現(xiàn)工具的建議。

2 關(guān)鍵概念

明確一些關(guān)鍵的概念是很重要的,雖然RESTful風(fēng)格的API設(shè)計(jì)方案并沒有統(tǒng)一的標(biāo)準(zhǔn),但是還是需要符合一定的原則進(jìn)行設(shè)計(jì),否則就不能稱為RESTful風(fēng)格的API。因?yàn)樵S多人并沒有對(duì)REST進(jìn)行充分的了解就宣稱自己的API是RESTful風(fēng)格的API,以至于RESTful的提出者Fielding博士本人無(wú)法忍受,在2008年為此專門寫了一篇博客『REST APIs must be hypertext-driven』,hypertext-driven與HATEOAS是同一個(gè)概念的不同表述,在下文會(huì)進(jìn)行闡述。

2.1 RESTful

REST不是一種協(xié)議,也不是一種文件格式,更不是一種開發(fā)框架。它是一系列的設(shè)計(jì)約束的集合:無(wú)狀態(tài)性、將超媒體作為應(yīng)用狀態(tài)的引擎等。REST是Representation State Transfer的縮寫,中文是『表述性狀態(tài)轉(zhuǎn)移』,這里就涉及到資源的表述與狀態(tài)兩個(gè)概念。

簡(jiǎn)單地說(shuō),資源可以看作是服務(wù)器上存儲(chǔ)的所有數(shù)據(jù),資源的表述則是服務(wù)器對(duì)外提供的指向這些資源的方式,使用JSON、XML等均可,一個(gè)資源可以有多種表述;資源的狀態(tài)則是服務(wù)器的數(shù)據(jù)存儲(chǔ)狀態(tài),例如在t時(shí)刻,服務(wù)器中存儲(chǔ)了m條數(shù)據(jù),這時(shí)候客戶端向服務(wù)端提交了一個(gè)創(chuàng)建數(shù)據(jù)的請(qǐng)求,服務(wù)器處理了此請(qǐng)求并創(chuàng)建了一條數(shù)據(jù),那么在t+1時(shí)刻,服務(wù)器中就存儲(chǔ)了m+1條數(shù)據(jù),這兩個(gè)時(shí)刻的資源狀態(tài)就是不一樣的,t時(shí)刻發(fā)生的請(qǐng)求導(dǎo)致了資源狀態(tài)的改變。

2.2 HATEOAS

Hypermedia As The Engine Of Application State,超媒體作為應(yīng)用程序狀態(tài)的引擎。這是REST區(qū)別于其他SOA風(fēng)格的主要特點(diǎn)??蛻舳伺c服務(wù)端進(jìn)行互動(dòng)的時(shí)候,完全是通過(guò)服務(wù)端動(dòng)態(tài)提供的超媒體進(jìn)行的。除了對(duì)超媒體的一般理解,客戶端不需要知道其他額外的知識(shí)。相反,在一些SOA接口的設(shè)計(jì)中,客戶端與服務(wù)端的通信是要事先進(jìn)行約定的,例如通過(guò)文檔或者接口描述語(yǔ)言(Interface Description Language, IDL)。而基于HTTP協(xié)議的REST設(shè)計(jì)里,一般采用的就是請(qǐng)求與響應(yīng)的Header來(lái)體現(xiàn)HATEOAS原則(具體請(qǐng)參考:https://en.wikipedia.org/wiki/HATEOAS)。這里也隱含這樣一層含義:REST應(yīng)盡可能地利用HTTP標(biāo)準(zhǔn)中現(xiàn)有的東西,例如Header、標(biāo)準(zhǔn)方法與狀態(tài)碼。

從標(biāo)準(zhǔn)的角度看,HTTP標(biāo)準(zhǔn)是一項(xiàng)RFC標(biāo)準(zhǔn),世界認(rèn)可;而其他自定義的SOA標(biāo)準(zhǔn)則可能是一項(xiàng)個(gè)人標(biāo)準(zhǔn)或者公司標(biāo)準(zhǔn),最多是一項(xiàng)互聯(lián)網(wǎng)草案(這對(duì)大部分公司來(lái)說(shuō)都不可能),而一項(xiàng)標(biāo)準(zhǔn)越是被廣為認(rèn)可接受,其實(shí)現(xiàn)的通用性就越強(qiáng)。個(gè)人標(biāo)準(zhǔn)和公司標(biāo)準(zhǔn)都五花八門,這樣對(duì)每一個(gè)標(biāo)準(zhǔn)都要參照其相關(guān)文檔實(shí)現(xiàn)相應(yīng)的行為邏輯是很麻煩的。

2.3 安全性

一個(gè)方法被調(diào)用1次與被調(diào)用0次是一樣的,此方法就是安全的,否則就是不安全的。例如,一個(gè)方法A僅僅是讀取數(shù)據(jù),并不創(chuàng)建或者修改數(shù)據(jù),不論A方法被調(diào)用多少次,都不對(duì)數(shù)據(jù)記錄產(chǎn)生任何影響,A方法是安全的。而假如有另一個(gè)方法B對(duì)數(shù)據(jù)進(jìn)行刪除,B方法被調(diào)用1次后,數(shù)據(jù)會(huì)被刪除(或者標(biāo)識(shí)位被修改),系統(tǒng)里的數(shù)據(jù)發(fā)生了變化,那么B方法是不安全的。

2.4 冪等性

一個(gè)方法被同樣地調(diào)用1次與被調(diào)用多次是一樣的,即同樣的輸入會(huì)得到同樣的輸出,此方法就是冪等的,否則就不是冪等的。

2.3節(jié)中A方法與B方法都是冪等的,一個(gè)安全的方法一定是冪等的,一個(gè)冪等的方法不一定是安全的。

假設(shè)一個(gè)方法C對(duì)某個(gè)全局計(jì)數(shù)器執(zhí)行自增操作并寫入數(shù)據(jù)庫(kù),每次調(diào)用C方法都會(huì)對(duì)系統(tǒng)數(shù)據(jù)產(chǎn)生影響,那么C方法就不是冪等的。

3 URL命名

URL用于標(biāo)識(shí)資源,因此URL應(yīng)該以名詞進(jìn)行命名,例如/users, /users/children等。

一般URL會(huì)內(nèi)嵌參數(shù),例如要獲取id為313的user的信息,那么URL應(yīng)該為/users/313,前面的user采用復(fù)數(shù),如果要列出其所有后代,則URL應(yīng)為/users/313/children,children為復(fù)數(shù)形式,如果要獲取其id為499的后代,則URL應(yīng)為/users/313/children/499

4 消息實(shí)體

消息實(shí)體,就是請(qǐng)求和響應(yīng)消息中的entity-body(也稱為body),消息實(shí)體采用JSON字符串格式。

5 請(qǐng)求

5.1 方法

使用HTTP標(biāo)準(zhǔn)定義的請(qǐng)求方法。

5.1.1 get

獲取資源,單個(gè)參數(shù)一般寫在URL上,多個(gè)參數(shù)則作為query parameter附在URL后面,例如:

  • 單個(gè)參數(shù):/user/123, 表示id為123的user

  • 多個(gè)參數(shù):/user?name=tom&phone=13787890987&gender=male

get方法應(yīng)為冪等的,并且不對(duì)數(shù)據(jù)記錄產(chǎn)生影響。對(duì)于漢字與特殊字符,應(yīng)該進(jìn)行urlencode。

5.1.2 post

創(chuàng)建資源,請(qǐng)求的headers里設(shè)置Content-typeapplication/json,參數(shù)為json類型。

根據(jù)約定,在創(chuàng)建成功之后,返回的狀態(tài)碼應(yīng)該是201(Created),并且在response的Header里設(shè)置Location為新創(chuàng)建的資源的URL,例如,創(chuàng)建了一個(gè)新的user,該user創(chuàng)建后id為888,那么Header里應(yīng)該設(shè)置Location/users/888,當(dāng)然,這應(yīng)該是一個(gè)完整的URL,這里只是給出了一個(gè)相對(duì)路徑的URI以作為說(shuō)明。返回了這些數(shù)據(jù)后,客戶端可以自定義后續(xù)行為,或者查看創(chuàng)建后的user,或者刷新當(dāng)前的user列表,這些行為服務(wù)端并不關(guān)心。

如果重復(fù)提交了相同的數(shù)據(jù),第一次應(yīng)該返回201,以后則應(yīng)返回409(Conflict),并且在response的Header里設(shè)置Location指向已經(jīng)存在的資源,說(shuō)明沖突的來(lái)源。

5.1.3 put

更新資源,對(duì)現(xiàn)有資源進(jìn)行修改,請(qǐng)求的headers與post一樣,參數(shù)也是。此方法應(yīng)該是冪等的。

5.1.4 delete

刪除資源。此方法應(yīng)是冪等的。

5.2 Header

Content-type應(yīng)設(shè)為application/json。

另外應(yīng)設(shè)置一個(gè)version,指明所使用的接口版本。這不屬于HTTP協(xié)議中的一部分,是自定義的,出于版本控制的考量,具體見第七章。

5.3 body

采用JSON字符串,具體的結(jié)構(gòu)有待商定,這不屬于HTTP協(xié)議的一部分,是自定義的。

這里主要放置業(yè)務(wù)相關(guān)的數(shù)據(jù)。

6 響應(yīng)

6.1 Header

根據(jù)響應(yīng)的狀態(tài)碼不同,相應(yīng)地設(shè)置頭部,具體見下一節(jié)。

但是在我所了解的公司里,做法都是統(tǒng)一返回200,然后在返回的JSON字符串里設(shè)置消息碼。我是不能理解的。據(jù)一位前端同學(xué)說(shuō),前端代碼接收到了請(qǐng)求以后,不方便獲取Http狀態(tài)碼。其實(shí)我也寫過(guò)前端,不深入,但是一些基本的知識(shí)還是有的,我覺得這并不難做到,估計(jì)是他的代碼封裝的時(shí)候沒有考慮到這一點(diǎn),現(xiàn)在要改比較麻煩,所以不想大動(dòng)干戈、傷筋動(dòng)骨。

6.2 狀態(tài)碼

狀態(tài)碼 語(yǔ)義 使用場(chǎng)景
200 OK 正常返回消息,什么問題也沒有
201 Created 創(chuàng)建資源成功,Header里應(yīng)設(shè)置Location指向新創(chuàng)建的資源
202 Accepted 請(qǐng)求已被接收,但是處理過(guò)程較長(zhǎng),不能馬上返回結(jié)果
304 Not Modified 沒有任何修改發(fā)生
401 Unauthorized 缺乏權(quán)限,指已經(jīng)登錄但是缺乏請(qǐng)求這個(gè)資源的權(quán)限
403 Forbidden 拒絕訪問,可用于未登錄時(shí)攔截返回的狀態(tài)碼,此時(shí)Header里應(yīng)設(shè)置Location為登錄頁(yè)面的URL
404 Not Found 不存在所請(qǐng)求的資源
406 Not Acceptable 請(qǐng)求沒有被接收,參數(shù)約束校驗(yàn)不通過(guò),或者其他業(yè)務(wù)類型的錯(cuò)誤都可以返回這個(gè)狀態(tài)碼,response的body里應(yīng)有表示錯(cuò)誤信息的JSON實(shí)體。
409 Conflict 請(qǐng)求的資源有沖突,例如多次提交一樣的創(chuàng)建請(qǐng)求,response的Header里應(yīng)設(shè)置Location為產(chǎn)生沖突的資源的URL
500 Internal Server Error 服務(wù)器的非業(yè)務(wù)類錯(cuò)誤,response的body里應(yīng)有表示錯(cuò)誤信息的JSON實(shí)體

6.3 body采用JSON字符串。

JSON的結(jié)構(gòu)分為兩種:成功、失敗。

一般而言,只有返回200的時(shí)候才需要讀取成功的JSON,只有返回406和500的時(shí)候才需要讀取失敗的JSON,對(duì)于其他的狀態(tài)碼,客戶端不需要服務(wù)器提供額外的消息。

對(duì)于成功的JSON,里面應(yīng)該只包含一個(gè)result對(duì)象,而失敗的JSON應(yīng)該使用這樣的結(jié)構(gòu):


{

error: {

code: xxx,

message: "xxx",

data: {...}

}

}

失敗的JSON只有一個(gè)error對(duì)象,包含錯(cuò)誤碼、消息及相關(guān)數(shù)據(jù),message應(yīng)該是直接可讀的消息,客戶端毋需理解發(fā)生了什么錯(cuò)誤,客戶端只需將消息展示出來(lái)即可。在收到406的時(shí)候,客戶端只需知道發(fā)生的錯(cuò)誤是由客戶端造成的即可,具體是什么類型并不需要知道,將消息直接展示出來(lái),讓使用的人知道是什么即可,所以message應(yīng)該是人類可以理解的文本。同理,收到500的時(shí)候,只需知道這個(gè)錯(cuò)誤是服務(wù)端的問題即可,客戶端也毋需知道具體的錯(cuò)誤類型,最多就將錯(cuò)誤碼和消息展示出來(lái),讓使用者有反饋的依據(jù)即可。

7 版本控制

考慮到接口有可能升級(jí),升級(jí)的類型有幾種:

  1. 新增功能接口

  2. 原有接口返回?cái)?shù)據(jù)增加字段

  3. 現(xiàn)有接口返回?cái)?shù)據(jù)變更現(xiàn)有字段格式或刪除現(xiàn)有字段

  4. 現(xiàn)有接口變更業(yè)務(wù)邏輯

  5. 刪除接口

其中,前兩種升級(jí)并不會(huì)影響客戶端,因此毋需處理。而后面三種會(huì)導(dǎo)致使用舊接口的客戶端不能正常工作。

一般服務(wù)端升級(jí)與客戶端升級(jí)都不是同步的,客戶端升級(jí)往往會(huì)滯后,因此在服務(wù)端升級(jí)后應(yīng)該保留舊版本的接口繼續(xù)運(yùn)行一段時(shí)間,讓未升級(jí)的客戶端可以繼續(xù)工作一段時(shí)間,同時(shí)可以上線新版本的客戶端。過(guò)一段時(shí)間后再將舊版本的接口下線。

而版本控制應(yīng)該是向下兼容的,即假設(shè)當(dāng)前版本是1.2,如果客戶端請(qǐng)求1.3版本的服務(wù),應(yīng)當(dāng)用當(dāng)前版本提供服務(wù)。如果沒有注明請(qǐng)求的版本號(hào),應(yīng)當(dāng)提供當(dāng)前版本的服務(wù)。

一般情況下,客戶端請(qǐng)求需要帶版本號(hào),但是服務(wù)端并不需要對(duì)此進(jìn)行處理,除非是同時(shí)運(yùn)行新舊版本的同一個(gè)接口,才需要做差異處理。

8 實(shí)現(xiàn)

8.1 Spring HATEOAS

Spring HATEOAS可以很方便地與Spring MVC結(jié)合來(lái)開發(fā)RESTful接口。具體參照其文檔:

http://docs.spring.io/spring-hateoas/docs/0.20.0.RELEASE/reference/html/#fundamentals.jaxb-json

原文鏈接:
https://bungder.github.io/2017/07/24/REST/

我的技術(shù)博客:
https://bungder.github.io


為什么簡(jiǎn)書的MarkDown不支持表格語(yǔ)法......

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,597評(píng)論 19 139
  • 一說(shuō)到REST,我想大家的第一反應(yīng)就是“啊,就是那種前后臺(tái)通信方式?!钡窃谝笤敿?xì)講述它所提出的各個(gè)約束,以及如...
    時(shí)待吾閱讀 3,601評(píng)論 0 19
  • 原文鏈接:https://https://howardwchen.com/2017/09/18/talk-abou...
    守望者Howard閱讀 6,569評(píng)論 1 6
  • 01 “先生!我的手指甲斷了!”七七將手指往閔先生的眼下伸去。 閔先生拉起那雙小手,粉嫩粉嫩的指甲此時(shí)滲著一滴滴地...
    薩摩七七閱讀 486評(píng)論 12 0

友情鏈接更多精彩內(nèi)容