引言
Web 服務希望並且承諾將分散的應用程序以一種無縫的方式進行集成。但企業應用程序是在不同的平台上采用不同的技術構建的,因此,跨業務的集成並不是一件輕而易舉的事。最近出現的基於 Web 服務的業務流程執行語言(BPEL)為定義 Web 服務的行為提供了一個高層描述語言。它提供一個標准和可移植的語言來將多個 Web 服務融合到一個商業流程中。由於 BPEL 受到一些主要廠商的歡迎,一些用來自動設計商業流程的集成開發工具,如 IBM® WebSphere® Studio Application Developer Integration Edition (Application Developer) 已經進入市場。這些工具減少了 Web 服務集成時需要的大量編碼工作,並允許您通過拖拽 WSDL 文件到工具中的方式構建商業流程。我們期望這些工具能夠自動產生調用 Web 服務的客戶端的存根。因此,集成的成功目前很大程度上一來於底層復雜的工具支撐。這就更加要求開發成員采用最佳實踐來確保共享 Web 服務的內在互操作性。
正如我在本文以及隨後的文章中所講的,以下幾個問題需要特別關注:
利用廠商工具根據實現代碼得到 WSDL 中的 Web 服務語言非常方便。但是這種方式忽略了消息腳本的設計,但這一點正是異構的環境(如 J2EE 技術對.NET)中 Web 服務互操作的關鍵。
流行的 RPC/encoded 方式以其簡單、靈活且熟悉的特點對開發人員是個有吸引力地選擇。然而,在廠商之間對抽象 SOAP 編碼數據模型的同步實現帶來的困難成為 Web 服務互操作的一個困難性的挑戰。
弱類型的集合對象、包含空元素的數組和特定的本地數據類型都給互操作性帶來一定的問題,特別是:
對於廠商提供的工具來講,准確地解釋代表弱類型集合對象的 XML 腳本並將它們映射到本地數據類型是不可能的。
帶有空元素的數組的 XML 表示在 .NET 和 Websphere 之間是不同的。
由於本地數據類型和 XSD 數據類型並不具有一一對應關系,在轉換過程中,信息或精度將會丟失。
由於使用相對 URI 引用,Java 技術和 .NET之間不同命名轉換將會導致命名空間沖突。
從 WSDL 開始
所有 Web 服務的互操作問題都可以歸結到 WSDL 文件上。WSDL 是 Web 服務的接口定義語言(IDL)和服務器與客戶間的協議。服務語法,即 WSDL 文件中的消息類型、數據類型和交互方式,是構建松耦合的系統的關鍵。盡管 WSDL 並不要求使用特定的類型系統, XML 腳本數據類型(XSD)在 Web 服務領域內廣受歡迎。
XSD 提供了大量的內置的基本類型並允許服務提供者進一步定義定制的復雜類型。XSD 的類型系統比任何編程語言的類型系統都更加復雜和強大,並且更重要的是,它是獨立於編程語言的,因此,它成為 WSDL 定義 Web 服務語法的邏輯起點。
理論上講,就像 COM 和 CORBA 的 IDL,您應該首先創建並編輯 WSDL --在您基於 WSDL中的服務語法來構建 Web 服務和特定實現語言的客戶程序之前,定義接口、消息和數據類型。
然而,事實上,即使經驗豐富的都不會那麼做。他們最初開始實現特定於語言的接口,如 Java 接口,然後依賴廠商的工具從它們的實現代碼中獲取 WSDL 服務語法;再把這些 WSDL 服務語法交給 Web 服務客戶程序以便根據 WSDL 找出如何來調用 Web 服務。最後,在他們經常不需要知道 WSDL 之前, 他們就能夠創建並運行復雜的 Web 服務。他們可能在異構的環境中運行他們的客戶和服務器程序,即或者 J2EE 平台,或者 .NET 平台,或者二者都有。
目前廠商的集成開發工具下面的方向發展,即所有的東西,包括 WSDL都可以自動生成,不需要寫任何代碼(或者,至少許多廠商都聲稱並希望如此)。這一趨勢很有吸引力,因為它開發效率很高,且相對於人工編碼它很少出錯(當然在工具功能合適且可靠的條件下)。在單一的平台上,互操作性一般不成問題,只要 Web 服務和客戶都使用同樣的開發工具。
當對跨越異構的環境產生互操作性要求時,問題變得比較明顯。現在,您從實現語言來創建 Web 服務交互的關鍵構件,然後利用平台獨立的工具將它們映射到 WSDL 中語言獨立的元素。當客戶平台上的工具根據 工具生成的 WSDL 來產生服務代理時,將進行另外一個映射。這一流程基於下面的事實,WSDL 是 Web 服務的接口定義語言。語言獨立的 WSDL 應該成為客戶和服務器的共同基礎。以這種方式使用開發工具將雙倍增加映射過程中信息丟失的可能。
這並不意味著您應該放棄這些工具,所有的廠商應該停止生產並在市場上銷售這些工具。在當前的企業應用程序開發領域自動化已經成為一個重要的因素。工具本身很強大,關鍵在您怎麼利用它們的力量?您可以利用這些工具產生一個框架 WSDL 來作為起點或者模板。腳本、消息部件和數據綁定必須仔細設計來遵循 WS-I 一致性要求。就像您出於數據庫效率的考慮而不希望由工具產生數據庫腳本一樣,您不應該忽視手工設計高效 Web 服務消息和數據類型。有時,即使元素的名字也要仔細設計來遵循潛在的異構平台間的命名轉換。對於 Web 服務互操作性來講, WSDL 是唯一比較重要的構件。程序員必須學會基於原始 XML 消息進行編程,至少學會讀取 XSD、 WSDL 和 SOAP 消息。
仍舊采用 RPC/encoded?
WS-I基本規范提出采用文本 XML 實現互操作。它禁止對 soap:Envelope 或派生的 soap:Body 元素使用 soap:encodingStyle 屬性。因此, RPC/literal 和 Document/literal 是 WS-I 標准唯一支持的 2 種格式。但是有許多 Web 服務工具不支持 RPC/literal ,因此,它將 Document/literal 作為唯一的實際互操作性標准。
然而,采用 RPC/encoded 方式來處理在 XML 腳本或 WSDL 出現之前就已經存在很久;另外,一些編碼規則仍舊不能夠采用 XSD 來表述。盡管 SOAP 編碼被認為 Web 服務互操作性問題的主要根源,以及 ASP.NET WebMethod 基礎設施缺省采用 Document/literal,直到現在,大部分 J2EE Web 服務缺省采用 RPC/encoded 方式。
RPC/encoded 方式受歡迎主要因為對於習慣了遠程過程調用慣例的程序員來說是較為簡單的編程模型。RPC 綁定方式采用方法名稱和參數來產生代表方法調用堆棧的結構,因此,它使 Web 服務看起來像一個帶有封裝對象的單一的邏輯組件,所有操作都在 SOAP RPC 堆棧中處理。這一點與 Document/literal 方式相反,在那種方式下,程序員不得不處理所有的事務,包括基於 XML 的 SOAP 消息的序列化和逆序列化。
另外,SOAP 編碼規則定義了標准,方便了從編程類型到 XML 的映射。編碼規則非常靈活並支持圖形數據和多態的表示。因此,它毫無疑問比 Document/literal 更適合,後者依賴於自然樹結構來表示數據對象。
為了對 RPC/encoded 能做什麼、不能做什麼有個清晰的概念,讓我們看一個示例。Serializable Person 類定義了一個它自己的友元,如 清單 1所示。
清單 1. 自引用的 Person 類public class Person implements java.io.Serializable {
private Person friend;
private String name;
public Person getFriend() {
return this.friend;
}
public void setFriend(Person friend) {
this.friend = friend;
}
<!-- Other setter and getter methods -->
}
如果您初始化 2 個 Person:A 和 B,並使 Person A 成為 Person B 的友元,使 Person B 成為 Person A 的友元,那麼您創建了一個循環的對象圖:
清單 2. makeFriend 方法public Person makeFriend(Person A, Person B) {
A.setFriend(B);
B.setFriend(A);
return A;
}
如果您以 Document/literal Web 服務提供 清單 2 中的方法,那麼在 XML 中怎樣表示循環對象呢?
事實證明利用 Document/literal 方式沒有方便的方法來實現這一點。原因在於: Document/literal 方法基於 XML 腳本定義了消息類型作為固定類型。XML 腳本利用 XSD 基本類型作為葉子節點來表示自然樹結構。一個循環對象圖不能夠轉換為樹結構。在這種情況下,由於 Person 類的示例 A 和 B 之間的循環引用,對象 A 和 B 的序列化成為遞歸循環。利用 WebSphere Studio ,最終您將看到堆棧溢出意外。這並不意味著 Document/literal 方法沒有辦法來解決它,但是需要開發人員更加深入的工作。
如果您采用 SOAP 編碼作為消息綁定,通過在循環圖上應用 SOAP 編碼規則的多引用,循環引用可以很簡單地表示。通過在 WebSphere 平台上運行 Web 服務來響應將 John 和 Jason 互相成為友元的 SOAP 請求,可以看一下序列化 XML 的載荷。清單 3顯示了循環圖是多麼容易表示。
清單 3. WebSphere 的 SOAP 編碼響應消息
<soapenv:Body soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<makeFriendResponse xmlns="http://cyclic.test">
<makeFriendReturn href="http://www.bianceng.cn/index.php#id0" xmlns=""/>
</makeFriendResponse>
<multiRef id="id0" soapenc:root="0"
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns-
520570027:Person" xmlns:ns-520570027="http://cyclic.test" xmlns="">
<name xsi:type="xsd:string">John</name>
<friend href="http://www.bianceng.cn/index.php#id1"/>
</multiRef>
<multiRef id="id1" soapenc:root="0"
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns-
520570027:Person" xmlns:ns-520570027="http://cyclic.test" xmlns="">
<name xsi:type="xsd:string">Jason</name>
<friend href="http://www.bianceng.cn/index.php#id0"/>
</multiRef>
</soapenv:Body >
注意 SOAP 怎樣使用本地 id0 和 id1 的類型 ID 來對編碼元素 Person A (John) 和 Person B (Jason) 定義唯一的標志符,另外一個本地 unqualified 屬性 href 用來定義這兩個 ID 值的引用。對象 A 和 對象 B 之間的循環引用通過靈活的 SOAP 編碼規則可以很方便地表示。
RPC/encoded 模型的能力對於傳統的面向對象的程序員具有巨大的吸引力,它們已經習慣了在單一的領域內編程,如 J2EE 技術或 .NET 。當范圍被限定在一個單一的平台 ,SOAP 消息的編寫者和閱讀者具有同步的存根來理解編碼的 SOAP 消息,盡管 RPC/encoded 模型為 Web 服務的設計者和實現者提供了很大的方便。但當 Web 服務跨平台時,它將帶來巨大的代價。面對跨平台的互操作的要求,它的強大的能力反而變成弱點。為了說明互操作問題,仔細看一下 Application Developer 從前面 Web 服務生成的 WSDL 文件(參閱 清單 4)。
在 WSDL 文檔中, 操作 makeFriend 的輸出消息 makeFriendResponse 有一個 Person 類型的消息部件 makeFriendReturn。根據腳本,復雜類型 Person 必須有一個 xsd:string 類型名字的直接子元素,和一個 Person 類型的友元,並且不能有其它類型和屬性。然而,序列化的 SOAP 響應消息( 清單 3)當然不符合這個規則。響應消息中的 href 沒有在定義中出現。對於這個問題,怎樣告訴接收者元素 A 和 B 是 Person 類的實例呢?
事實上,如果您編寫一個 .NET 客戶來與這個 Web 服務交互, .NET 客戶將不能夠正確地逆序列化 清單 3 所示的 WebSphere 響應。.NET 客戶期望的描述 John 和 Jason 是友元的響應消息如下所示:
清單 5:來自 .NET 的 SOAP 編碼響應消息<soap:Body
soap:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/"><tns:makeFriendsResponse>
<makeFriendsResult href="http://www.bianceng.cn/index.php#id1" />
</tns:makeFriendsResponse>
<types:Person id="id1" xsi:type="types:Person">
<name xsi:type="xsd:string">John</name>
<friend href="http://www.bianceng.cn/index.php#id2" />
</types:Person>
<types:Person id="id2" xsi:type="types:Person">
<name xsi:type="xsd:string">Jason</name>
<friend href="http://www.bianceng.cn/index.php#id1" />
</types:Person></soap:Body>
正如您看到的,多引用訪問器編碼是很強大的,但是它很難在 XSD 中表達。因此在不同的平台之間的實現有些細微的不同,如 清單 3 和 清單 5 所示。示例說明了 SOAP 編碼和 XML 腳本校驗之間的不同。Document/literal 通過文本文檔來傳遞信息並基於 XML 腳本來校驗和逆序列化對象的 XML 表示。不同於 Document/literal , RPC/encoded 模型使用 SOAP 編碼規則來表述抽象 SOAP 數據模型,並依賴廠商的 SOAP 庫來提供抽象數據模型的具體實現。它是高度平台獨立的。它不需要中間的規范來填補空白,因此,這種方式對於不同廠商的實現是開放的。盡管 SOAP 是標准,由於缺少 XML 腳本支持,SOAP 實現(如 Application Developer V4 和 V5 的基於 DOM 的 Apache SOAP 庫和基於 SAX 的 Apache AXIS 庫、MS .NET 1.1 框架的 MS SOAP 工具包 )還是有細微不同。
利用 WS-I 一致性測試工具檢測一致性
WS-I 基本規范為遵守 Web 服務規范來構建互操作的 Web 服務提供了指導。WS-I 工作組開發了 WS-I 一致性測試工具來幫助開發人員判斷它們的 Web 服務是否遵循規范指南。WS-I 測試工具分析器可以產生一個規范一致性報告來記錄您的 WSDL 文檔的一致性聲明。目前,測試工具的 C# 和 Java 版本都可以下載。通過運行測試工具,WS-I 基本規范 1.0 的大部分明顯的沖突都會被報告。清單 4 中的 WSDL 文檔的一致性檢查測試的沖突如 清單 6 中所示。
清單 6. 從 WS-I 測試工具獲取的失敗報告Result: failed
Failure Message: The use attribute of a soapbind:body, soapbind:fault,
soapbind:header and soapbind:headerfault does not have value
of "literal".
Failure Detail Message:
SOAPBody ({http://schemas.xmlsoap.org/wsdl/soap/}body):
required=null
use=encoded
encodingStyles=[http://schemas.xmlsoap.org/soap/encoding/]
namespaceURI=http://cyclic.test
Element Location:
lineNumber=87
失敗消息是自我解釋的: soapbind:body、 soapbind:fault、 soapbind:header 或 soapbind:headerfault 中的 "use=encoded" 被認為是不遵守 WS-I 規范的。您應該使用 "use=literal" 來代替。由於毫無疑問要使用 Document/encoded,也就是說,您不應該使用 RPC/encoded 方式。
盡管 WS-I 一致性測試工具並不能夠捕捉到 Web 服務互操作相關的所有問題,但它仍舊是個很強大的工具。與開發 Web 服務一起,不斷開發復雜的測試工具包來捕捉潛在的互操作問題是一個很好的實踐。關於工具的更多詳細信息,請參閱 參考文獻 部分的文章。
結束語
作者強調了在真正實現 WSDL 中的 Web 服務語法之前必須仔細設計,解釋了為什麼 SOAP 編碼是 Web 服務互操作的主要障礙,並演示了構建復雜測試包的重要性來測試 WS-I 規范的一致性。在隨後的系列文章中,作者將討論由 inbound 和 outbound 數據類型和命名空間轉換而導致的互操作性問題。