原文地址: http://martinfowler.com/articles/richardsonMaturityModel.html
Richardson 成熟模型
通往REST榮光的步驟
一個將rest做法的基本原則元素分成3個步驟的模型(Leonard Richardson發(fā)明的).這些介紹了資源(resources),http動作和超媒體控制(hypermedia controls).
最近我在讀<Rest In Practice>(一本我?guī)讉€同事最近在寫的書)的草稿.他們的目的是解釋如何用rest網(wǎng)絡(luò)服務(wù)去解決許多企業(yè)會遇到的結(jié)合問題.這本書的中心思想是一個概念:網(wǎng)絡(luò)是一個大量大規(guī)模的分布系統(tǒng)工作地很好的存在性證明.因此我們可以利用這個思想的一些主意去更簡單地建立集合系統(tǒng).
為了說明一個網(wǎng)絡(luò)風(fēng)格的系統(tǒng)的特殊屬性,這些作者用了一個Leonard Richardson發(fā)明的Rest成熟模型和在一次QCon談話上的解釋.這個模型用簡潔的方法去思考利用這三個工具,因此我想嘗試一個自己的解釋.(這些協(xié)議的例子只是用來說明,我不覺得它們值得寫成代碼并進(jìn)行測試,因此在細(xì)節(jié)上會存在許多問題)
等級0
這個模型的起點是用http作為一個遠(yuǎn)程交互的傳輸系統(tǒng),但是不用任何網(wǎng)絡(luò)的機(jī)制.基本上你在這里做的就是把HTTP作為你遠(yuǎn)程交互系統(tǒng)的通道機(jī)制,一般基于遠(yuǎn)程程序調(diào)取(Remote Procedure Invocation).
讓我們假設(shè)我希望和我的醫(yī)生預(yù)約一次約定.我的預(yù)約軟件首先需要知道我的醫(yī)生在給定日期的開放位置,因此我發(fā)起一個請求到醫(yī)院的約定系統(tǒng)獲得這個信息.在等級0的場景中,醫(yī)院將公開一個服務(wù)端點到一些URI上.我接著post到那個端點一個文檔包含我的請求的細(xì)節(jié).
POST /appointmentService HTTP/1.1
[various other headers]
<openSlotRequest date = "2010-01-04" doctor = "mjones" />
這個服務(wù)器將返回一個文檔給我以下的信息:
HTTP/1.1 200 OK
[various headers]
<openSlotList>
? ? <slot start = "!400" end = "1450" >
? ? ? ? <doctor id = "mjones" />
? ? </slot>
? ? <slot start = "1600" end = " 1650">
? ? ? ? <doctor id = "mjones" />
? ? </slot>
</openSlotList>
我的例子用的是XML,但是內(nèi)容可以是任何格式的:JSON,YAML,鍵值對或者任何自定義的類型.
我下一步將要定一個約定,我也是post一個文檔到那個端點.
POST /appointmentService HTTP/1.1
[various other headers]
<appointmentRequest>
? ? </slot doctor = "mjones" start = "1400" end = "1450" />
? ? <patient id = "jsmith" />
</appointmentRequest>
如果一切順利我將得到一個回復(fù)說我已經(jīng)約定成功了.
HTTP/1.1 200 OK
[various headers]
<appointment>
? ? <slot doctor = "mjones" start = "1400" end = "1450" />
? ? <patient id = "jsmith" />
</appointment>
如果這中間有問題,比如有人在我之前約定了,那么我將獲得一些錯誤信息在回復(fù)的正文里.
HTTP/1.1 200 OK
[various headers]
<appointmentRequestFailure>
? ? <slot doctor = "mjones" start = "1400" end = "1450" />
? ? <patient id = "jsmith" />
? ? <reason>Slot not available</reason>
</appointmentRequestFailure>
到目前為止這是一個直截了當(dāng)?shù)腞PC風(fēng)格的系統(tǒng).這很簡單因為這僅僅是平常老套的XML來回傳遞.如果你用SOAP或者XML-RPC,基本是一樣的機(jī)制,唯一的區(qū)別就是你將XML格式的信件用某種格式的信封封裝了起來.
等級1
在RMM中通往Rest的榮光的第一步即是介紹資源.因此現(xiàn)在我們不再讓我們的請求到單一的網(wǎng)絡(luò)端點,而是開始和獨(dú)立的資源交流.
因此對于我們的初始請求,我們可能有一個給定醫(yī)生的資源.
POST /doctors/mjones HTTP/1.1
[various other headers]
<openSlotRequest date = "2010-01-04" />
這個請求包含了一些基本的信息,但是每一個開放時間現(xiàn)在成為了一個被強(qiáng)調(diào)的獨(dú)立資源.
HTTP/1.1 200 OK
[various headers]
<openSlotList>
? ? <slot id = "1234" doctor = "mjones" start = "1400" end = "1450" />
? ? <slot id = "5678" doctor = "mjones" start = "1600" end = "1650" />
</openSlotList>
用特定的資源去預(yù)訂意味著要post到特定的資源.
POST /slots/1234 HTTP/1.1
[various other headers]
<appointmentRequest>
? ? <patient id = "jsmith" />
</appointmentRequest>
如果一切順利,那么將得到和之前類似的回復(fù).
HTTP/1.1 200 OK
[various headers]
<appointment>
? ?<slot id = "1234" doctor = "mjones" start = "1400" end = "1450" />
? ?<patient id = "jsmith"/>
</appointment>
不同之處在于如果有任何人需要對預(yù)約做任何事,類似于定一些測試,他們首先要get到約定這一個資源,可能通過一個類似于http://royalhope.nhs.uk/slots/1234/appointment的URI,并且post到那個資源.
對于像我這樣的對象男士這類似于對象的識別的表達(dá).不是調(diào)用方法傳遞參數(shù),我們提供額外信息的參數(shù)去調(diào)用關(guān)于某個特定對象的方法.
等級2-HTTP動作
我已經(jīng)在等級0和等級1的所有交互動作用了post動作,但是有些人使用get動作代替或者增加get動作.在那些等級這沒有啥區(qū)別,它們都是被用作成通道機(jī)制運(yùn)行你通過HTTP進(jìn)行交互.等級2要駛離這一點了,運(yùn)用HTTP動作盡可能和HTTP本身運(yùn)用這些動作一樣.
對于我們的開發(fā)時間列表,這意味著我們希望使用get動作.
GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1
Host: royalhope.nhs.uk
回復(fù)和之前用post動作時的回復(fù)一樣
HTTP/1.1 200 OK
[various headers]
<openSlotList>
? ? <slot id = "1234" doctor = "mjones" start = "1400" end = "1450" />
? ? <slot id = "5678" doctor = "mjones" start = "1600" end = "1650" />
</openSlotList>
在等級2,在一個類似這個的請求中用get動作是很決斷的.HTTP將get定義為了一個很安全的操作,它不會造成顯著的改變狀態(tài)或之類的事情.這允許我們調(diào)用get動作,無論是多少次,無論什么順序,都會返回同樣的結(jié)果.一個重要的結(jié)果就是這允許任何請求的路由可以使用緩存,這是使得網(wǎng)絡(luò)表現(xiàn)地這么好的關(guān)鍵.HTTP有多種方法來支持緩存,這都能用于所有的通訊中.遵循HTTP的規(guī)則使得我們能夠利用這種能力的優(yōu)勢.
去定一個約定我們需要能改變狀態(tài)的HTTP動作,一個post動作或者put動作.我將和之前一樣用post動作.
POST /slots/1234 HTTP/1.1
[various other headers]
<appointmentRequest>
? ? <patient id = "jsmith" />
</appointmentRequest>
運(yùn)用post還是put的優(yōu)劣勢比我這里能講到的要更復(fù)雜深入,也許我會再寫一篇文章談這一點.但是我想指出一些人沒能正確理清POST/PUT和create/update之前的對應(yīng)關(guān)系.他們之前的區(qū)別和那不同.
盡管我在等級1運(yùn)用了同樣的post,那里還有一個顯著的區(qū)別關(guān)于遠(yuǎn)程服務(wù)是如何回應(yīng)的,如果一切順利,服務(wù)器將返回201碼以表明一個新的資源在這個世界上創(chuàng)建了.
HTTP/1.1 201 Created
Location: slots/1234/appointment
[various headers]
<appointment>
? ? <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
? ? <patient id = "jsmith"/>
</appointment>
這個201回復(fù)包含了一個URI位置特性,因此客戶可以利用它去GET未來資源現(xiàn)在的狀態(tài).這個回復(fù)同時也含有一個那資源的表現(xiàn),可以替用戶節(jié)省一個額外的請求.
當(dāng)時期出現(xiàn)錯誤時,這里還有個不同,比如另一個人在定這個會話.
HTTP/1.1 409 Conflict
[various headers]
<openSlotList>
? ? <slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>
</openSlotList>
這個回復(fù)重要的一部分是用HTTP狀態(tài)碼預(yù)示有些事出錯了.比如是409是不錯的選擇去預(yù)示有人已經(jīng)很矛盾地更新了這個資源.與其用200的返回碼卻包含一個錯誤信息,在等級2我們曾經(jīng)明確地運(yùn)用過這類的錯誤返回.這取決于協(xié)議設(shè)計者去決定用什么狀態(tài)碼,但是如果出現(xiàn)了錯誤不應(yīng)該是2xx系列的回復(fù).等級2介紹了HTTP動作和HTTP狀態(tài)碼.
這里有個不一致的前行.REST貢獻(xiàn)者談到了用所有的HTTP動作.他們也在證明他們說的REST試圖學(xué)習(xí)網(wǎng)絡(luò)的部分成功.但是全世界范圍的網(wǎng)絡(luò)在實踐中不怎么使用PUT和POST結(jié)構(gòu).這有許多理由去多用PUT和PATCH,這里證明現(xiàn)有的網(wǎng)絡(luò)不是其中之一.
被現(xiàn)在網(wǎng)絡(luò)的存在性所支持的關(guān)鍵元素是在安全元素和非安全元素間的分離.結(jié)合在一起就是用狀態(tài)碼去幫助交流遇到的錯誤.
等級3-超媒體控制
最后一個等級將解釋一些你經(jīng)常聽到在丑陋的首字母縮寫詞HATEOAS(超文本作為應(yīng)用狀態(tài))下面的一些事情.它強(qiáng)調(diào)了如何從得到開放時間列表到知道如何預(yù)約的問題.
我們從和我們在等級2發(fā)送的同樣的get請求開始.
GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1
Host: royalhope.nhs.uk
但是返回加上了一些新元素.
HTTP/1.1 200 OK
[various headers]
<openSlotList>
? ? <slot id = "1234" doctor = "mjones" start = "1400" end = "1450">
? ? ? ? <link rel = "/linkrels/slot/book" uri = "/slots/1234" />
? ? </slot>
? ? <slot id = "5678" doctor = "mjones" start = "1600" end = "1650">
? ? ? ? <link rel = "/linkrels/slot/book" uri = "/slots/5678"/>
? ? </slot>
</openSlotList>
每一個開放時間有一個鏈接元素包含了一個uri來告訴我們?nèi)绾稳ヮA(yù)訂一個約定.
超媒體的關(guān)鍵是它們告訴了我們下一步可以做什么和可以操作的資源的URI.不要求我們必須知道如何去post我們的預(yù)約請求,回復(fù)中的超媒體控制告訴了我們?nèi)绾稳プ?
從等級2拷貝下來的post請求:
POST /slots/1234 HTTP/1.1
[various other headers]
<appointmentRequest>
? ? <patient id = "jsmith"/>
</appointmentRequest>
回復(fù)包含了一些超媒體控制關(guān)于下一步可以做的不同事情.
HTTP/1.1 201 Created
Location: http://royalhope.nhs.uk/slots/1234/appointment
[various headers]
<appointment>
? ? <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
? ? <patient id = "jsmith" />
? ? <link rel = "/linkrels/appointment/cancel" uri = "/slots/1234/appointment"/>
? ? <link rel = "/linkrels/appointment/addTest" uri = "/slots/1234/appointment/tests"/>
? ? <ink rel = "self" uri = "/slots/1234/appointment"/>
? ? <link rel = "/linkrels/appointment/changeTime" uri = "/doctors/mjones/slots?date=20100104@status=open"/>
? ? <link rel = "/linkrels/appointment/updateContactInfo" uri = "/patients/jsmith/contactInfo"/>
? ? <link rel = "/linkrels/help" uri = "/help/appointment"/>
</appointment>
超媒體控制的一個很明顯的優(yōu)點在于允許服務(wù)器改變其URI計劃而不破壞客戶端.只要客戶查找"addTest"鏈接的URI,接下來服務(wù)端可以改變所有的URI而不是始終用初始的入口.
一個更遠(yuǎn)的受益處在于它幫助了客戶端的開發(fā)者探索協(xié)議.這些鏈接給了客戶端的開發(fā)者一個下一步可能需要做什么的提示.它沒有給所有的信息:"latest"和"cancel"控制都指向同一個URI-他們需要弄清楚其中一個是Get動作另一個是Delete動作.但是這至少給了他們一個起點去思考更多的信息和去協(xié)議文檔看類似的URI.
類似的,它允許了服務(wù)端組去提倡新的能力通過放一個鏈接到回復(fù)中去.如果客戶端的開發(fā)者關(guān)注這些未知的新鏈接,這些鏈接會觸發(fā)他們未來新的探索.
這里沒有絕對的標(biāo)準(zhǔn)如何表示超媒體控制.我這里做的只是用了<<Rest In Practice>>組目前的建議,他們服從了ATOM(RFC 4287).我用了一個包含為了特定URI的uri屬性和描述關(guān)系的ref屬性的link元素.一個廣為人知的關(guān)系是赤裸的(比如self用了指向元素自己),任何為那個服務(wù)的明確都是一個完全合格的URI.ATOM狀態(tài)定義了很有名的linkref是一種鏈接關(guān)系的注冊.我寫的這些受到ATOM可以做什么的限制,這在等級3restfuless中被看作成一個領(lǐng)導(dǎo)者.
這些等級的意義
我本應(yīng)強(qiáng)調(diào)RMM盡管是一個很好的方式去思考REST的元素,卻不是一種REST本身的等級定義.Roy Fielding在<<level 3 RMM is a pre-condition of REST>>中說的很清楚了.像在軟件業(yè)的許多團(tuán)隊一樣,REST有許多定義.但是因為Roy Fielding創(chuàng)造了這個術(shù)語,他的定義應(yīng)該比其他的更重要一些.
我發(fā)現(xiàn)RMM有用之處在于其提供了一個一步步的方法去理解在restful思考背后的基本想法.從這點來說,我把它看作一個幫助我們學(xué)習(xí)概念的工具,而不是我們應(yīng)該在任務(wù)機(jī)制中必須用的東西.我不確定目前我們有了足夠的例子來確定restful做法是結(jié)合系統(tǒng)的正確做法.我真心覺得其是一種很有吸引力的做法以及我會在很多情況下推薦這種做法.
和Ian Robinson談起這點,他強(qiáng)調(diào)當(dāng)Leonard Richardson第一次提出這個模型時他發(fā)現(xiàn)非常有吸引力之處在于他和普遍的設(shè)計技術(shù)的聯(lián)系.
- 等級1 利用分治去解決了處理復(fù)雜性的問題,將大規(guī)模的服務(wù)終點打碎成多個資源.
- 等級2 引入了標(biāo)準(zhǔn)的動作集合,因此我們可以用同樣的方法處理類似的問題,移除不必要的改變.
- 等級3 引入了可發(fā)現(xiàn)性,提供了一種使得協(xié)議自己就是文檔的方法.
結(jié)果是一種模型幫助我們思考我們想要提供的HTTP服務(wù)的種類和框起人們對和其交互的期望.