從"SOAP"到"REST" 程序員
最近有不少同仁問我,咱們爲何要用REST?REST比SOAP好在哪?對於這個問題我想了不下十種答案。但轉念一想,如何以一種最直接,相似於武俠小說中"一劍封喉"般的方式,"穩準狠"的解答他們的問題。就不至於就此展開一場"辯論賽"或是"科普貼"。好在問我這個問題的同仁大都不是程序猿界的"小鮮肉",你們對於時下軟件研發的基本理念仍是有共同認知的,基於此,我對這個問題的標準答案就是:"REST更OO"。架構
若是您還有興趣深刻了解REST和SOAP的區別,我以爲仍是自行Google吧。spa
這篇文章想給你們介紹的重點更偏重於:咱們如何從"SOAP"轉向"REST",以及什麼樣的"RESTful API"纔是真正的REST。設計
雖不贅述SOAP和REST的概念,但仍是簡單介紹一下何爲SOAP,何爲REST:code
SOAP(Simple Object Access Protocol):簡單對象訪問協議對象
REST(Rerepresentational State Transfer):表示性狀態轉移接口
你在網上能搜到的異同點大體分爲主要三個方面:進程
其實SOAP和REST嚴格來講不是兩個對等的概念,咱們姑且理解成兩種服務設計思想及其具體的實現架構。資源
若是說SOAP和REST不夠具象,那我來找兩位代言人,自行感覺一下咱們所謂的"SOAP風格"和"REST風格"。it
Web Service或是WCF你們想必不陌生,而Web Service和WCF就是利用SOAP協議進行的服務實現(固然他們也支持REST,但很雞肋,不足以代言)。咱們要對外提供服務離不開相似的技術。那咱們是如何設計這樣的服務接口的呢。提及來真是輕車熟路:咱們先定義對象,然後咱們設計接口。更有甚者,咱們直接定義服務接口。因此一說到Web Service或是WCF咱們就會想到接口,咱們一看到接口,咱們就很是的舒服。那咱們爲何舒服,由於這很後臺。進程間的接口調用方式老是會給程序員們以莫名的親切感。
熟悉Web API風格的朋友可能就能想到REST。可Web API的服務應該怎麼設計?若是咱們還用上面說到設計Web Service/WCF的"SOAP風格"進行服務的設計,那REST比SOAP還有什麼優點?僅僅是更輕量級的優點?
咱們來舉個例子:一隻喵兒餓了,他要獲取食物,食物分別有魚和熊掌。因此咱們要提供一個服務是食物的獲取。基於這個簡單的小例子,咱們來看看從"SOAP"到"REST"的轉型之路。
第一階段:
咱們提供一個爲喵兒提供食物的REST服務。接到任務咱們開始進行業務分析,最後肯定2個方法:吃魚和吃熊掌,對!就是這樣一個簡單粗暴的方法,咱們想讓喵兒調用這些方法,調用EatingFish能夠得到魚,調用EatingBearpaw能夠得到熊掌。
咱們的思路是:定義對象 --> 定義方法 改寫成RESTful風格
很快RESTful API 被定義好了:Get http://service-root/EatingFish和Get http://service-root/EatingBearpaw
喵兒只要用GET請求這些URI就能獲得食物。
然而這一版設計很快宣告失敗。並非咱們對服務的功能定義出了問題,而是問題出在這個服務"很不RESTful",緣由是:沒有站在資源的角度考慮RESTful API的定義,而是延續了"接口"定義的"SOAP風格"。這樣從"接口"入手,而後將"接口"以REST的URI形式暴露出來的API仍然是很"SOAP"的,這樣定義出的RESTful API,通常是Level 0或Level 1的。
那咱們就來介紹一下REST的4個境界,我也稱之爲REST的"成熟度模型"。
Level 0:沒有明確的資源概念,只有一個URL,只是用單個HTTP方法
Level 1:有明確的資源概念,存在不少URL,只是用單個HTTP方法
Level 2:有明確的資源概念,存在不少URL,使用HTTP做爲資源的統一接口
Level 3:在知足2級標準的基礎上,使用超媒體做爲應用狀態的引擎
咱們再來回過頭看看咱們的表達Get http://service-root/EatingFish,無疑是Level 0。
即使是咱們生硬的在"EatingFish"前將"Food"表達出來也不過是Level1的表達:Get http://service-root/Food/EatingFish
分別參照一下Level 0和Level 1的定義,咱們發現Level0的表達沒有"資源"的概念,仍然是提供了一個"行爲"的"接口",這種作法像極了"SOAP"。咱們再來看看Level 1的表達,雖然已經明確出了"Food"這個資源,Food資源下也能夠定義多個針對Food的方法,但仍然定義的是諸如"EatingFish"和"EatingBearPaw"這樣的一個個行爲來做爲資源的方法。
第二階段:
咱們從新思考,要站在"資源"的角度考慮這個服務,而資源的CRUD又能夠經過HTTP的POST、GET、PUT和DELETE請求表達。也就是說咱們只要把"資源"表述清楚,利用REST的理念CRUD咱們就沒必要考慮了。基於這樣的考慮,咱們趕快調整思路:定義資源 --表述資源
資源定義:
咱們定義了三個資源"Food"做爲食物的集合,而"Food"下,有"Fish"和"BearPaw",對於資源的表述咱們採起了Collection+Json的超媒體格式。
資源表述:
{
"collection": {
"version": "1.0",
"href": "http://service-root/Food",
"links": [ ],
"items": [
{
"href": "/Fish",
"data": [
{
"name": "Name",
"value": "魚"
},
{
"name": "Code",
"value": "Fish"
}
],
"links": [ ]
},
{
"href": "/ BearPaw ",
"data": [
{
"name": "Name",
"value": "熊掌"
},
{
"name": "Code",
"value": " BearPaw "
}
],
"links": [ ]
}
],
"queries":[ ],
"template":{
"data": [
{
"name": "Name",
"value": ""
},
{
"name": "Code",
"value": ""
}
]
},
"error": {
"code": "",
"Message": ""
}
}
}
根據資源的定義,經過Collection+Json這種超媒體類型進行資源的表述。使用HTTP協議語義規定的請求類型做爲資源的統一接口,不須要再像Level 0 和Level 1中那樣單獨定義或描述接口。
這時喵兒若是想要吃魚,只須要GET http://service-root/Food/Fish他就能得到魚。而若是咱們想要從食物中把熊掌刪除掉,也只須要 DELETE http://service-root/Food/BearPaw。若是咱們想幫喵增長一種食物-肉,只須要POST http://service-root/Food/Meat,同時利用資源表述中的模板template,將肉的信息傳回去,肉這種食物就被添加進了食物中。
這時咱們發現咱們再去思考服務的設計已經不是站在"要提供什麼樣的方法"這種基於過程、基於行爲的角度去思考。而是基於"資源"的面向對象的設計方法。
作到這個程度,感受已經很是的RESTful了,但回到REST的本意看看,就體會出了問題的所在。回觀REST的定義:REST(Rerepresentational State Transfer)表示性狀態轉移,多讀幾遍咱們就漸漸的感受到了一個詞:"轉移"。
而再對照"成熟度模型"咱們發現這時候的API已是Level 2的。Level 2到Level 3,如何表示"轉移"成了下一個階段進階的關鍵。
爲了更好的展示出第三個階段的特色,咱們如今擴充一下這個小例子,在喵兒獲取過食物以後,他還想來點甜點,甜點有蛋糕和冰激凌。(真是隻貪得無厭的喵兒)
第三階段:
繼續調整思路:定義資源 --定義資源的連接關係 --> 資源表述
第一步和第三步與第二階段沒有什麼差異,而關鍵在於第二步,REST的本意但願可以經過資源的表述,描述出每一個資源與其餘資源的連接關係。
回到咱們的例子,咱們定義資源,這時候會有兩棵樹,"食物"和"甜點":
而接下來,咱們但願喵兒在得到到食物以後,可以知道接下來有甜點吃。這時咱們引入了資源的狀態圖:
資源的狀態圖表述的是從當下的資源可以連接到哪一個資源。咱們能夠看到當獲取到Fish以後,喵兒就能看到有Desserts,當他訪問Desserts時,就能看到爲他準備好的Cake和Ice Cream了。
那這種連接如何表述呢?
咱們觀察到不論是REST的哪種超媒體格式,都爲咱們準備了Link字段:
{
"collection": {
"version": "1.0",
"href": "http://service-root/Food ",
"links": [ ],
"items": [
{
"href":"/Fish",
"data": [
{
"name":"Name",
"value":"魚"
},
{
"name":"Code",
"value":"Fish"
}
],
"links": [
{
"rel":"Desserts",
"href":" http://service-root/Desserts",
"prompt": "甜點"
}
]
},
{
"href":"/ BearPaw ",
"data": [
{
"name":"Name",
"value":"熊掌"
},
{
"name":"Code",
"value":" BearPaw "
}
],
"links":[
{
"rel":"Desserts",
"href":" http://service-root/Desserts",
"prompt": "甜點"
}
]
}
],
"queries": [ ],
"error": {
"code": "",
"Message": ""
}
}
}
在Level 2 的基礎上,利用Collection+Json中對Links的表達,表述了資源間轉移的關係。若有必要同時利用Queries表達了對資源集合的過濾。至此咱們完成了從"SOAP"到"REST"的轉變。
一些注意:
1、 定義資源不是在定義邏輯模型,更不是E-R;
2、 一個服務中描述的資源不必定是同一根的,"轉移"也能夠發生在兩棵樹之間。
3、 一旦出現了不用Level0 和Level 1就表達不了的狀況,基本上就是資源的定義出了問題
4、 REST並非"銀彈",它解決不了你資源(對象)設計自己的問題。