原帖地址:http://www.infoq.com/cn/articles/tilkov-rest-doubts

解答有關REST的十點疑惑

作者 Stefan Tilkov 譯者 徐涵 發布于 2008年5月22日 下午8時13分

社區SOA 主題設計,Web服務,REST 標簽事務,WS-AtomicTransactions,WS-Reliable Messaging,WS-Coordination

在了解過REST之后,你肯定很想知道這個概念在你的實際應用當中究竟能派上多大用場。而且,假如你已經熟悉另一套完全不同的架構 手法的話,那么你擔心“REST或REST式HTTP(RESTful HTTP),是否真的能在實踐中派上用場,還是在介紹性的、‘Hello, World’級場景以外就不靈光了”是很正常的。我將在本文解答人們——尤其是那些深諳基于SOAP/WSDL的Web服務架構手法的人——開始研究 REST時容易產生的關于REST的十點疑惑。

1. REST也許適用于CRUD,但并不適用于“真實的”業務邏輯

這是那些對REST的好處持懷疑態度的人最常見的反應。畢竟,要是你只能create/read/update/delete,那你如何表達更復雜 的應用語義呢?我已經在本系列介紹性的文章中探討過這些被大家所關心的問題了,不過這方面絕對值得進一步討論。

首先,HTTP動詞(verbs)——GET、PUT、POST和DELETE——跟數據庫的CRUD操作并不是一一對應的。例如,POST和 PUT都可用于創建新資源,它們的區別在于:PUT請求是由客戶端決定(被創建或更新的)資源的URI;而POST請求是向一個“集合 (collection)”或 “工廠(factory)”資源發出的,是由服務器來指派URI的。不過無論怎樣,我們回到那個問題:如何應付更復雜的業務邏輯呢?

任何返回一個結果 c 的計算 calc(a, b),都可被轉換為一個標識其結果的URI——例 如 x = calc(2,3) 可被轉換為http://example.com/calculation?a=2&b=3。 初看,這仿佛是完全錯誤的REST式HTTP的用法——我們應當用URIs來標識資源(resources)而不是操作(operations),不是 嗎?沒錯,其實你就是這么做的:http://example.com/sum?augend=2&addend=3 標識的是一個資源,即2加3的結果。在這一特定的(顯然是精心設計的)示例中,用GET來獲取計算結果可能是個好主 意——畢竟它是可緩存的(cacheable),你可以引用它,而且該計算多半是安全的(safe)且代價很小的。

當然,在許多(即便稱不上大多數)情況下,用GET來執行計算也許是會犯錯的。別忘了,GET應當是一個“安全的(safe)”操作,也就是說,假 如客戶端只是通過發出GET請求來跟隨一個鏈接,那么它不承擔任何義務(比如因使用你的服務而向你付費)或責任。所以,在許多其他情況下,“通過POST 請求向服務器提供輸入數據、以便服務器新建一個資源”是更合適的做法。服務器在響應該POST請求時,可以給出結果的URI (而且有可能發起一個重定向把你轉向過去)。這個結果接下來便可被重用、被加入書簽、在獲取時被緩存等等。你基本上可以將這一模型推廣應用到任何產生結果 的操作——這涵蓋幾乎你所能想到的所有操作。

2. 沒有正式的契約與描述語言

從RPC到CORBA,從DCOM到Web服務,我們已習慣于擁有一個“列出操作、名稱及輸入輸出參數類型”的接口描述(interface description)了。沒有接口描述語言的話,REST怎么用呢?

就這一被十分頻繁問到的問題,有三點答復。

首先,假如你決定用XML(這是很普遍的做法)來配合REST式HTTP的話,那么各種現有的XML模式語言(schema languages)(如DTD、XML Schema、RELAX NGSchematron等) 仍舊可供你使用。可以說,一個用WSDL描述的東西,常常有95%的內容并不是跟WSDL相關、而是跟你定義的XML Schema復雜類型(complex types)相關的。WSDL所增加的,大部分是有關操作(operations)及其名稱的——對于REST的統一接口(uniform interface)來說,描述這些是頗為無趣的,因為GET、PUT、POST和DELETE就是你所能使用的全部操作了。關于XML Schema的使用,這意味著,即便你依賴于一個REST式接口,你仍舊可使用你所偏愛的數據綁定工具(假如剛好你有的話)來為你偏愛的語言生成數據綁定 代碼。(回答還沒結束,見下。)

第二,問問你自己需要描述做什么。最常見(盡管并非唯一)的用例(use case)是:描述被用來給接口生成樁(stubs)和骨架(skeletons)代碼。它通常不是文檔 (documentation),因為WSDL格式的描述并不是告訴你操作的語義——它只是告訴你操作的名稱。你得通過一些人類可讀的文檔來了解如何調用 它。典型的REST做法是,你應提供HTML格式的文檔,其中可能包含指向你的資源的直接鏈接(direct links)。如果你采取提供多個表示(multiple representations)的做法的話,那么你可以真正擁有自文檔化的(self-documenting)資源——你只要在瀏覽器中對一個資源做 HTTP GET請求,就可以得到一個HTML文檔,其中不但包含數據,還包含你可以對它執行的操作(HTTP動詞)的列表以及它接受和返回的內容類型 (content types)。

最后,假如你堅持為你的REST式服務(RESTful service)使用描述語言,那么你可以使用WADL(Web Application Description Language,Web應用描述語言),或適當地使用WSDL 2.0(其 制定者聲稱它也可以描述REST式服務)。不過,WADL和WSDL 2在描述超媒體(hypermedia)方面均無幫助——而且考慮到這是REST的核心方面之一,我不太確信它們是否充分有用。

3. 誰真會把他們應用中如此多的實現細節暴露出來?

另一個普遍關心的問題是,資源太低層(low-level),暴露了那些不應暴露出來的實現細節。說到底,這不就把“按有意義的方式來運用資源”的 擔子加到客戶端(消費者)的身上了嗎?

簡單的回答是:不。一個資源的GET、PUT或其他方法的實現,可以跟一個“服務”或RPC操作的實現復雜程度相當。應用REST設計原則,并不是 說你必須把下層數據模型(underlying data model)中的各項暴露出來——它只意味著,你采用以數據為中心的(data-centric)方式、而不是以操作為中心的(operation- centric)方式把業務邏輯暴露出來。

一個相關的關切是,不支持對資源的直接訪問將增加安全性。這是由“通過隱匿得到安全(security by obscurity)”這條陳舊的謬論得出的結論。人們可以這樣反駁:其實恰恰相反,如果你隱瞞你通過特定于應用的協議訪問哪些資源,你將無法利用基礎設 施(infrastructure)來保護它們。通過為有意義的資源指派單獨的URI,你可以利用Apache的安全規則(以及重寫邏輯、日志和統計等) 對不同資源采取不同處理。把這些明確化了,你的安全性將得到提升,而不是降低。

4. REST只能配合HTTP使用,它不是傳輸協議無關的

首先,毫無疑問,HTTP不是一種傳輸協議(transport protocol),而是一種應用協議(application protocol)。它采用TCP作為下層傳輸(underlying transport),但它擁有自己的語義(否則它就沒什么用處了)。僅將HTTP作為傳輸,是不恰當的。

第二,抽象未必總是好事。Web服務的做法,是試圖把許多大不相同的技術隱藏在單個抽象層背后——但這容易引發抽象泄露(leak)。例如,通過 JMS和通過HTTP請求發送消息存在著巨大的不同,試圖把各種存在極大差異的技術弱化為它們的最基本共通特性是毫無益處的。 打個比方,如果要創建一個通用抽象(common abstraction),把一個關系數據庫和一個文件系統隱藏在一個通用API之后,當然這可以去做,但一旦你涉及到解決像查詢這樣的問題時,該抽象的 問題就顯露出來了。

最后,正如Mark Baker曾說過的:“協議無關性是一個缺陷,而不是一個特性”。雖然這給人的最初感覺是比較奇怪,但你要知道,真正的協議無關性(protocol independence)是不可能實現的——你所能做的只是決定依賴于哪一種協議。依賴于一種得到了廣泛采納和官方標準化的協議(如 HTTP)根本不是問題,而且它還得到了比試圖取代它的抽象更廣泛的普及與支持。

5. 沒有實際的、明確且一致的指南教你如何設計REST式應用

REST式設計在許多方面均沒有“官方”最佳實踐和“如何按符合REST原則的方式、用HTTP解決一個特定問題”的標準方式。毋庸置疑,這是會逐 漸得到改善的。盡管如此,REST具體表達了比基于WSDL/SOAP的Web服務更多的應用概念。換言之,雖然該批評對REST有很大價值,但這一批評 更適用于其替換技術(它們基本上沒有向你提供任何建議)。

有時,這種疑慮以“連REST專家們都無法就具體怎么做達成一致”的形式出現。但一般說來,情況并不是這樣——舉個例子,我比較相信我數周前在這里講述的核心概念尚未(而且也不會)遭到REST圈內人士(假定存在這個圈子 的話)的質疑,這并不是因為那是一篇特別好的文章,而是因為人們在做過更深入的了解以后便能達成許多共識。假如你有機會做個實驗的話,可以試試看,是讓五 位SOA支持者在某方面達成一致更容易,還是讓五位REST支持者在某方面達成一致更容易。根據我個人的過往經驗和長期參與數個SOA與REST討論組的 經歷來看,我傾向于相信后者更加容易。

6. REST不支持事務

“事務(transaction)”一詞存在著多種不同解釋,不過人們一般所說的事務,指的是數據庫里的ACID這種。在一個SOA環境中——無論 是否基于Web服務或HTTP——各個服務(或系統、或Web應用)的實現仍然有可能與一個支持事務的數據庫進行交互:這無需很大改變,假如你不用顯式創 建事務的話(除非你的服務運行在一個EJB容器或其他可以為你處理事務創建的環境中)。如果你與多個資源交互,情況也一樣。

如果你打算把事務組合(或者創建)為一個更大的單元,情況將有所不同。在Web服務環境中,至少有一種辦法可以做到跟人們所熟知的(比如Java EE環境所支持的)兩階段提交(2PC)相似:即采用WS-Atomic Transaction(WS-AT),它是WS-Coordination標 準族中的成員。本質上,WS-AT所實現的是跟XA規定的兩階段提交協議非常相似或相同的。這意味著,你的事務上下文(transaction context)將用SOAP報頭來傳播,而你的實現(implementation)將負責確保資源管理器進入現有事務。本質上,跟EJB開發者所熟悉 的模型一樣——你的分布式事務跟本地事務一樣是原子性的。

關于SOA環境中的原子事務(atomic transactions),有很多看法或反對意見:

  • 松耦合與事務(尤其是ACID那樣的)根本格格不入。比如“跨越多個獨立系統來協調提交,會在它們之間造成緊耦合”就充分說明了這一點。
  • 為了進行這種協調,需要對所有服務進行中央控制——而跨越企業邊界進行兩階段提交事務是不可能或基本無法做到的。
  • 支持這種事務所需的基礎設施(infrastructure)通常極為昂貴和復雜。

很大程度上,在SOA或REST環境中需要ACID事務,其實是一種設計異味(design smell)——你很可能已經為你的服務或資源采用了錯誤的模型。當然,原子事務只是一種類型的事務——除此以外還有擴展的事務模型,也許它更適合松耦合 系統。不過,即便在Web服務陣營里,它們也沒得到較多采納。

7. REST是不可靠的

常有人指出,REST式HTTP里沒有與WS-ReliableMessaging對 等的特性,于是許多人便斷定,REST不能應用于講究可靠性(reliability)的場合(那就是說差不多所有跟業務場景相關的系統)。但很多時候, 你不一定需要一個處理消息遞送(message delivery)的基礎設施組件(infrastructure component),相反,你需要知 道一個消息是否已被遞送。

通常,收到一個響應消息——比方說HTTP里的200 OK——表明你知道你的通信伙伴已經收到請求。但如果你沒有收到響應消息,那問題就來了:你不知道是你的請求沒有到達另一端,還是已經收到了(觸發了某些 處理)、但響應消息丟失了。

確保請求消息抵達另一端的最簡單的做法,就是把消息重發一遍——當然,僅當接受方有能力處理重復消息(比如通過忽略它們)時才可以這么做。這種能力 被稱作冪等性(idempotency)。HTTP確保GET、PUT和DELETE是冪等的(idempotent )——如果你的應用實現得當的話,那么客戶端在沒有收到響應時只需把請求重發一遍即可。但POST消息不是冪等的——至少在HTTP規范里沒有保證。對此 你有多種選擇:要么改用PUT(如果你的語義可以映射上去的話),采用Joe Gregorio描述的一種常見的最佳實踐; 要么采納一種致力于統一有關做法的提案(例如Mark Nottingham的POE(POST Once Exactly)Yaron Goland的SOA-RityBill de hóra的HTTPLR)。

就我個人而言,我傾向于上述第一種做法——即把可靠性問題轉嫁到應用設計方面,不過對此存在多種不同看法。

盡管這些方案均解決了相當一部分可靠性問題,但沒有(或至少就我所知沒有)一個能支持消息遞送承諾,比如按序遞送一系列HTTP請求和響應。不過值 得一提的是,許多現有的SOAP/WSDL方案沒有依靠WS-ReliableMessaging(或其前身)也勉強應付了。

8. 不支持發布/訂閱

本質上,REST基于的是一種客戶端-服務器模型(client-server model),HTTP總把客戶端和服務器稱為通信端點(endpoints of communication)。客戶端通過發送請求和接受響應的方式與服務器進行交互。在發布/訂閱模型(publish/subscribe model)中,客戶訂閱特定種類的信息,然后每當有新信息出現時它就會得到通知。REST式HTTP環境怎么可能支持發布/訂閱呢?

尋找理想的例子并不難,聚合(syndication)就是一個。RSSAtom Syndication都是聚合的例子。客戶端通過“向一個代表變更集合(collection of changes)的資源發出HTTP請求”來查詢新信息,如獲取特殊分類或定時輪詢。這搞不好會相當低效,但實際上并沒有,因為GET是Web上最優化的 操作。其實,你可以很容易想象,要是一個受歡迎的博客服務器主動把各個變更通知各訂閱者的話,那么它應該可以在可伸縮性方面得到很大提高。輪流通知 (notification by polling)具有極好的可伸縮性。

你可以將這一聚合模型(syndication model)推廣應用到你的各個應用資源——例如,為用戶資源或賬目審計追蹤記錄的變更提供Atom提要(feed)。除了可以滿足任意數量應用的訂閱需 求,你還可以用提要閱讀器(feed reader)來查看這些提要(feeds),就像在瀏覽器里查看一個資源的HTML表示(representation)一樣。

當然,在某些情況下這就不合適了。比如,對于軟實時(soft real-time)需求,采用其他技術也許更為合適。但在許多情況下,由聚合模型贏得的松耦合(loose coupling)、可伸縮性(scalability)與通知(notification),整體上是相當不錯的。

9. 無異步交互

在HTTP的請求/響應模型之下,如何實現異步通信?同樣地,我們應注意到人們在談及異步性(asynchronicity)時常常指的是不同方 面。有人指的是編程模型,它可以是跟線上交互(wire interactions)無關的阻塞或非阻塞模型。這不是我們所關心的。但假如你把一個請求從客戶端(用戶)遞送到服務器端(提供者)的過程需花費數小 時,這怎么辦呢?用戶如何知道處理有沒有結束?

HTTP有一個專門的響應代碼202 Accepted,它的意思是“請求已被接受處理,但處理還沒有結束”。顯然,這正是你所需要的。至于處理結果,有多種辦法:服務器可以返回一個資源的 URI,然后客戶端通過向該URI發送GET請求來訪問結果(盡管在專門為一個請求創建資源時采用響應代碼201 Created更為恰當)。或者,客戶端可以提供一個URI,并期待服務器在處理完成后把結果POST上去。

10. 缺少工具

最后一點,人們常常抱怨缺少用于支持REST式HTTP開發的工具。正如我在第二點里提到的,在數據方面其實不是這樣——你可以使用你熟悉的數據綁 定與其他數據APIs,因為這與方法數量和調用它們的方式無關。至于普通的HTTP與URI支持,現在所有的編程語言、框架及工具包都能提供立即支持。最 后,廠商們正在為“用它們的框架進行更便捷的REST式HTTP開發”提供越來越多的支持,例如Sun的JAX-RS(JSR 311)、微軟的.NET 3.5及ADO.NET數據服務框架里對REST的支持。

總結

那么,REST及其最常見的實現——HTTP——理想嗎?當然不。世界上沒有在所有情況下都理想的東西,而且很多時候即便在單個情況下都未必能夠理 想。我已經避免了許多相當合理、但需要更復雜解答的問題領域,比如基于消息的安全、部分更新以及批處理等,我承諾將在后續文章中討論這些問題。希望我已經 解答了你的一些疑惑——假如我遺漏了你最關心的問題,歡迎在此發表評論。

Stefan Tilkov是InfoQ SOA社區的首席編輯,以及位于德國/瑞士的innoQ公司的合伙人、首席顧問和主要REST狂熱 主義者。

查看英文原文:http://www.infoq.com/articles/tilkov-rest-doubts


譯者簡介:徐涵,中文W3C技術推廣網站 W3China(w3china.org) 創始人,開放 翻譯計劃(transwiki.org) 發起人,W3C特邀專家。2005 年畢業于東南大學,獲計算機碩士學位。2003年創辦中文W3C技術推廣網站W3China(w3china.org),2004年發起開放翻譯計劃 (transwiki.org)致力于W3C技術文檔的翻譯,2006年翻譯出版國內首本中文SOA專著《Understanding SOA with Web Services 中文版》(榮獲2006年度CSDN讀書頻道SOA先鋒獎,入選China-pub 2006年度好書榜),2008年翻譯出版REST專著《RESTful Web Services 中文版》 。研究興趣包括:Web Architecture、Semantic Web、Web Services、SOA、Social Network等。聯系方式:hanxu@w3china.org。參與InfoQ中文站內容建設,請郵件至editors@cn.infoq.com