天天看點

解答有關REST的十點疑惑解答有關REST的十點疑惑

解答有關REST的十點疑惑

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

在了解過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 NG、Schematron等) 仍舊可供你使用。可以說,一個用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-Rity或Bill de hóra的HTTPLR)。

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

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

8. 不支援釋出/訂閱

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

尋找理想的例子并不難,聚合(syndication)就是一個。RSS和Atom 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

繼續閱讀