服務端REST與SOAP的探討
聲明:
閑來逛論壇看到一篇不錯的文章,閱讀后受益匪淺。
本文從一個簡單的應用場景出發,使用REST和SOAP兩種不同的架構風格實現,通過對REST與SOAP Web服務具體對比,旨在幫助讀者更深刻理解REST架構風格。
REST簡介
在開始我們的正式討論之前,讓我們簡單看一下REST的定義。
REST(Representational State Transfer)是Roy Fielding提出的一個描述互聯系統架構風格的名詞。為什么稱為REST?Web本質上由各種各樣的資源組成,資源由URI唯一標識。瀏覽器(或者任何其它類似于瀏覽器的應用程序)將展示出該資源的一種表現方式,或者一種表現狀態。如果用戶在該頁面中定向到指向其它資源的鏈接,則將訪問該資源,并表現出它的狀態。這意味著客戶端應用程序隨著每個資源表現狀態的不同而發生狀態轉移,也即所謂REST。關于REST本身,本文就不再這里過多地討論,讀者可以參考developerWorks上其它介紹REST的文章。本文的重點在于通過REST與SOAP Web服務的對比,幫助讀者更深刻理解REST架構風格的特點,優勢。
應用場景介紹(在線用戶管理)
本文將借助于一個應用場景,通過基于REST和SOAP Web服務的不同實現,來對兩者進行對比。該應用場景的業務邏輯會盡量保持簡單且易于理解,以有助于把我們的重心放在REST和SOAP Web服務技術特質對比上。
需求描述
這是一個在線的用戶管理模塊,負責用戶信息的創建,修改,刪除,查詢。用戶的信息主要包括:
- 用戶名(唯一標志在系統中的用戶)
- 頭銜
- 公司
- 描述
需求用例圖如下圖一:
如圖所示,客戶端1(Client1)與客戶端2(Client2)對于信息的存取具有不同的權限,客戶端1可以執行所有的操作,而客戶端2只被允許執行用戶查詢(Query User)與用戶列表查詢(Query User List)。關于這一點,我們在對REST Web服務與SOAP Web服務安全控制對比時會具體談到。下面我們將分別向您介紹如何使用REST和SOAP架構實現Web服務。
使用REST實現Web服務
本部分將基于Restlet框架來實現該應用。Restlet為那些要采用REST結構體系來構建應用程序的Java開發者提供了一個具體的解決方案。關于更多的Restlet相關內容,本文不做深入討論,請見參考資源列表。
設計
我們將采用遵循REST設計原則的ROA(Resource-Oriented Architecture,面向資源的體系架構)進行設計。
ROA是什么?簡單點說,ROA是一種把實際問題轉換成REST式Web服務的方法,它使得URI、HTTP和XML具有跟其他Web應用一樣的工作方式。在使用ROA進行設計時,我們需要把真實的應用需求轉化成ROA中的資源,基本上遵循以下的步驟:
- 分析應用需求中的數據集。
- 映射數據集到ROA中的資源。
- 對于每一資源,命名它的URI。
- 為每一資源設計其Representations。
- 用hypermedia links表述資源間的聯系。
接下來我們按照以上的步驟來設計本文的應用案例。
在線用戶管理所涉及的數據集就是用戶信息,如果映射到ROA資源,主要包括兩類資源:用戶及用戶列表。用戶資源的URI用 http://localhost:8182/v1/users/{username} 表示,用戶列表資源的URI用 http://localhost:8182/v1/users表示。它們的Representation如下,它們都采用了如清單1和清單2所示的XML表述方式。
清單1. 用戶列表資源Representation
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <users> <user> <name>tester</name> <link>http://localhost:8182/v1/users/tester</link> </user> <user> <name>tester1</name> <link>http://localhost:8182/v1/users/tester1</link> </user> </users>
清單2. 用戶資源 Representation
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <user> <name>tester</name> <title>software engineer</title> <company>IBM</company> <email>tester@cn.ibm.com</email> <description>testing!</description> </user>
客戶端通過User List Resource提供的LINK信息(如:<link>http://localhost:8182/v1/users/tester</link>
)獲得具體的某個USER Resource。
Restful Web服務架構
首先給出Web服務使用REST風格實現的整體架構圖,如下圖所示:
接下來,我們將基于該架構,使用Restlet給出應用的RESTful Web服務實現。下面的章節中,我們將給出REST Web服務實現的核心代碼片段。關于完整的代碼清單,讀者可以通過資源列表下載。
客戶端實現
清單給出的是客戶端的核心實現部分,其主要由四部分組成:使用HTTP PUT增加、修改用戶資源,使用HTTP GET得到某一具體用戶資源,使用HTTP DELETE刪除用戶資源,使用HTTP GET得到用戶列表資源。而這四部分也正對應了上面架構圖中關于架構描述的四對HTTP消息來回。關于UserRestHelper類的完整實現,請讀者參見本文所附的代碼示例。
public class UserRestHelper { //The root URI of our ROA implementation. public static final String APPLICATION_URI = "http://localhost:8182/v1"; //Get the URI of user resource by user name. private static String getUserUri(String name) { return APPLICATION_URI + "/users/" + name; } //Get the URI of user list resource. private static String getUsersUri() { return APPLICATION_URI + "/users"; } //Delete user resource from server by user name. //使用 HTTP DELETE 方法經由 URI 刪除用戶資源 public static void deleteFromServer(String name) { Response response = new Client(Protocol.HTTP).delete(getUserUri(name)); …… } //Put user resource to server. //使用 HTTP PUT 方法經由 URI 增加或者修改用戶資源 public static void putToServer(User user) { //Fill FORM using user data. Form form = new Form(); form.add("user[title]", user.getTitle()); form.add("user[company]", user.getCompany()); form.add("user[email]", user.getEmail()); form.add("user[description]", user.getDescription()); Response putResponse = new Client(Protocol.HTTP).put( getUserUri(user.getName()), form.getWebRepresentation()); …… } //Output user resource to console. public static void printUser(String name) { printUserByURI(getUserUri(name)); } //Output user list resource to console. //使用 HTTP GET 方法經由 URI 顯示用戶列表資源 public static void printUserList() { Response getResponse = new Client(Protocol.HTTP).get(getUsersUri()); if (getResponse.getStatus().isSuccess()) { DomRepresentation result = getResponse.getEntityAsDom(); //The following code line will explore this XML document and output //each user resource to console. …… } else { System.out.println("Unexpected status:"+ getResponse.getStatus()); } } //Output user resource to console. //使用 HTTP GET 方法經由 URI 顯示用戶資源 private static void printUserByURI(String uri) { Response getResponse = new Client(Protocol.HTTP).get(uri); if (getResponse.getStatus().isSuccess()) { DomRepresentation result = getResponse.getEntityAsDom(); //The following code line will explore this XML document and output //current user resource to console. …… } else { System.out.println("unexpected status:"+ getResponse.getStatus()); } } }
服務器端實現
清單給出的是服務器端對于用戶資源類(UserResourc)的實現,其核心的功能是響應有關用戶資源的HTTP GET/PUT/DELETE請求,而這些請求響應邏輯正對應了UserRestHelper類中關于用戶資源類的HTTP請求。
public class UserResource extends Resource { private User _user; private String _userName; public UserResource(Context context, Request request, Response response) { //Constructor is here. …… } //響應 HTTP DELETE 請求邏輯 public void delete() { // Remove the user from container. getContainer().remove(_userName); getResponse().setStatus(Status.SUCCESS_OK); } //This method will be called by handleGet. public Representation getRepresentation(Variant variant) { Representation result = null; if (variant.getMediaType().equals(MediaType.TEXT_XML)) { Document doc = createDocument(this._user); result = new DomRepresentation(MediaType.TEXT_XML, doc); } return result; } //響應 HTTP PUT 請求邏輯。 public void put(Representation entity) { if (getUser() == null) { //The user doesn't exist, create it setUser(new User()); getUser().setName(this._userName); getResponse().setStatus(Status.SUCCESS_CREATED); } else { getResponse().setStatus(Status.SUCCESS_NO_CONTENT); } //Parse the entity as a Web form. Form form = new Form(entity); getUser().setTitle(form.getFirstValue("user[title]")); getUser().setCompany(form.getFirstValue("user[company]")); getUser().setEmail(form.getFirstValue("user[email]")); getUser().setDescription(form.getFirstValue("user[description]")); //Put the user to the container. getApplication().getContainer().put(_userName, getUser()); } //響應 HTTP GET 請求邏輯。 public void handleGet() { super.handleGet(); if(this._user != null ) { getResponse().setEntity(getRepresentation( new Variant(MediaType.TEXT_XML))); getResponse().setStatus(Status.SUCCESS_OK); } else { getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND); } } //build XML document for user resource. private Document createDocument(User user) { //The following code line will create XML document according to user info. …… } //The remaining methods here …… }
UserResource類是對用戶資源類的抽象,包括了對該資源的創建修改(put方法),讀取(handleGet方法 )和刪除(delete方法),被創建出來的UserResource類實例被Restlet框架所托管,所有操縱資源的方法會在相應的HTTP請求到達后被自動回調。
另外,在服務端,還需要實現代表用戶列表資源的資源類UserListResource,它的實現與UserResource類似,響應HTTP GET請求,讀取當前系統內的所有用戶信息,形成如上用戶列表資源Representation所示,然后返回該結果給客戶端。具體的實現請讀者參見本文所附的代碼示例。
使用SOAP實現Web服務
本文對于SOAP實現,就不再像REST那樣,具體到代碼級別的實現。本節將主要通過URI,HTTP和XML來宏觀上表述SOAP Web服務實現的技術本質,為下一節REST Web服務與SOAP Web服務的對比做鋪墊。
SOAP Web服務架構
同樣,首先給出SOAP實現的整體架構圖,如下圖所示:
可以看到,與REST架構相比,SOAP架構圖明顯不同的是:所有的SOAP消息發送都使用HTTP POST方法,并且所有SOAP消息的URI都是一樣的,這是基于SOAP的Web服務的基本實踐特征。
獲得用戶信息列表
基于SOAP的客戶端創建如清單所示的SOAP XML文檔,它通過類RPC方式來獲得用戶列表信息。
getUserList SOAP消息
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <p:getUserList xmlns:p="http://www.exmaple.com"/> </soap:Body> </soap:Envelope>
客戶端將使用HTTP的POST方法,將上述的SOAP消息發送至http://localhost:8182/v1/soap/servlet/messagerouter URI,SOAP SERVER收到該HTTP POST請求,通過解碼SOAP消息確定需要調用getUserList方法完成該WEB服務調用,返回如下的響應:
getUserListResponse消息
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <p:get UserListResponse xmlns:p="http://www.exmaple.com"> <Users> <username>tester<username> <username>tester1<username> ...... </Users> <p: getUserListResponse > </soap:Body> </soap:Envelope>
獲得某一具體用戶信息
getUserByName SOAP消息
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <p:getUserByName xmlns:p="http://www.exmaple.com"> <username>tester</username> </p:getUserByName > </soap:Body> </soap:Envelope>
同樣地,客戶端將使用HTTP的POST方法,將上述的SOAP消息發送至http://localhost:8182/v1/soap/servlet/messagerouter URI,SOAP SERVER處理后返回的Response如下:
getUserByNameResponse SOAP消息
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <p:getUserByNameResponse xmlns:p="http://www.exmaple.com"> <name>tester</name> <title>software engineer</title> <company>IBM</company> <email>tester@cn.ibm.com</email> <description>testing!</description> </p:getUserByNameResponse> </soap:Body> </soap:Envelope>
實際上,創建新的用戶,過程也比較類似,在這里,就不一一列出,因為這兩個例子對于本文在選定的點上對比REST與SOAP已經足夠了。
REST與SOAP比較
本節從以下幾個方面來對比上面兩節給出REST實現與SOAP實現。
接口抽象
RESTful Web服務使用標準的HTTP方法(GET/PUT/POST/DELETE)來抽象所有Web系統的服務能力,而不同的是,SOAP應用都通過定義自己個性化的接口方法來抽象Web服務,這更像我們經常談到的RPC。例如本例中的getUserList與getUserByName方法。
RESTful Web服務使用標準的HTTP方法優勢,從大的方面來講:標準化的HTTP操作方法,結合其他的標準化技術,如URI,HTML,XML等,將會極大提高系統與系統之間整合的互操作能力。尤其在Web應用領域,RESTful Web服務所表達的這種抽象能力更加貼近Web本身的工作方式,也更加自然。同時,使用標準HTTP方法實現的RRESTful Web服務也帶來了HTTP方法本身的一些優勢:
無狀態性(Stateless)
HTTP協議從本質上說是一種無狀態的協議,客戶端發出的HTTP請求之間可以相互隔離,不存在相互的狀態依賴。基于HTTP的ROA,以非常自然的方式來實現無狀態服務請求處理邏輯。對于分布式的應用而言,任意給定的兩個服務請求Request 1與Request 2, 由于它們之間并沒有相互之間的狀態依賴,就不需要對它們進行相互協作處理,其結果是:Request 1與Request 2可以在任何的服務器上執行,這樣的應用很容易在服務器端支持負載平衡(load-balance)。
安全操作與冪指相等特性(Safety /Idempotence)
HTTP的GET、HEAD請求本質上應該是安全的調用,即:GET、HEAD 調用不會有任何的副作用,不會造成服務器端狀態的改變。對于服務器來說,客戶端對某一URI做n次的GET、HAED調用,其狀態與沒有做調用是一樣的,不會發生任何的改變。
HTTP的PUT、DELTE調用,具有冪指相等特性 , 即:客戶端對某一URI做n次的PUT、DELTE調用,其效果與做一次的調用是一樣的。HTTP的GET、HEAD方法也具有冪指相等特性。
HTTP這些標準方法在原則上保證你的分布式系統具有這些特性,以幫助構建更加健壯的分布式系統。
安全控制
為了說明問題,基于上面的在線用戶管理系統,我們給定以下場景:
參考一開始我們給出的用例圖,對于客戶端Client2,我們只希望它能以只讀的方式訪問User和User List資源,而Client1具有訪問所有資源的所有權限。
如何做這樣的安全控制?
通行的做法是:所有從客戶端Client2發出的HTTP請求都經過代理服務器(Proxy Server)。代理服務器制定安全策略:所有經過該代理的訪問User和User List資源的請求只具有讀取權限,即:允許 GET/HEAD 操作,而像具有寫權限的PUT/DELTE是不被允許的。
如果對于REST,我們看看這樣的安全策略是如何部署的。如下圖所示:
REST與代理服務器 (Proxy Servers)
一般代理服務器的實現根據(URI, HTTP Method)兩元組來決定HTTP請求的安全合法性。當發現類似于(http://localhost:8182/v1/users/{username},DELETE)這樣的請求時,予以拒絕。對于SOAP,如果我們想借助于既有的代理服務器進行安全控制,會比較尷尬,如下圖:
SOAP與代理服務器 (Proxy Servers)
所有的SOAP消息經過代理服務器,只能看(http://localhost:8182/v1/soap/servlet/messagerouter, HTTP POST)這樣的信息,如果代理服務器想知道當前的HTTP請求具體做的是什么,必須對SOAP的消息體解碼,這樣的話,意味著要求第三方的代理服務器需要理解當前的SOAP消息語義,而這種SOAP應用與代理服務器之間的緊耦合關系是不合理的。
關于緩存
眾所周知,對于基于網絡的分布式應用,網絡傳輸是一個影響應用性能的重要因素。如何使用緩存來節省網絡傳輸帶來的開銷,這是每一個構建分布式網絡應用的開發人員必須考慮的問題。
HTTP協議帶條件的HTTP GET請求 (Conditional GET) 被設計用來節省客戶端與服務器之間網絡傳輸帶來的開銷,這也給客戶端實現Cache機制 ( 包括在客戶端與服務器之間的任何代理 ) 提供了可能。HTTP 協議通過 HTTP HEADER 域:If-Modified-Since/Last- Modified,If-None-Match/ETag 實現帶條件的GET請求。
REST的應用可以充分地挖掘HTTP協議對緩存支持的能力。當客戶端第一次發送HTTP GET請求給服務器獲得內容后,該內容可能被緩存服務器(Cache Server) 緩存。當下一次客戶端請求同樣的資源時,緩存可以直接給出響應,而不需要請求遠程的服務器獲得。而這一切對客戶端來說都是透明的。
REST與緩存服務器(Cache Server)
而對于SOAP,情況又是怎樣的呢?
使用HTTP協議的SOAP,由于其設計原則上并不像REST那樣強調與Web的工作方式相一致,所以,基于SOAP應用很難充分發揮HTTP本身的緩存能力。
SOAP與緩存服務器 (Cache Server)
兩個因素決定了基于SOAP應用的緩存機制要遠比REST復雜:
其一、所有經過緩存服務器的SOAP消息總是HTTP POST,緩存服務器如果不解碼SOAP消息體,沒法知道該HTTP請求是否是想從服務器獲得數據。
其二、SOAP消息所使用的URI總是指向SOAP的服務器,如本文例子中的http://localhost:8182/v1/soap/servlet/messagerouter,這并沒有表達真實的資源URI,其結果是緩存服務器根本不知道那個資源正在被請求,更不用談進行緩存處理。
關于連接性
在一個純的SOAP應用中,URI本質上除了用來指示SOAP服務器外,本身沒有任何意義。與REST的不同的是,無法通過URI驅動SOAP方法調用。例如在我們的例子中,當我們通過getUserList SOAP消息獲得所有的用戶列表后,仍然無法通過既有的信息得到某個具體的用戶信息。唯一的方法只有通過WSDL的指示,通過調用getUserByName獲得,getUserList與getUserByName是彼此孤立的。而對于REST,情況是完全不同的:通過http://localhost:8182/v1/users URI獲得用戶列表,然后再通過用戶列表中所提供的LINK屬性,例如<link>http://localhost:8182/v1/users/tester</link>
獲得tester用戶的用戶信息。這樣的工作方式,非常類似于你在瀏覽器的某個頁面上點擊某個hyperlink, 瀏覽器幫你自動定向到你想訪問的頁面,并不依賴任何第三方的信息。
總結
典型的基于SOAP的Web服務以操作為中心,每個操作接受XML文檔作為輸入,提供XML文檔作為輸出。在本質上講,它們是RPC風格的。而在遵循REST原則的ROA應用中,服務是以資源為中心的,對每個資源的操作都是標準化的HTTP方法。
本文主要集中在以上的幾個方面,對SOAP與REST進行了對比,可以看到,基于REST構建的系統其系統的擴展能力要強于SOAP,這可以體現在它的統一接口抽象、代理服務器支持、緩存服務器支持等諸多方面。并且,伴隨著Web Site as Web Services演進的趨勢,基于REST設計和實現的簡單性和強擴展性,有理由相信,REST將會成為 Web 服務的一個重要架構實踐領域。
終極總結
SOAP(Simple Object Access Protocol)是一個嚴格定義的信息交換協議,用于在Web Service中把遠程調用和返回封裝成機器可讀的格式化數據。事實上SOAP數據使用XML數據格式,定義了一整套復雜的標簽,以描述調用的遠程過程、參數、返回值和出錯信息等等。而且隨著需要的增長,又不得增加協議以支持安全性,這使SOAP變得異常龐大,背離了簡單的初衷。另一方面,各個服務器都可以基于這個協議推出自己的API,即使它們提供的服務及其相似,定義的API也不盡相同,這又導致了WSDL的誕生。WSDL (Web Service Description Language) 也遵循XML格式,用來描述哪個服務器提供什么服務,
怎樣找到它,以及該服務使用怎樣的接口規范,簡言之,服務發現。現在,使用Web Service的過程變成,獲得該服務的WSDL描述,根據WSDL構造一條格式化的SOAP請求發送給服務器,然后接收一條同樣SOAP格式的應答,最后根據先前的WSDL解碼數據。絕大多數情況下,請求和應答使用HTTP協議傳輸,那么發送請求就使用HTTP的POST方法。
REST(REpresentational State Transfort)形式上應該表述為客戶端通過申請資源來實現狀態的轉換,在這個角度系統可以看成一臺虛擬的狀態機。拋開R. T. Fielding博士論文里晦澀的理論不說,REST應該滿足這樣的特點:
- 客戶端和服務器結構;
- 連接協議具有無狀態性;
- 能夠利用Cache機制增進性能;
- 層次化的系統;
- 按需代碼;
說到底,REST只是一種架構風格,而不是協議或標準。但這種新的風格(也許已經歷史悠久?)對現有的以SOAP為代表的Web Service造成的沖擊也是革命性的,因為它面向資源,甚至連服務也抽象成資源,因為它和HTTP緊密結合,因為它服務器無狀態。
因為SOAP并不假定傳輸數據的下層協議,因此必須設計為能在各種協議上運行。即使絕大多數SOAP是運行在HTTP上,使用URI標識服務,SOAP也僅僅使用POST方法發送請求,用一個唯一的URI標識服務的入口。
REST簡單而直觀,把HTTP協議利用到了極限,在這種思想指導下,它甚至用HTTP請求的頭信息來指明資源的表示形式(如果一個資源有多種形式的話,例如人類友善的頁面還是機器可讀的數據?),用HTTP的錯誤機制來返回訪問資源的錯誤。由此帶來的直接好處是構建的成本減少了,例如用URI定位每一個資源可以利用通用成熟的技術,而不用再在服務器端開發一套資源訪問機制。又如只需簡單配置服務器就能規定資源的訪問權限,例如通過禁止非GET訪問把資源設成只讀。
服務器無狀態帶來了更多額外好處,因為每次請求都包含響應需要的所有信息,所有狀態信息都存儲在客戶端,服務器的內存從龐大的狀態信息中解放出來。而且現在即使一臺服務器突然死機對客戶的影響也微乎其微,因為另一臺服務器可以馬上代替它的位置,而不需要考慮恢復狀態信息。更多的緩存也變成可能,而之前由于服務器有狀態,對同一個URI的請求可能導致完全不同的響應。
總體結果是,網絡的容錯性和延展性都增強了,這些本來是WEB設計的初衷,日趨復雜和定制的WEB把它們破壞了,現在REST又返璞歸真,試圖把Web Service帶回簡單的原則中來。
但是REST就是萬能的嗎?無狀態帶來了巨大的優勢,同時也帶來了難以解決的問題,例如,怎樣授權特定用戶才能使用的服務?怎樣驗證用戶身份?如果堅持服務器無狀態,也就是不記錄用戶登錄狀態,勢必要求每一次服務請求都包含完整的用戶身份和驗證信息。在這種情況下,怎樣避免冒認?怎樣避免用戶信息泄漏?事實上,構建REST附屬的安全機制已經在討論中,其結果無非導致另一個SOAP:復雜的需求摧殘了易用性。
REST的支持者聲稱REST的請求和應答數據簡單可讀,而SOAP則需要一系列繁瑣的封裝;即使如此,SOAP仍然不能達到接口的一致性,不同的廠商有各自的接口,而REST只使用HTTP定義的方法,因此是通用的。事實確實如此嗎?試想用REST實現兩數求和的服務,如果按照建議的做法,把服務(此處是加法)作為一個資源,參數(此處是兩個加數)作為請求的參數,結果以XML或JSON語法返回,是否比SOAP更簡單易用?通用接口仍然沒法達到,因為資源的名稱、參數的名稱、結果的格式仍然是服務提供者定義的。
為了解決這個問題,提出了WASL(Web Application Description Language)來描述REST接口。WADL就像是WSDL的REST版,隨著REST被應用到復雜的領域,SOAP的影子無處不在。
面向資源和面向事務(非常明顯的說明了Rest的試用范圍,請求地圖數據就可以認為主要是請求一種特殊的資源)REST在面向資源的應用中左右逢源,但在面向事務的應用中卻未如人意。面向資源的應用操作簡單,無非創建、讀取、改變、刪除幾項,但是面向事務的應用不允許用戶直接操作資源,用戶只需向系統提交一個事務說明要求,然后等待事務的完成,就如一個網上銀行的用戶不直接修改賬戶和存款,而是提交一個事務告訴銀行自己要轉賬。如果把這樣的服務看成一種資源,通過向資源發送POST請求完成事務,那不過是SOAP的翻版而已,無論是這樣,還是通過PUT來創建事務,都改變了系統的狀態(資源本身未改變,此處是改變了用戶的余額),顯然違背了REST直觀的初衷。
事實上,一些Web Service提供者提供的REST API只有REST的外殼,傳輸的請求和應答全然是簡化了的SOAP,這種新瓶裝舊酒的做法只是加深了標準的分歧而已。歸根結底REST無法簡單地解決一些應用,
因此我們只能看到SOAP在REST外殼下的借尸還魂。沒有一項技術能一勞永逸地解決所有問題,只需要在預定的約束下優美地解決所在領域的問題就足夠了。一項新技術推出的時候總是引來無數的跟風和吹捧,只有當塵埃落定之后才能得到中肯的評價。