REST總結
REST(Representational State Transfer)是代表狀態傳輸的縮寫,它代表了分佈式超媒體系統的體系結構風格,它是一種針對網絡應用的設計和開發方式,可以降低開發的複雜性,提高系統的可伸縮性。REST提出了一些設計概念和準則:
1.網絡上的所有事物都被抽象爲資源(resource);
2.每個資源對應一個唯一的資源標識(resource identifier);
3.通過通用的連接器接口(generic connector interface)對資源進行操作;
4. 對資源的各種操作不會改變資源標識;
5.所有的操作都是無狀態的(stateless)。
對於當今最常見的網絡應用來說,resource identifier是url,generic connector interface是HTTP,第4條準則就是我們常說的url不變性。這些概念中的resouce最容易使人產生誤解。resouce所指的並不是數據,而是數據+特定的表現形式(representation),這也是爲什麼REST的全名是Representational State Transfer的原因。舉個例子來說,“本月賣得最好的10本書”和“你最喜歡的10本書”在數據上可能有重疊(有一本書即賣得好,你又喜歡),甚至完全相同。但是它們的representation不同,因此是不同的resource。
REST之所以能夠簡化開發,是因爲其引入的架構約束,比如Rails 1.2中對REST的實現默認把controller中的方法限制在7個:index、show、new、edit、create、update和 destory,這實際上就是對CURD的實現。更進一步講,Rails(也是當今大部分網絡應用)使用HTTP作爲generic connector interface,HTTP則把對一個url的操作限制在了4個之內:GET、POST、PUT和DELETE。
REST之所以能夠提高系統的可伸縮性,是因爲它強制所有操作都是stateless的,這樣就沒有context的約束,如果要做分佈式、做集羣,就不需要考慮context的問題了。同時,它令系統可以有效地使用pool。REST對性能的另一個提升來自其對client和server任務的分配:server只負責提供resource以及操作resource的服務,而client要根據 resource中的data和representation自己做render。這就減少了服務器的開銷。
REST特點
* 客戶端-服務器:拉風格的交互模式:消費方組件把表示拉出來
* 無狀態:每個從客戶端到服務器的請求必須包含足夠的信息使得服務器理解該請求,不需要服務器上存儲的上下文信息
* 緩存:爲了提高網絡效率,響應應該可以被標示成可緩存的或是不可緩存的
* 統一的接口:所有資源通過通用的接口訪問(HTTP GET, POST, PUT, DELETE)
* 命名資源:系統由通過URL命名的資源組成
* 互連的資源表示:資源的表示通過URL互相聯繫起來,從而客戶端可以從一個狀態轉換到另一個
* 層次化的組件:中間件,比如代理服務器,緩存服務器,網關等,可以插入到客戶端和資源中間來完成安全和安全等功能
REST設計原則
1. 構建REST的關鍵是識別出所有需要暴露成服務的概念層實體。
2. 爲每個資源創建URL。
3. 對資源進行分類,區分出哪些客戶端只能獲取表示,哪些是可以修改的。前者使用HTTP GET來訪問,後者使用HTTP POST, PUT和DELETE來訪問。
4. 所有通過HTTP GET訪問的資源必須是沒有副作用的。
5. 沒有表示是作爲孤島存在的。在資源表示中放入超鏈接。
6. 逐步的暴露數據。不要在單一的文檔中暴露所有數據。提供超鏈接來獲取更多的信息。
7. 使用schema來指定響應的格式。
8. 說明該服務是使用WSDL文檔來調用,而是簡單的HTML文檔。
REST優點
可以利用緩存Cache來提高相應速度
通信本身的無狀態性可以讓不同的服務器處理一系列請求中的不同請求,提高服務器可擴展性
瀏覽器即可作爲客戶端,簡化軟件需求
相對於其他疊加在HTTP協議之上的機制,REST的軟件依賴性更小
不需要額外的資源發現機制
在軟件技術演進中長期的兼容性更好
二、使用REST
REST 和MVC的關係。
第一個問題假設REST是我們應該採用的架構,然後討論如何使用;第二個問題則要說明REST和當前最普遍應用的MVC是什麼關係,互補還是取代?
我們先來談談第一個問題,如何使用REST。我感覺,REST除了給我們帶來了一個嶄新的架構以外,還有一個重要的貢獻是在開發系統過程中的一種新的思維方式:通過url來設計系統的結構。根據REST,每個url都代表一個resource,而整個系統就是由這些resource組成的。因此,如果url是設計良好的,那麼系統的結構就也應該是設計良好的。對於非高手級的開發人員來說,考慮一個系統如何架構總是一個很抽象的問題。敏捷開發所提倡的 Test Driven Development,其好處之一(我覺得是最大的好處)就是可以通過testcase直觀地設計系統的接口。比如在還沒有創建一個class的時候就編寫一個testcase,雖然設置不能通過編譯,但是testcase中的方法調用可以很好地從class使用者的角度反映出需要的接口,從而爲 class的設計提供了直觀的表現。這與在REST架構中通過url設計系統結構非常類似。雖然我們連一個功能都沒有實現,但是我們可以先設計出我們認爲合理的url,這些url甚至不能連接到任何page或action,但是它們直觀地告訴我們:系統對用戶的訪問接口就應該是這樣。根據這些url,我們可以很方便地設計系統的結構。
重申一遍:REST允許我們通過url設計系統,就像Test Driven Development允許我們使用testcase設計class接口一樣。
OK,既然url有這樣的好處,那我們就着重討論一下如何設計url。網絡應用通常都是有 hierarchy的,像棵大樹。我們通常希望url也能反映出資源的層次性。比如對於一個blog應用:/articles表示所有的文章, /articles/1表示id爲1的文章,這都比較直觀。遺憾的是,網絡應用的資源結構永遠不會如此簡單。因此人們常常會問這樣一個問題:RESTful的url能覆蓋所有的用戶請求嗎?比如,login如何RESTful?search如何RESTful?
從REST的概念上來看,所有可以被抽象爲資源的東東都可以使用RESTful的url。因此對於上面的兩個問題,如果login和search可以被抽象爲資源,那麼就可以使用RESTful的url。search比較簡單,因爲它會返回搜索結果,因此可以被抽象爲資源,並且只實現index方法就可以了(只需要顯示搜索結果,沒有create、destory之類的東西)。然而這裏面也有一個問題:search的關鍵字如何傳給 server?index方法顯然應該使用HTTP GET,這會把關鍵字加到url後面,當然不符合REST的風格。要解決這個問題,可以把每次search看作一個資源,因此要創建create和 index方法,create用來在用戶點擊“搜索”按鈕是通過HTTP POST把關鍵字傳給server,然後index則用來顯示搜索結果。這樣一來,我們還可以記錄用戶的搜索歷史。使用同樣的方法,我們也可以對 login應用REST,即每次login動作是一個資源。
讓我再說明一下我的想法:如果每個用戶需求都可以抽象爲資源,那麼就可以完全使用REST。
由此看來,使用REST的關鍵是如何抽象資源,抽象得越精確,對REST的應用就越好。因此,如何改變我們目前根深蒂固的基於action的思想是最重要的。
三、 REST與MVC
REST會取代MVC嗎?還是彼此是互補關係(就像AOP對於OOP)?答案是It depends!。當然,這是非常理想的論斷。可能我們無法找到一種方法可以把所有的用戶需求都抽象爲資源,因爲保證這種抽象的完整性(即真的是所有需求都可以)需要形式化的證明。而且即使被證明出來了,由於開發人員的能力和喜好不同,MVC肯定也會成爲不少人的首選。但是對於希望擁抱REST的人來說,這些都沒有關係。只要你開發的系統所設計的問題域可以被合理地抽象爲資源,那麼REST就會成爲你的開發利器。
到目前爲止,傳統的Java Web MVC框架(Struts、WebWork、Spring MVC、etc.)還無法很好地支持REST風格的架構設計。它們在設計之初時基本上都是圍繞着基於HTML表單的交互模式來設計的,View的粒度難以達到單個頁面以下。不能把響應Ajax請求而返回的XML/JSON/純文本格式的數據簡單地認爲是Web MVC框架中的View,實際上這個時候這些數據的語義已經與傳統的Web MVC架構中的View的語義相距甚遠。
傳統的Web MVC框架一個最大的問題是它們看待URL的方式與REST有很大的差別。REST把服務器端所有的URL都當作是抽象的資源(相當於是面向對象設計中的接口),雖然Web MVC也強調不應該將硬編碼的URL(例如:http://www.xxx.com/yyy/zzz/abcd.jsp )直接暴露出來,而應該通過某個 Controller來暴露,將這個頁面配置爲Controller的一個View,但是Web MVC框架並沒有有意識地將URL當作抽象的資源來看待和設計。還有一個較爲嚴重的問題是傳統的Web MVC框架基本上都只支持GET和POST兩種HTTP方法,而不支持PUT和DELETE方法,而Servlet API是支持上述所有的HTTP方法的(支持doPut和doDelete方法)。好在不需要我們親自來做這個事情,已經有一些Java的服務器端框架可以支持REST了。例如restlet(http://www.restlet.org )
四、REST與Ajax
傳統的服務器端 Web 應用程序將數據的標識和服務器上的動態數據元素合併在了一起,並將所構成的完整HTML文檔返回給瀏覽器,違法了REST無狀態服務器的原則,導致服務器端必須保存大量會話狀態,耗費服務器資源,削弱了應用程序的可伸縮性,並使得應用程序部署到集羣環境更加困難,也使得分佈式緩存變爲不可能。
Ajax 應用程序在其主要 UI 和瀏覽器中的主要邏輯方面有所不同;基於瀏覽器的應用程序代碼可以在必要時獲取新的服務器數據,並將這些數據織入當前頁面。呈現和數據綁定的位置看起來可能是一個實現細節,但是這種區別會導致完全不同的架構風格。利用Ajax我們可以將用戶狀態信息保存在客戶端,而非服務器,也就是說我們可以把ajax看做是一種REST無狀態原則的具體實現技術,通過REST與Ajax的結合,我們的web應用程序會更加完善。
利用有狀態 Web 客戶機的優點
人們通常將 Ajax 應用程序描述成無需在每次點擊時徹底地刷新整頁的Web頁面。儘管這個描述非常確切,但是根本的動機在於徹底刷新整頁會令用戶不耐煩,從而無法獲得愉快、融入式的用戶體驗。從架構的角度來看,整個頁面全部刷新的設計甚至非常危險,這種設計使您無法選擇在客戶機存儲應用程序狀態,這可能會導致妨礙應用程序充分利用 Web 最強大的架構設計點的設計決策。Ajax 讓我們不需要進行完全刷新就可以與服務器進行交互,這一事實使有狀態客戶機再次成爲可用選擇。這一點對於動態融入式 Web 應用程序架構的可能性有深遠的影響:由於應用程序資源和數據資源的綁定轉換到了客戶端,因此這些應用程序都可以享受這兩個世界中最好的東西 —— 融入式 Web 應用程序中動態、個性化的用戶體驗,以及遵守REST準則的應用程序中簡單、可伸縮的架構。
緩存 Ajax 引擎
Ajax 引擎一個有趣的特徵就是:儘管它包含了很多應用程序邏輯和表示框架元素,但是如果經過恰當的設計,它可以不包含任何業務數據或個性化內容。應用程序和表示都凍結在部署時。在典型的Web 環境中,應用程序資源可能 6 個月纔會變更一次。這意味着負責隔離應用程序資源和數據資源的 Ajax 引擎是高度可緩存的。
由於 Ajax 應用程序引擎只是一個文件,因此它也是可以使用代理緩存的。在大型的企業內部網中,只要有一名員工曾經下載過某個特定版本的應用程序的 Ajax 引擎,其他任何人都可以從內部網網關上上獲取一個緩存過的拷貝。
因此對於應用程序資源來說,經過良好定義的 Ajax 應用程序引擎符合 REST 準則,與服務器端 Web 應用程序相比,它具有顯著的可伸縮性優勢。
緩存 Ajax 數據
用戶瀏覽一個 Ajax Web 站點,加載 Ajax 應用程序引擎,最好是從瀏覽器緩存中加載的,否則就從本地代理服務器加載。那麼對於業務數據來說情況如何呢?由於應用程序邏輯和狀態都在瀏覽器上駐留並執行,因此應用程序與服務器的交互就與傳統 Web 應用程序的方式有很大的不同。不需要獲取混合的內容頁面,只需要獲取業務數據即可。
現在我們考慮一下這個操作在(假想的)Ajax 版本的應用程序中的情況。對於 “最近查看的內容” 並不需要進行處理。當我們點擊某個鏈接時,這些在頁面上已經存在的信息並不會消失。有兩個請求很可能會與設計模式的書籍有關:
* /Books/0201633612(其中 0201633612 是設計模式書的 ISBN 號)
* /PurchaseHistory/0201633612/[email protected]
第一個假定的請求會返回有關書籍的信息(作者、標題、簡介等);其中並沒有包含特定於用戶的數據。非特定於用戶的數據意味着當更多用戶請求相同的資源時,很可能會從 Internet 上的中間節點上來檢索緩存版本,而不是從原始服務器上檢索這些資源。這種特性會降低服務器和總體網絡負載。另外一方面,第二個請求包含了特定於用戶的信息(Bill Higgins 的購買該書的歷史記錄)。由於這些數據包括一些個性化信息,因此只有一名用戶需要從這個 URI 中獲取並緩存數據。儘管這種個性化數據並沒有非個性化數據的可伸縮特性,但是重要的問題是這些信息都是直接從 URL 中獲取的,因此都具有這樣的正面特徵:它們都不會妨礙其他可緩存的應用程序和數據資源的緩存。
Ajax 和健壯性
Ajax 架構風格的另外一個優點是它可以輕鬆處理服務器的故障。正如我們前面介紹的一樣,具有融入式用戶體驗的服務器端 Web 應用程序通常會在服務器上保存大量的用戶會話狀態。如果服務器發生了故障,會話狀態就丟失了,那麼用戶就會體驗到非常奇怪的瀏覽器行爲(“爲什麼我又回到主頁上來了?我的購物車中的東西都到哪裏去了?”)。在採用有狀態客戶機和無狀態服務的 Ajax 應用程序中,服務器崩潰/重新啓動對於用戶來說都是完全透明的,因爲服務器崩潰不會影響會話狀態,這些都保存在用戶的瀏覽器中;無狀態服務的行爲是冪等的,可以由用戶請求的內容來單獨確定。
五、架構風格對比
目前基於網絡應用的架構風格主要有三種:
RPC架構風格 將服務器看作是由一些過程組成,客戶端調用這些過程來執行特定的任務。SOAP就是RPC風格的一種架構。過程是動詞性的(做某件事),因此RPC建模是以動詞爲中心的。
分佈式對象架構風格 認爲服務器是由一些對象和對象上的方法組成,客戶端通過調用這些對象上的方法來執行特定的任務。並且客戶端調用這些對象上的方法應該就像是調用本地對象上的方法一樣,這樣開發就可以完全按照統一的面向對象方法來做。但是很可惜,這樣的抽象並不是很有效,因爲分佈式對象與本地對象存在着巨大的本質差別,想要掩蓋這些差別很多時候甚至是有害無益的。
REST架構風格 將服務器抽象爲一組離散資源的集合。資源是一個抽象的概念,而不是代表某個具體的東西。注意:要真正理解REST,就一定要增強自己的抽象思維能力,充分理解到資源是抽象的。資源是名詞性的,因此REST建模是以名詞爲中心的。
上述是目前基於網絡的應用的主要的三種抽象方式。這三種不同的抽象方式會嚴重影響客戶端與服務器的交互模式,而不同交互模式的交互效率差別相當大。分佈式對象的交互模式很多時候效率很低,因爲掩蓋了分佈式對象與本地對象的差別,很多時候都會導致細粒度的API。實踐已經證明,與RPC和分佈式對象相比,REST是一種對於服務器更加有效的抽象方式,將會帶來粒度更大和更有效率的交互模式。這樣的效果與Fielding設計REST的初衷是吻合的,REST就是專門爲交互的性能和可伸縮性進行過優化的一種架構風格。而SOAP在設計的時候優先考慮的從來不是性能和可伸縮性,而是互操作性,其開發效率比較低。
REST的主要優勢其實在於它是一種對於服務器的更加有效的抽象方式。
性能分析:與基於二進制通信的RPC例如RMI對比,REST是基於文本傳輸的,從這點看應該說性能不如前者,但是REST強調通信語義對於中間組件的可見性,這樣可以改善性能和可伸縮性。例如瀏覽器就是一種中間件。瀏覽器可以根據通信的語義來確定數據有沒有發生改變,從而進行有效的緩存。RMI傳輸數據再有效,它的數據也無法做有效的緩存。因此RMI的性能未必比REST好,況且不能緩存會帶來嚴重的可伸縮性問題。