我選擇的案例很簡單,就是《應用Rails進行敏捷開發》一書中的depot應用。為了簡化問題,我只使用了其中的store控制器部分,并將這一部分改寫成REST+RIA的方式。具體地說,就是:
1、重寫一個控制器(我把它稱為RestStore),它的功能和Store控制器類似,但只提供RESTful風格的接口;
2、表示層部分用Flex重寫。該層與RestStore控制器通信以獲取必要的后臺數據;
為了比較的目的,以下將《應用Rails進行敏捷開發》所使用的“經典”方法(Rails作為后臺,rhtml作為表現層)稱為方案一,將我所試驗的方法(RESTful Rails作為后臺,Flex作表現層)稱為方案二。除了結構上的差異,這兩個方案所實現的功能和界面基本上是完全相同的。
代碼量比較
方案一:
Controllers(1個文件,57行)
Helpers(1個文件,9行)
Models(5個文件,85行)
Views(6個文件,93行)
CSS(1個文件,227行)
------------------------------------------
共計14個文件,471行
方案二:
Controllers(1個文件,37行)
Models(3個文件,39行)
Views(3個文件,15行)
MXMLs(3個文件,278行)
ActionScripts(4個文件,142行)
CSS(1個文件,76行)
--------------------------------------------
共計15個文件,587行
方案二比方案一要大概多出25%的代碼,這主要是因為前后臺采用了不同的技術,所以必須寫一些用于轉換數據格式的方法。
需要說明的是,由于沒有詳細統計注釋和空行,因此這個數字并不十分精確,只能作為一個數量級上的參考。但是代碼量的分布上還是能看出一些問題來的。方案二比起方案一而言,服務器部分得到了很大的簡化,原因在于(1)視圖部分只提供XML數據,不再負責處理界面;(2)購物車移到客戶端來實現,后臺不再需要Cart和相關的類。
此外,方案一的CSS文件相當長,而方案二的CSS就要小得多。這是因為方案一的CSS也包括許多用于頁面布局的部分,而在Flex中,布局是用布局容器來實現的,CSS只負責組件樣式。這就使得Flex中MXML比較大而CSS可以稍小一些。(我個人的看法是使用布局容器進行整體布局比用CSS更加合理)
通信量比較
在客戶端使用如下操作步驟,這些操作覆蓋了控制器所實現的所有功能。使用HttpLook軟件記錄并比較兩個方案與服務器通信的次數和通信數據量大小。
1、瀏覽首頁
2、添加貨品1
3、清空購物車
4、添加貨品2
5、添加貨品3
6、結賬
7、不輸入任何數據就提交
8、輸入正確的數據并提交
以下為比較結果。括號內為服務器返回的HTTP body大小,如果有兩個數字,則分別表示請求數據和應答數據的大小。
比較結果不包含貨品的圖片,因為這部分數據量對所有方案都是一樣的。
方案一 | 方案2 | |
1 | GET /store(9834) GET /stylesheets/depot.css(3263) GET /javascripts/prototype.js(71260) GET /javascripts/effects.js(38200) GET /javascripts/dragdrop.js(30550) GET /javascripts/controls.js(28911) GET /javascripts/application.js(148) GET /images/logo.png(1070) GET /images/favicon.ico(0) |
GET /depotclient.html(4308) GET /AC_OETags.js(7826) GET /history.js(1292) GET /history.html(1272) GET /depotclient.swf(298104) GET /history.swf(2656) GET //favicon.ico(0) GET /rest_store/list_pay_types.xml(157) GET /rest_store/list.xml(5281) |
2 | POST /store/add_to_cart/11(1851) | |
3 | POST /store/empty_cart(93) GET /store(9834) GET /stylesheets/depot.css(0) |
|
4 | POST /store/add_to_cart/5(1881) | |
5 | POST /store/add_to_cart(1(2065) | |
6 | POST /store/checkout(2718) GET /images/logo.png(0) |
|
7 | POST /store/save_order(94,3217) | |
8 | POST /store/save_order(129,93) GET /store(9891) |
POST /rest_store/save_order(300,21) |
可以看到兩種方案在通信方式上的巨大差異。類似的地方是,兩種方案初次瀏覽時都需要加載相當多的附件,不同之處在于方案二要加載的東西中有一個相當大的swf文件,其他內容則都比較小。而方案一則也需要加載幾個比較大的JS文件,這些JS文件大多是Ajax所需要的。
在后續過程中,方案二的優點就體現出來了:由于購物車是在客戶端處理的,結賬的表單也嵌入在SWF中,因此方案二可以在絕大部分時間不必與服務器通信,只在最后提交表單的時候需要再次請求服務器。方案一雖然每次請求的數據量都不大,但是請求次數相當頻繁,對于服務器來說負擔仍然相當沉重,而且請求次數越多,由于網絡問題等原因出錯的可能性也越大。
仔細觀察表中的數據也能看出一些有意思的結論。請看方案一中的步驟2和步驟3:步驟3清空購物車使用的傳統的Web方法,即向服務器發出請求,然后重新載入整個頁面;步驟2添加一件貨品,使用的是Ajax方法。兩個步驟其實數據量差不多大小,步驟2還要多出一個product_id參數,但是無論從請求數量(1:3)還是數據量大小(1851:9927)都表明Ajax比傳統的全頁面刷新方法要優越得多。不過在這個方面,RIA走得更遠:完全不需要向服務器請求,客戶端本身就可以完成相當一部分工作。
結論
REST+RIA是一種新興的Web應用結構,這種結構具有如下的優點:
1、將表現層與后臺徹底分離
傳統的Web表現層技術總是依賴于特定的服務器編程語言的。比如你用JSP編寫頁面,就意味著服務器后臺必須使用Java,如果后來決定改用ROR,那么除了用rhtml重寫表現層以外大概沒有什么更好的辦法。而使用RESTful風格的接口意味著服務器只需要提供資源,不論后臺使用Java,Ruby或.Net來實現,對表現層都完全沒有影響,哪怕把后臺整個換掉也不需要重寫表現層,這也意味著表現層是完全可重用的。考慮到真實的項目中對表現層的修改往往是最麻煩且工作量極大的部分,這項分離意義重大。
要將表現層與后臺分離,其實也有其他的一些技術方案可選。以前出現過的兩種比較常見的辦法:(1)服務器提供XML數據,然后用XSLT轉換為HTML或其他格式;(2)服務器提供Web Service接口,用支持WS的客戶端訪問。不過這兩種方法可能也存在一定的問題,最后并沒有真正流行開來。Web Service更多的用在異構系統整合而不是系統內部通信上。
2、方便程序員和美工協同開發
在頁面中嵌入代碼是好是壞?問題不在于代碼本身,而在于這樣一來美工和程序員的工作難以協調,任何一方做出修改,都必須復核自己的修改是否破壞了對方的工作。RESTful風格的接口只提供資源數據,不暴露服務器上的任何實現細節,這對美工和程序員來說絕對是個好消息:只需要約定服務器提供數據的格式,美工就可以完全按照自己的節奏去設計和測試頁面,程序員也可以專心實現后臺邏輯,彼此都不需要擔心破壞對方的工作,可以實現完全的并行開發,這是以往任何Web開發技術都沒能做到的。
3、有利于采用快速原型的開發方式
在上面已經提到過,在這種方式下,美工和程序員可以完全并行工作。即使在還沒有實現任何后臺邏輯之前,只要先寫一個符合格式的Xml占位文件,表現層就可以開始設計,不論后臺開發速度快慢,表現層都可以盡快提供一個可以工作的原型,對于盡早審核需求和獲得用戶反饋都是非常有利的。
4、合理分配負載,減輕服務器壓力
大多數Web應用在負載的分配問題上其實是非常不合理的:服務器承擔著成千上萬的用戶請求,每時每刻都在忙碌之中,而客戶端機器在這個時間里只是傻傻的等待響應,根本無事可做。從前面的測試結果可以看到,RIA的方案需要一次性下載較大的數據,但在這之后客戶端可以承擔相當一部分工作,避免頻繁請求服務器,不僅在資源分配上更加合理,也能夠讓服務器同時承載更多的用戶。
5、提高用戶體驗
減少服務器請求不僅是資源問題,也關系到用戶體驗,頻繁的頁面閃爍是相當糟糕的。Ajax可以在相當程度上緩解這一問題,
但是Ajax并不太適用于大范圍的頁面變化和頁面跳轉,一般的Web應用中使用頁面跳轉的比例仍然遠高于使用Ajax的比例。當網絡情況不佳或服務器繁忙時,等待服務器響應也是很惱人的事情。
一個RIA(如Flex)應用中通常包括多個頁面,在頁面之間切換不會有停頓,甚至不太大的應用可以One page one application,更不必擔心愉快的沖浪過程中突然拋給你一個404或500錯誤(這對心臟相當有好處)。
(PS:不過世界上的事情有時候也難說。我的確知道有這么一些人,他們覺得看瀏覽器頁面閃動的白屏比看Flash的平滑窗口漸變更舒服。除了習慣成自然外,我實在很難想出有什么別的理由可以解釋這個問題)
但是這種開發方式也有一些問題:
1、至少在目前,Flex面臨著和瀏覽器一樣的限制——只支持GET/POST調用,還不能完整支持REST所要求的全部動詞。雖然這個問題有一些臨時的解決辦法,不過需要在后臺和表現層上都必須寫一些hack代碼,并不是很優雅。上面的例子也僅使用了GET/POST調用,還不能算是很嚴謹的REST方案。我們期待下一個版本能解決此一問題;
2、這種方式的開發代碼量要略大于純ROR的方法。Flex中使用的Actionscript語法比較類似于Java,仍然需要寫很多大括號,對于循環也只能用笨拙的for ...。動態語言在生產力方面的優越性還是相當明顯的。當然,比起Java或ASP.Net來說,Flex的代碼量或許已經算是很少了。
3、關于如何組織Flex程序,目前還沒有一致的意見,因此在Flex程序中還沒法體現慣例優于配置的原則,仍然要編寫一些自定義代碼;
4、Flex編譯后的程序通常比較大(普通規模的一般有200~300K,嵌入資源的話更多),在低速網絡上首次下載會比較慢。據說Flex 3已經在著手解決此問題。
感想:
RIA(這里的RIA特指Adobe Flex和M$ WPF/Silverlight等,不包括Ajax)現在還是一種褒貶不一的技術。贊賞者認為,RIA結合了B/S結構和C/S結構的優點,安裝部署的代價接近于前者而表現力上接近于后者;批評者則認為RIA在兩方面都算不上最好:部署上不如B/S(需要安裝瀏覽器插件)而表現力不如C/S(因為安全原因不能訪問全部本地資源)。其實這兩種說法都有道理。世上沒有一種技術能具有所有的優點而沒有缺點,也沒有一種技術能包打天下。DHH說RIA沒用,現代的Web技術足夠發達了,我覺得此說不確。目前的Web應用或許有80%用HTML加上一些Ajax就夠了,但余下的那20%呢?那些是用純Web方案很難甚至沒辦法解決的領域,雖然少,但是硬骨頭也要有人來啃。
因為REST和RIA目前都不能算比較成熟的技術,即使在這個很小的例子里也暴露出一些問題,大規模采用這種解決方案還是不太可行的。不過這些問題大多已經有人開始想辦法去克服,相信未來會逐步得到解決。
事實上在這個簡單的例子里我已經感覺到這種方案“分離關注點”帶來的好處。在ROR的方案里,我需要同時考慮:在Controller里面提供什么數據,在View中怎么展示這些數據,思路總是在Model,Controller和View中來回切換。但是在REST+RIA的方案下,我可以在實現Controller的時候完全不考慮表示層,在實現界面的時候也完全不用考慮后臺,思路非常集中,也容易專注于去思考一些設計上的問題。這是一次很好的開發體驗,也讓我有足夠的信心說:盡管還存在一些問題,但是REST+RIA的方案必將有著光明的前途。