2005 年 10 月 10 日 學(xué)習(xí)如何使用應(yīng)用動(dòng)態(tài)代理模式(Dynamic Proxy Patterns)來進(jìn)行動(dòng)態(tài)解耦的 Web 服務(wù)適配器(Web Service Adapters)。通過適當(dāng)?shù)厥褂眠@種機(jī)制,您可以提供所需要的抽象級(jí)別,這樣有助于適當(dāng)?shù)拿嫦蚍?wù)的體系結(jié)構(gòu)(Service-Oriented Architecture,SOA)的實(shí)現(xiàn)與服務(wù)重用。 理想的基于面向服務(wù)的體系結(jié)構(gòu)系統(tǒng)設(shè)計(jì)的需求是在服務(wù)消費(fèi)者與服務(wù)提供者之間進(jìn)行解耦。解耦可采用多種形式,其范圍從靜態(tài)(在編譯時(shí)通過請求端存根來解耦)到全動(dòng)態(tài)解耦(在所有服務(wù)調(diào)用的運(yùn)行時(shí)完全封裝并建立解耦)。顯然,全動(dòng)態(tài)解耦是一個(gè)嚴(yán)格的要求。如果一個(gè)服務(wù)被動(dòng)態(tài)解耦,然后服務(wù)特性中的更改導(dǎo)致了服務(wù)實(shí)現(xiàn)中的修正,但是所有其它的系統(tǒng)元素,特別是調(diào)用機(jī)制,仍舊保持不變。這種設(shè)計(jì)的優(yōu)點(diǎn)顯而易見。 對(duì)于 SOA 與 Web 服務(wù)的更強(qiáng)大功能與性能的日益增長的期望與需求經(jīng)常導(dǎo)致顯著增長的軟件復(fù)雜性。今天,SOA 實(shí)現(xiàn)的實(shí)際情況是,盡管規(guī)劃了服務(wù)的功能模塊化,但是它們?nèi)耘f經(jīng)常與獨(dú)立的中間件或通信協(xié)議(如基于 MOM 的 JMS、HTTP/SOAP 等等)耦合。在大多數(shù)組織中,當(dāng)應(yīng)用程序擴(kuò)展功能時(shí),開發(fā)者關(guān)注的,在某種程度上,是靜態(tài)解耦(通常是指綁定),并強(qiáng)制為每一個(gè) Web 服務(wù)創(chuàng)建獨(dú)立的客戶端,而不是單獨(dú)的能夠同時(shí)訪問多個(gè)服務(wù)的客戶端。 靜態(tài)綁定的局限性是非常明顯的——代碼中的靜態(tài)綁定阻止了不同應(yīng)用程序中貫穿整個(gè)企業(yè)的服務(wù)的重用。簡而言之,即使需要提取并封裝所用的不同中間件及協(xié)議來執(zhí)行服務(wù)組件之間的交互,這些組件被包括在服務(wù)消費(fèi)者與服務(wù)提供者的交互中,這樣的需求非常普遍,但是封裝了 Web 服務(wù)接口的體系結(jié)構(gòu)還未得到廣泛應(yīng)用。造成這種狀態(tài)的其中一個(gè)主要原因就在于,盡管動(dòng)態(tài)綁定的幾種機(jī)制已被設(shè)計(jì)用來適應(yīng)這種局限性,但是對(duì)動(dòng)態(tài)綁定相關(guān)聯(lián)的實(shí)際技術(shù)依然存在很多的誤解。
已經(jīng)有許多關(guān)于 SOA 最重要的概念——服務(wù)接口(換句話說,調(diào)用功能)及在運(yùn)行時(shí)如何定位并調(diào)用它們的文章。換句話說, Web 服務(wù)調(diào)用應(yīng)該在后期綁定。需要指明的是 Web 服務(wù)調(diào)用已經(jīng)被設(shè)計(jì)為傳統(tǒng)的 Remote Procedure Call(RPC)方法,該方法準(zhǔn)許一個(gè)應(yīng)用程序調(diào)用由第二個(gè)應(yīng)用程序發(fā)布的功能。RPC 工作方式如下(如圖 1 所示):
圖 1. 基于 RPC 的 Web 服務(wù)調(diào)用 ![]() 在 Web 服務(wù)中,存根代碼通常是自動(dòng)產(chǎn)生的,并且是基于使用被調(diào)用的功能的接口描述,它通過 Web 服務(wù)描述語言(Web Services Description Language,WSDL)來表示,并創(chuàng)建了存根 shell。此外,存根通常是在應(yīng)用程序開發(fā)的編碼階段產(chǎn)生,因?yàn)榇娓侵饕菑膽?yīng)用程序代碼中直接調(diào)用,而且,結(jié)果是必須在編譯時(shí)解決的(換句話說,即所謂的前期綁定)。 大多數(shù)應(yīng)用程序開發(fā)者,主要是 Java 開發(fā)者,認(rèn)為使用這樣的存根是必要的,因?yàn)閷?duì)他們來說,如何處理從調(diào)用服務(wù)返回的復(fù)雜數(shù)據(jù)類型是不清楚的。當(dāng)然,來自被調(diào)用應(yīng)用程序功能的數(shù)據(jù)必須同 SOAP 消息相分離,而且,當(dāng)使用 Java 的時(shí)候,必須有一個(gè)相對(duì)應(yīng)的(兼容的)實(shí)現(xiàn)序列化接口的類。顯然,同樣的存根產(chǎn)生工具被用于產(chǎn)生那些所需求的類。 本文的首要思想就是,一般來說,通常自動(dòng)化是雙刃劍。一方面,自動(dòng)化準(zhǔn)許行業(yè)內(nèi) Web 服務(wù)的快速適應(yīng),其原因是,有了自動(dòng)化工具,大多數(shù)應(yīng)用程序開發(fā)者的行為不再發(fā)生在 WSDL 自身的級(jí)別上。另一方面,不幸的是,市場仍需要一種主要的工具來選擇需要使用的調(diào)用形式——顯式的存根(存根在編譯時(shí)解析)或隱式的存根(存根在運(yùn)行時(shí)解析)。隱式的存根通常被當(dāng)成 Web 服務(wù)的無存根調(diào)用。 大多數(shù)基于 Java 的 Web 服務(wù)工具提供了一些 Web 服務(wù)接口的運(yùn)行時(shí)后期綁定功能。來自 webMethods 的 Glue 產(chǎn)品與 J2EE Web Services Developer 組件的 JAX-RPC 包就是兩個(gè)例子。然而,這些嘗試大多導(dǎo)致了軟件深受供應(yīng)商特定邏輯的影響。而且,問題并不僅僅存在于供應(yīng)商指定的代碼。在許多情況下,供應(yīng)商專有的解決方案產(chǎn)生了實(shí)際的可管理性與可維護(hù)性的問題。 現(xiàn)在讓我們回顧提到的問題點(diǎn),例如用于后期綁定的 webMethods Glue 方式。目前,Glue 是廣泛使用的工具。由于使用了一個(gè)代理用于 Web 服務(wù)的接口,Glue 看起來好象是無所不能。然而,僅僅通過自身使用一個(gè)代理并不能消除所有的問題。下面的 Java 代碼片斷描述了在它們的應(yīng)用中的一個(gè)典型的負(fù)面情形: 清單 1. lateBindingGlue
您能從上面的例子中學(xué)到很多東西。首先,由 Glue 引入的代碼是一個(gè)兩層代碼。一層“消除”了 SOAP 分離的負(fù)面效應(yīng),通過使用 Java 異常處理程序和一個(gè)專門的組件——SOAP 攔截器來攔截這種特定的應(yīng)用程序異常而實(shí)現(xiàn)。第二層將服務(wù)調(diào)用的結(jié)果作為完整的 XML 文檔來提交給調(diào)用應(yīng)用程序。然后,XML 文檔通過使用一個(gè)諸如 DOM 結(jié)構(gòu)的工具進(jìn)行分離。顯然,這樣的方法不能“安全地”移植到其它 Web 服務(wù)工具中。但更重要的是,盡管攔截應(yīng)用程序異常的技術(shù)廣為人知,但它真的適合于 Web 服務(wù)調(diào)用嗎? 一般而言,異常是 Java 語言極為有用的特性,特別是,這意味著一種檢測錯(cuò)誤位置、無計(jì)劃的編程操作(如使用空指針)的簡單方式。使用這種技術(shù)作為一個(gè)計(jì)劃的控制操作,而不是寫一個(gè) if-then 邏輯段來測試一個(gè)指針是否真正為空,使代碼的可管理性與可維護(hù)性顯著地復(fù)雜化。顯然,SOAP 分離并不同于空指針。您不能通過一個(gè)簡單的 if-then 代碼測試出來。不過,它們的負(fù)面影響是相同的——拋出異常并捕獲它,這是一項(xiàng)代價(jià)非常高昂的技術(shù),特別是如果您需要獲得每秒數(shù)千次的 Web 服務(wù)調(diào)用的話。換句話說,運(yùn)行時(shí)異常應(yīng)該專門針對(duì)意外情況作為“防御線”而保留,以應(yīng)對(duì)軟件的錯(cuò)誤。 我進(jìn)一步指出為什么分離 SOAP 的問題不能通過一個(gè) Java Exception API 進(jìn)行處理的更多原因。但是,讓我們先來看看引用的范例所顯現(xiàn)的另外一個(gè)問題。由于調(diào)用操作需要直接分析 XML,它不得不訪問響應(yīng)信息的 XSD 定義,而且必須依次成為提供的 WSDL 文件的一部分(且被存根產(chǎn)生工具所使用)。這里的最終結(jié)果就是甚至一些后期綁定的形式也存在于引例之中;在 Web 服務(wù)伙伴之間的緊耦合依舊存在。換句話說,Web 服務(wù)的客戶端必須訪問 WSDL 文件,并且在大多數(shù)情況下,它必須是一個(gè)完整(而不是部分)的 WSDL 文件。 還存在一個(gè)與使用 JAX-RPC API 相關(guān)的問題。記住這是非常必要的:在 Java 語言中,由于在 XML schema 中的數(shù)據(jù)類型不能直接準(zhǔn)確地映射到 Java 語言的數(shù)據(jù)類型當(dāng)中,所以在調(diào)用的應(yīng)用程序端 WSDL 中產(chǎn)生的服務(wù)終端接口將與 JAX-RPC 編譯器在調(diào)用端產(chǎn)生的 WSDL 的形式不同。而且,生成的服務(wù)接口也將依賴于 SOAP 采用的編碼體制。例如,使用文檔文字編碼利用了每個(gè)方法類的封裝版本,并造成了額外的可管理性與可維護(hù)性的問題。
Web 服務(wù)編程構(gòu)件的自由應(yīng)用程序邏輯:最佳實(shí)踐設(shè)計(jì)模式 根據(jù)到目前為止的講述,您容易想到使用任何種類的動(dòng)態(tài)調(diào)用技術(shù)(如后期綁定的動(dòng)態(tài)代理),不幸的是,許多開發(fā)者都是這樣做的。關(guān)于 Web 服務(wù)其中一個(gè)最常見的誤解就是,使用動(dòng)態(tài)調(diào)用取代靜態(tài)存根的好處并沒有那么大,而且最好是堅(jiān)持用生成的存根作為 Web 服務(wù)調(diào)用的主要方法。 最后,我們的討論轉(zhuǎn)到第二個(gè),而且是最重要的原則——它不是真正的編程技術(shù),它確保不依賴服務(wù)編程構(gòu)件(如 WSDL),但是取而代之的是,它的價(jià)值體現(xiàn)在高級(jí)設(shè)計(jì)中。這對(duì)于獲得 SOA 的真正利益是必要的,因?yàn)殡S著服務(wù)的發(fā)展,服務(wù)消費(fèi)者的代碼將完全不受影響。無論如何,基于 SOA 的應(yīng)用程序必須注重于提供彈性體系結(jié)構(gòu),且更少的關(guān)注于公共通信協(xié)議(比如 SOAP)。服務(wù)與應(yīng)用之間不應(yīng)該具有嚴(yán)格的界限。一個(gè)終端用戶的應(yīng)用程序可以被看作是其它終端用戶的服務(wù)。整個(gè)企業(yè) IT 環(huán)境應(yīng)該被設(shè)計(jì)成高度模塊化,允許開發(fā)者能夠挑選適合他們需要的服務(wù)與應(yīng)用組合。 在良好的 SOA 設(shè)計(jì)中,Web 服務(wù)消費(fèi)者的應(yīng)用邏輯能夠使用兩個(gè)基礎(chǔ)體系結(jié)構(gòu)原則來從服務(wù)構(gòu)件完全解耦:
最終設(shè)計(jì)目標(biāo)是,為 Web 服務(wù)消費(fèi)者應(yīng)用提供一組類,這些類根據(jù)上面引述的原則組成了可重用適配器層。這種適配器層封裝了使用存根及其生成的類的代碼。適配器的公共 API 并不公開任何存根類;而是將其映射到 Web 服務(wù)消費(fèi)者應(yīng)用可以理解的類。 為了得到最好的適配器靈活性與可擴(kuò)展性,適配器的總體類結(jié)構(gòu)均通過使用下列設(shè)計(jì)模式的組合來構(gòu)建:
圖 2 演示了使用上面引用的設(shè)計(jì)原則而設(shè)計(jì)的“好萊塢”類型適配器的概念模型。 圖 3 展示了使用適配器時(shí)的基本交互的序列圖。 圖 2. Web 服務(wù)適配器的概念模型 ![]() 圖 3. 包含 Web 服務(wù)適配器的序列圖 ![]() 創(chuàng)建一個(gè)使用適配器的服務(wù),包括創(chuàng)建一個(gè)使用服務(wù)描述符(代表 SOAP 服務(wù))的服務(wù)類。這個(gè)描述符被包括在服務(wù)類成員之中。服務(wù)類被作為 SOAP 來部署,例如,使用來自 Open Source Foundation 的 Axis。 服務(wù)描述符包括:
現(xiàn)在,有一個(gè)關(guān)于 Axis 的耦合注意事項(xiàng)。使用 Axis 作為 SOAP 引擎非常有助于實(shí)現(xiàn) Web 服務(wù)的松耦合,理由如下:
在適配器的設(shè)計(jì)中,應(yīng)該對(duì)性能進(jìn)行特殊考慮。使用動(dòng)態(tài)代理類具有性能含義。訪問目標(biāo)的直接方法快于訪問代理類的方法。然而,這不應(yīng)是在穩(wěn)健體系結(jié)構(gòu)與性能之間進(jìn)行選擇。這就是為什么目前適配器實(shí)現(xiàn)緩存封裝器的原因。通過應(yīng)用緩存,使用靜態(tài)存根與基于適配器的解決方案之間的差異相對(duì)較小。根據(jù)如何實(shí)現(xiàn)緩存,必須提到可能的解決方案之一——記憶(Memoization)。 記憶是一種廣泛使用的技術(shù),它在 Lisp、Python 與 Perl 這樣的功能編程語言中使用,給功能賦予預(yù)先計(jì)算的值。記憶一個(gè)功能將為功能添加一個(gè)透明的緩存封裝,因此已經(jīng)得到的值將從緩存中返回而不是每次都重建。記憶可以顯著提高動(dòng)態(tài)代理調(diào)用的性能。 總結(jié)我們關(guān)于此處描述的適配器的討論,允許支持本地和遠(yuǎn)程服務(wù)實(shí)現(xiàn)的設(shè)計(jì)是非常重要的。服務(wù)類對(duì)適配器來說將是本地的,而遠(yuǎn)程 Web 服務(wù)則可以相互替換,因?yàn)榉?wù)類和代理類使用同樣的接口來訪問遠(yuǎn)程 Web 服務(wù)。本地服務(wù)類將實(shí)現(xiàn)與 Web 服務(wù)器遠(yuǎn)程實(shí)現(xiàn)相類似的方法 清單 2. 本地服務(wù)類及其接口
實(shí)現(xiàn)了 IService 的代理類采用了方法 清單 3. 遠(yuǎn)程服務(wù)
顯然,使用 Web 服務(wù)的靜態(tài)存根與前期綁定是 Web 服務(wù)調(diào)用的最簡單的形式。但是簡單化也帶來了明顯的缺點(diǎn)。與緊耦合方法不同,使用本文提出的適配器方法將給您留下可高度重用和可配置的 Web 服務(wù)代碼。由于 Web 服務(wù) 調(diào)用全部通過部署了服務(wù)描述符的公共適配器來引導(dǎo),因此您能夠動(dòng)態(tài)地決定調(diào)用什么服務(wù)——在部署代碼時(shí)或在運(yùn)行時(shí)。 同樣,那些可能尋找商業(yè)產(chǎn)品作為 Web 服務(wù)松耦合調(diào)用定制解決方案的組織應(yīng)該研究企業(yè)服務(wù)總線(Enterprise Service Bus,ESB)技術(shù)(參見參考資料),該技術(shù)提供了類似于上面所引述的功能的選項(xiàng)來連接企業(yè)內(nèi)部及跨企業(yè)的可作為 Web 服務(wù)的應(yīng)用程序,并具備一套功能來管理并監(jiān)視已連接應(yīng)用之間的交互。
|
聯(lián)系客服