1 概述
1.1 撰寫目的
本文用于定義一種統(tǒng)一的RESTful接口設計方案,希望具有參考價值。本文所描述的方案比較學院派两残,在上一家公司提出沒有被采納,在所了解到的有限的若干家聲稱采用了RESTful風格的公司里委刘,發(fā)現(xiàn)他們也偏離甚遠丧没。當然,他們這么做是有理由的锡移,我也理解呕童,這只是取舍問題。這篇文章其實是舊文了淆珊,2016年年底就已經寫好夺饲,但是一直躺在電腦的硬盤里,不想白費了當時的功夫施符,因此在此公開往声。
1.2 為什么采用REST
目的是為了服務端與客戶端的解耦。SOA僅僅是從結構上將前后端分離戳吝,但是實際上數(shù)據(jù)邏輯還是沒有實現(xiàn)解耦浩销,服務端接口升級往往會影響客戶端,兩者的行為需要嚴格約定听哭。而REST采用HTTP協(xié)議進行約定慢洋,客戶端僅僅需要按照HTTP協(xié)議來理解服務端返回的數(shù)據(jù),雖然與業(yè)務相關的數(shù)據(jù)結構還是需要約定陆盘,但是這確實進一步解耦了服務端與客戶端普筹。
另外,由于嚴格遵照HTTP協(xié)議進行數(shù)據(jù)返回隘马,對于安全的接口太防,可以在返回的Header里設置緩存策略(接口安全性的概念在下文會解釋)。
1.3 文檔結構
第二部分將闡述關于RESTful的若干個關鍵的概念祟霍,明確第二部分闡述的幾個概念有利于設計杏头、實現(xiàn)優(yōu)雅規(guī)范的接口盈包。
第三部分就URL命名的問題進行約定。
第四部分對消息實體進行約定醇王。
第五部分對『向RESTful接口發(fā)起請求』進行闡述呢燥,約定要實現(xiàn)的方法,約定請求的頭部和body的格式寓娩。
第六部分對接口的響應格式進行約定叛氨,包括響應消息的頭部、狀態(tài)碼棘伴、JSON實體寞埠。
第七部分對版本控制的問題進行約定。
第八部分對RESTful接口的實現(xiàn)提出了實現(xiàn)工具的建議焊夸。
2 關鍵概念
明確一些關鍵的概念是很重要的仁连,雖然RESTful風格的API設計方案并沒有統(tǒng)一的標準,但是還是需要符合一定的原則進行設計阱穗,否則就不能稱為RESTful風格的API饭冬。因為許多人并沒有對REST進行充分的了解就宣稱自己的API是RESTful風格的API,以至于RESTful的提出者Fielding博士本人無法忍受揪阶,在2008年為此專門寫了一篇博客『REST APIs must be hypertext-driven』昌抠,hypertext-driven與HATEOAS是同一個概念的不同表述,在下文會進行闡述鲁僚。
2.1 RESTful
REST不是一種協(xié)議炊苫,也不是一種文件格式,更不是一種開發(fā)框架冰沙。它是一系列的設計約束的集合:無狀態(tài)性侨艾、將超媒體作為應用狀態(tài)的引擎等。REST是Representation State Transfer的縮寫倦淀,中文是『表述性狀態(tài)轉移』蒋畜,這里就涉及到資源的表述與狀態(tài)兩個概念。
簡單地說撞叽,資源可以看作是服務器上存儲的所有數(shù)據(jù)姻成,資源的表述則是服務器對外提供的指向這些資源的方式,使用JSON愿棋、XML等均可科展,一個資源可以有多種表述;資源的狀態(tài)則是服務器的數(shù)據(jù)存儲狀態(tài)糠雨,例如在t時刻才睹,服務器中存儲了m條數(shù)據(jù),這時候客戶端向服務端提交了一個創(chuàng)建數(shù)據(jù)的請求,服務器處理了此請求并創(chuàng)建了一條數(shù)據(jù)琅攘,那么在t+1時刻垮庐,服務器中就存儲了m+1條數(shù)據(jù),這兩個時刻的資源狀態(tài)就是不一樣的坞琴,t時刻發(fā)生的請求導致了資源狀態(tài)的改變哨查。
2.2 HATEOAS
Hypermedia As The Engine Of Application State,超媒體作為應用程序狀態(tài)的引擎剧辐。這是REST區(qū)別于其他SOA風格的主要特點寒亥。客戶端與服務端進行互動的時候荧关,完全是通過服務端動態(tài)提供的超媒體進行的溉奕。除了對超媒體的一般理解,客戶端不需要知道其他額外的知識忍啤。相反加勤,在一些SOA接口的設計中,客戶端與服務端的通信是要事先進行約定的同波,例如通過文檔或者接口描述語言(Interface Description Language, IDL)胸竞。而基于HTTP協(xié)議的REST設計里,一般采用的就是請求與響應的Header來體現(xiàn)HATEOAS原則(具體請參考:https://en.wikipedia.org/wiki/HATEOAS)参萄。這里也隱含這樣一層含義:REST應盡可能地利用HTTP標準中現(xiàn)有的東西,例如Header煎饼、標準方法與狀態(tài)碼讹挎。
從標準的角度看,HTTP標準是一項RFC標準吆玖,世界認可筒溃;而其他自定義的SOA標準則可能是一項個人標準或者公司標準,最多是一項互聯(lián)網草案(這對大部分公司來說都不可能)沾乘,而一項標準越是被廣為認可接受怜奖,其實現(xiàn)的通用性就越強。個人標準和公司標準都五花八門翅阵,這樣對每一個標準都要參照其相關文檔實現(xiàn)相應的行為邏輯是很麻煩的歪玲。
2.3 安全性
一個方法被調用1次與被調用0次是一樣的,此方法就是安全的掷匠,否則就是不安全的滥崩。例如,一個方法A僅僅是讀取數(shù)據(jù)讹语,并不創(chuàng)建或者修改數(shù)據(jù)钙皮,不論A方法被調用多少次,都不對數(shù)據(jù)記錄產生任何影響,A方法是安全的短条。而假如有另一個方法B對數(shù)據(jù)進行刪除导匣,B方法被調用1次后,數(shù)據(jù)會被刪除(或者標識位被修改)茸时,系統(tǒng)里的數(shù)據(jù)發(fā)生了變化贡定,那么B方法是不安全的。
2.4 冪等性
一個方法被同樣地調用1次與被調用多次是一樣的屹蚊,即同樣的輸入會得到同樣的輸出厕氨,此方法就是冪等的,否則就不是冪等的汹粤。
2.3節(jié)中A方法與B方法都是冪等的命斧,一個安全的方法一定是冪等的,一個冪等的方法不一定是安全的嘱兼。
假設一個方法C對某個全局計數(shù)器執(zhí)行自增操作并寫入數(shù)據(jù)庫国葬,每次調用C方法都會對系統(tǒng)數(shù)據(jù)產生影響,那么C方法就不是冪等的芹壕。
3 URL命名
URL用于標識資源汇四,因此URL應該以名詞進行命名,例如/users
, /users/children
等踢涌。
一般URL會內嵌參數(shù)通孽,例如要獲取id為313的user的信息,那么URL應該為/users/313
睁壁,前面的user采用復數(shù)背苦,如果要列出其所有后代,則URL應為/users/313/children
潘明,children為復數(shù)形式行剂,如果要獲取其id為499的后代,則URL應為/users/313/children/499
4 消息實體
消息實體钳降,就是請求和響應消息中的entity-body
(也稱為body)厚宰,消息實體采用JSON字符串格式。
5 請求
5.1 方法
使用HTTP標準定義的請求方法遂填。
5.1.1 get
獲取資源铲觉,單個參數(shù)一般寫在URL上,多個參數(shù)則作為query parameter附在URL后面吓坚,例如:
單個參數(shù):/user/123, 表示id為123的user
多個參數(shù):/user?name=tom&phone=13787890987&gender=male
get方法應為冪等的备燃,并且不對數(shù)據(jù)記錄產生影響。對于漢字與特殊字符凌唬,應該進行urlencode并齐。
5.1.2 post
創(chuàng)建資源漏麦,請求的headers里設置Content-type
為application/json
,參數(shù)為json類型况褪。
根據(jù)約定,在創(chuàng)建成功之后测垛,返回的狀態(tài)碼應該是201(Created)捏膨,并且在response的Header里設置Location為新創(chuàng)建的資源的URL,例如食侮,創(chuàng)建了一個新的user号涯,該user創(chuàng)建后id為888,那么Header里應該設置Location
為/users/888
锯七,當然链快,這應該是一個完整的URL,這里只是給出了一個相對路徑的URI以作為說明眉尸。返回了這些數(shù)據(jù)后域蜗,客戶端可以自定義后續(xù)行為,或者查看創(chuàng)建后的user噪猾,或者刷新當前的user列表霉祸,這些行為服務端并不關心。
如果重復提交了相同的數(shù)據(jù)袱蜡,第一次應該返回201丝蹭,以后則應返回409(Conflict),并且在response的Header里設置Location指向已經存在的資源坪蚁,說明沖突的來源半夷。
5.1.3 put
更新資源,對現(xiàn)有資源進行修改迅细,請求的headers與post一樣,參數(shù)也是淘邻。此方法應該是冪等的茵典。
5.1.4 delete
刪除資源。此方法應是冪等的宾舅。
5.2 Header
Content-type應設為application/json统阿。
另外應設置一個version,指明所使用的接口版本筹我。這不屬于HTTP協(xié)議中的一部分扶平,是自定義的,出于版本控制的考量蔬蕊,具體見第七章结澄。
5.3 body
采用JSON字符串,具體的結構有待商定,這不屬于HTTP協(xié)議的一部分麻献,是自定義的们妥。
這里主要放置業(yè)務相關的數(shù)據(jù)。
6 響應
6.1 Header
根據(jù)響應的狀態(tài)碼不同勉吻,相應地設置頭部监婶,具體見下一節(jié)。
但是在我所了解的公司里齿桃,做法都是統(tǒng)一返回200惑惶,然后在返回的JSON字符串里設置消息碼。我是不能理解的短纵。據(jù)一位前端同學說带污,前端代碼接收到了請求以后,不方便獲取Http狀態(tài)碼踩娘。其實我也寫過前端刮刑,不深入,但是一些基本的知識還是有的养渴,我覺得這并不難做到雷绢,估計是他的代碼封裝的時候沒有考慮到這一點,現(xiàn)在要改比較麻煩理卑,所以不想大動干戈翘紊、傷筋動骨。
6.2 狀態(tài)碼
狀態(tài)碼 | 語義 | 使用場景 |
---|---|---|
200 | OK | 正常返回消息藐唠,什么問題也沒有 |
201 | Created | 創(chuàng)建資源成功帆疟,Header里應設置Location指向新創(chuàng)建的資源 |
202 | Accepted | 請求已被接收,但是處理過程較長宇立,不能馬上返回結果 |
304 | Not Modified | 沒有任何修改發(fā)生 |
401 | Unauthorized | 缺乏權限踪宠,指已經登錄但是缺乏請求這個資源的權限 |
403 | Forbidden | 拒絕訪問,可用于未登錄時攔截返回的狀態(tài)碼妈嘹,此時Header里應設置Location為登錄頁面的URL |
404 | Not Found | 不存在所請求的資源 |
406 | Not Acceptable | 請求沒有被接收柳琢,參數(shù)約束校驗不通過,或者其他業(yè)務類型的錯誤都可以返回這個狀態(tài)碼润脸,response的body里應有表示錯誤信息的JSON實體柬脸。 |
409 | Conflict | 請求的資源有沖突,例如多次提交一樣的創(chuàng)建請求毙驯,response的Header里應設置Location為產生沖突的資源的URL |
500 | Internal Server Error | 服務器的非業(yè)務類錯誤倒堕,response的body里應有表示錯誤信息的JSON實體 |
6.3 body采用JSON字符串。
JSON的結構分為兩種:成功爆价、失敗垦巴。
一般而言媳搪,只有返回200的時候才需要讀取成功的JSON,只有返回406和500的時候才需要讀取失敗的JSON魂那,對于其他的狀態(tài)碼蛾号,客戶端不需要服務器提供額外的消息。
對于成功的JSON涯雅,里面應該只包含一個result對象鲜结,而失敗的JSON應該使用這樣的結構:
{
error: {
code: xxx,
message: "xxx",
data: {...}
}
}
失敗的JSON只有一個error對象,包含錯誤碼活逆、消息及相關數(shù)據(jù)精刷,message應該是直接可讀的消息,客戶端毋需理解發(fā)生了什么錯誤蔗候,客戶端只需將消息展示出來即可怒允。在收到406的時候,客戶端只需知道發(fā)生的錯誤是由客戶端造成的即可锈遥,具體是什么類型并不需要知道纫事,將消息直接展示出來,讓使用的人知道是什么即可所灸,所以message應該是人類可以理解的文本丽惶。同理,收到500的時候爬立,只需知道這個錯誤是服務端的問題即可钾唬,客戶端也毋需知道具體的錯誤類型,最多就將錯誤碼和消息展示出來侠驯,讓使用者有反饋的依據(jù)即可抡秆。
7 版本控制
考慮到接口有可能升級,升級的類型有幾種:
新增功能接口
原有接口返回數(shù)據(jù)增加字段
現(xiàn)有接口返回數(shù)據(jù)變更現(xiàn)有字段格式或刪除現(xiàn)有字段
現(xiàn)有接口變更業(yè)務邏輯
刪除接口
其中吟策,前兩種升級并不會影響客戶端儒士,因此毋需處理。而后面三種會導致使用舊接口的客戶端不能正常工作檩坚。
一般服務端升級與客戶端升級都不是同步的着撩,客戶端升級往往會滯后,因此在服務端升級后應該保留舊版本的接口繼續(xù)運行一段時間效床,讓未升級的客戶端可以繼續(xù)工作一段時間,同時可以上線新版本的客戶端权谁。過一段時間后再將舊版本的接口下線剩檀。
而版本控制應該是向下兼容的,即假設當前版本是1.2旺芽,如果客戶端請求1.3版本的服務沪猴,應當用當前版本提供服務辐啄。如果沒有注明請求的版本號,應當提供當前版本的服務运嗜。
一般情況下壶辜,客戶端請求需要帶版本號,但是服務端并不需要對此進行處理担租,除非是同時運行新舊版本的同一個接口砸民,才需要做差異處理。
8 實現(xiàn)
8.1 Spring HATEOAS
Spring HATEOAS可以很方便地與Spring MVC結合來開發(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/
我的技術博客:
https://bungder.github.io
為什么簡書的MarkDown不支持表格語法......