
如何快速實現(xiàn)REST API集成以優(yōu)化業(yè)務(wù)流程
圖8-1 REST成熟度模型
在第0級中,Web服務(wù)只是使用HTTP作為傳輸方式,實際上只是遠程方法調(diào)用(RPC)的一種具體形式。SOAP和XML-RPC都屬于此級別。比如,在一個醫(yī)院掛號系統(tǒng)中,醫(yī)院會通過某個URI來暴露出該掛號服務(wù)端點(Service Endpoint)。然后患者會向該URI發(fā)送一個文檔作為請求,文檔中包含了請求的所有細節(jié)。
POST /appointmentService HTTP/1.1
[省略了其他頭的信息……]
<openSlotRequest date = "2010-01-04" doctor = "mjones"/>
然后服務(wù)器會傳回一個包含了所需信息的文檔。
HTTP/1.1 200 OK
[省略了其他頭的信息……]
<openSlotList>
<slot start = "1400" end = "1450">
<doctor id = "mjones"/>
</slot>
<slot start = "1600" end = "1650">
<doctor id = "mjones"/>
</slot>
</openSlotList>
在這個例子中我們使用了XML,但是內(nèi)容實際上可以是任何格式,比如JSON、YAML、鍵值對等,或者其他自定義的格式。
有了這些信息,下一步就是創(chuàng)建一個預(yù)約。這同樣可以通過向某個端點發(fā)送一個文檔來完成。
POST /appointmentService HTTP/1.1
[省略了其他頭的信息……]
<appointmentRequest>
<slot doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
</appointmentRequest>
如果一切正常,那開發(fā)者能夠收到一個預(yù)約成功的響應(yīng)。
HTTP/1.1 200 OK
[省略了其他頭的信息……]<appointment>
<slot doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
</appointment>
如果發(fā)生了問題,比如有人在我前面預(yù)約上了,那我會在響應(yīng)體中收到某條錯誤信息。
HTTP/1.1 200 OK
[省略了其他頭的信息……]
<appointmentRequestFailure>
<slot doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
<reason>Slot not available</reason>
</appointmentRequestFailure>
到目前為止,這都是非常直觀的基于RPC風(fēng)格的系統(tǒng)。它是簡單的,因為只有Plain Old XML(POX)在這個過程中被傳輸。如果你使用SOAP或者XML-RPC,原理也是基本相同的,唯一的不同是——你將XML消息包含在了某種特定的格式中。
在第1級中,Web服務(wù)引入了“資源”的概念,每個資源有對應(yīng)的標識符和表達。所以,不是將所有的請求發(fā)送到單個服務(wù)端點,而是和單獨的資源進行交互。
因此在我們的首個請求中,對指定醫(yī)生會有一個對應(yīng)的資源。
POST /doctors/mjones HTTP/1.1
[省略了其他頭的信息……]
<openSlotRequest date = "2010-01-04"/>
響應(yīng)會包含一些基本信息,包含了各個時間段的就診時間信息,這些就診時間可以被作為資源單獨處理。
HTTP/1.1 200 OK[省略了其他頭的信息……]
<openSlotList>
<slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
<slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>
</openSlotList>
有了這些資源后,創(chuàng)建一個預(yù)約就是向某個特定的就診時間發(fā)送請求。
POST /slots/1234 HTTP/1.1
[省略了其他頭的信息……]
<appointmentRequest>
<patient id = "jsmith"/>
</appointmentRequest>
如果一切順利,則會收到和前面類似的響應(yīng):
HTTP/1.1 200 OK
[省略了其他頭的信息……]
<appointment>
<slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
</appointment>
在第2級中,Web服務(wù)使用不同的HTTP方法來進行不同的操作,并且使用HTTP狀態(tài)碼來表示不同的結(jié)果。例如,GET方法用來獲取資源,DELETE方法用來刪除資源。
在醫(yī)院掛號系統(tǒng)中,獲取醫(yī)生的就診時間信息需要使用GET。
GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1
Host: royalhope.nhs.uk
響應(yīng)和之前使用POST發(fā)送請求時一致。
HTTP/1.1 200 OK
[省略了其他頭的信息……]
<openSlotList><slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
<slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>
</openSlotList>
像上面那樣使用GET來發(fā)送一個請求是至關(guān)重要的。HTTP將GET定義為一個安全的操作,它并不會對任何事物的狀態(tài)造成影響。這也就允許我們可以用不同的順序若干次調(diào)用GET請求而每次還能夠獲取到相同的結(jié)果。一個重要的結(jié)論就是,GET允許參與到路由中的參與者使用緩存機制,該機制是讓目前的Web運轉(zhuǎn)良好的關(guān)鍵因素之一。HTTP包含許多方法來支持緩存,這些方法可以在通信過程中被所有的參與者使用。通過遵守HTTP的規(guī)則,我們可以很好地利用該緩存。
為了創(chuàng)建一個預(yù)約,我們需要使用一個能夠改變狀態(tài)的請求方式。
這里使用和前面相同的一個POST請求。
POST /slots/1234 HTTP/1.1
[省略了其他頭的信息……]
<appointmentRequest>
<patient id = "jsmith"/>
</appointmentRequest>
如果一切順利,則服務(wù)會返回一個201響應(yīng)來表明新增了一個資源。這是與第1級的POST響應(yīng)完全不同的。第2級中的操作響應(yīng)都有統(tǒng)一的返回狀態(tài)碼。
HTTP/1.1 201 Created
Location: slots/1234/appointment
[省略了其他頭的信息……]
<appointment>
<slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
<patient id = "jsmith"/>
</appointment>
201響應(yīng)包含了一個Location屬性,它是一個URI。將來客戶端可以通過GET請求獲得該資源的狀態(tài)。以上的響應(yīng)還包含該資源的信息,從而省去了一個獲取該資源的請求。當出現(xiàn)問題時,第2級和第1級還有一個不同之處。比如某人預(yù)約了該時段:
HTTP/1.1 409 Conflict
[various headers]
<openSlotList>
<slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>
</openSlotList>
在上例中,409表明該資源已經(jīng)被更新了。與使用200作為響應(yīng)碼再附帶一個錯誤信息相比,在第2級中我們會明確響應(yīng)碼的含義,以及其所對應(yīng)的響應(yīng)信息。
在第3級中,Web服務(wù)使用HATEOAS。HATEOAS是Hypertext AsThe Engine Of Application State的縮寫,是指在資源的表達中包含了鏈接信息,客戶端可以根據(jù)鏈接來發(fā)現(xiàn)可以執(zhí)行的動作。
從上述REST成熟度模型中可以看到,使用HATEOAS的REST服務(wù)是成熟度最高的,也是Roy Fielding所推薦的“超文本驅(qū)動”的做法。對于不使用HATEOAS的REST服務(wù),客戶端和服務(wù)器的實現(xiàn)之間是緊密耦合的。客戶端需要根據(jù)服務(wù)器提供的相關(guān)文檔來了解所暴露的資源和對應(yīng)的操作。當服務(wù)器發(fā)生變化(如修改了資源的URI)時,客戶端也需要進行相應(yīng)的修改。而在使用HATEOAS的REST服務(wù)中,客戶端可以通過服務(wù)器提供的資源的表達來智能地發(fā)現(xiàn)可以執(zhí)行的操作。當服務(wù)器發(fā)生了變化時,客戶端并不需要做出修改,因為資源的URI和其他信息都是被動態(tài)發(fā)現(xiàn)的。下面是一個HATEOAS的例子。
{
"id": 711,
"manufacturer": "bmw",
"model": "X5",
"seats": 5,"drivers": [
{
"id": "23",
"name": "Way Lau",
"links": [
{
"rel": "self",
"href": "/api/v1/drivers/23"
}
]
}
]
}
回到我們的醫(yī)院掛號系統(tǒng)案例中,還是使用在第2級中使用過的GET作為首個請求。
GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1
Host: royalhope.nhs.uk
但是響應(yīng)中添加了一個新元素。
HTTP/1.1 200 OK
[省略了其他頭的信息……]
<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>
每個就診時間信息現(xiàn)在都包含一個URI,用來告訴我們?nèi)绾蝿?chuàng)建一個預(yù)約。
超媒體控制(Hypermedia Control)的關(guān)鍵在于:它告訴我們下一步能夠做什么,以及相應(yīng)資源的URI。比如,我們事先就可以知道去哪個地址發(fā)送預(yù)約請求,因為響應(yīng)中的超媒體控制直接在響應(yīng)體中告訴了我們該如何做。
預(yù)約的POST請求與第2級中類似。
POST /slots/1234 HTTP/1.1
[省略了其他頭的信息……]
<appointmentRequest>
<patient id = "jsmith"/>
</appointmentRequest>
響應(yīng)包含了一系列的超媒體控制,用來告訴我們后面可以進行什么操作。
HTTP/1.1 201 Created
Location: http://royalhope.nhs.uk/slots/1234/appointment
[省略了其他頭的信息……]
<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"/>
<link 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,后臺開發(fā)團隊就可以根據(jù)需要隨意修改與之對應(yīng)的URI(只有最初的入口URI不能被修改)。
超媒體控制的另一個顯著優(yōu)點是:它能夠幫助客戶端開發(fā)人員進行探索。其中的鏈接告訴了客戶端開發(fā)人員下面可能需要執(zhí)行的操作。它并不會告訴所有的信息,但是至少它提供了一個思考的起點,引導(dǎo)開發(fā)人員在協(xié)議文檔中查看相應(yīng)的URI。
同樣地,它也讓服務(wù)器端的團隊可以通過向響應(yīng)中添加新的鏈接來增加功能。比如,如果客戶端開發(fā)人員發(fā)現(xiàn)了一個之前未知的鏈接,那他們就會知道這個鏈接是服務(wù)器端提供的新的功能。
下面介紹幾種簡潔的REST API設(shè)計的最佳實踐,可以作為真假REST的一個判別依據(jù)。
使用名詞來定義接口。
/resources
/resources/1024
不應(yīng)該使用動詞:
/getAllResources
/createNewResource
/deleteAllResources
如果要改變資源的狀態(tài),要使用PUT、POST和DELETE。下面使用GET方法來修改user的狀態(tài)是錯誤的:
GET /users/711?activate
或
GET /users/711/activate
不要混淆名詞的單復(fù)數(shù)。保持簡單,只用復(fù)數(shù)名詞來定義所有資源。
/cars 代替 /car
/users 代替 /user
/products 代替 /product/settings 代替 /setting
GET /cars/711/drivers/ 返回 711 號 car 的所有 driver 列表
GET /cars/711/drivers/4 返回 711 號 car 的 4 號 driver
客戶端、服務(wù)器都需要知道相互之間的通信格式。這些格式可以定義在HTTP header里面。
·Content-Type定義了請求格式。
·Accept定義了接收相應(yīng)的格式列表。
HATEOAS是REST架構(gòu)風(fēng)格中最復(fù)雜的約束,也是構(gòu)建成熟REST服務(wù)的核心。它的重要性在于打破了客戶端和服務(wù)器之間嚴格的合約,使得客戶端可以更加智能和自適應(yīng),而REST服務(wù)本身的演化和更新也變得更加容易。
下面是一個HATEOAS的例子。
{
"id": 711,
"manufacturer": "bmw",
"model": "X5",
"seats": 5,
"drivers": [
{
"id": "23",
"name": "Stefan Jauker",
"links": [
{
"rel": "self",
"href": "/api/v1/drivers/23"
}
]
}
]}
過濾:
GET /cars?color=red
GET /cars?seats<=2
排序:
GET /cars?sort=-manufactorer,+model
字段選擇:
GET /cars?fields=manufacturer,model,id,color
分頁:
GET /cars?offset=10&limit=5
版本號使用簡單的序號,并避免點號,如2.5等。正確用法如下。
/blog/api/v1
HTTP狀態(tài)碼(HTTP Status Code)是用來表示網(wǎng)頁服務(wù)器HTTP響應(yīng)狀態(tài)的3位數(shù)字代碼。它由RFC 2616規(guī)范定義,并得到RFC 2518、RFC 2817、RFC 2295、RFC 2774、RFC 4918等規(guī)范的擴展。
在設(shè)計API處理錯誤時,應(yīng)該充分使用HTTP狀態(tài)碼,而不是簡單地拋出一個“500-Internal Server Error(內(nèi)部服務(wù)器錯誤)”。所有的異常都應(yīng)該有一個錯誤的payload作為映射,下面是一個例子。
{
"errors": [
{
"userMessage": "Sorry, the requested resource does not exist",
"internalMessage": "No car found in the database",
"code": 34,
"more info": "http://dev.mwaysolutions.com/blog/api/v1/errors/12345"
}
]
}
文章轉(zhuǎn)自微信公眾號@IT大咖說