簡介: ESB作為SOA的基礎設施,在構建SOA的過程中起著舉足輕重的作用,本文是ESB系列文章中的第二篇。在第一篇文章中,我們對ESB的基礎知識進行了詳細的介紹,本文將著重對IBM最新的應用服務器WebSphere 6中對ESB的支持進行實例化的介紹,希望通過具體的例子讓讀者更快,更方便的利用WebSphere 6的提供的基礎設施向SOA(Service Oriented Architecture)進行遷移。
ESB作為SOA的基礎設施,在構建SOA的過程中起著舉足輕重的作用,本文是ESB系列文章中的第二篇。在第一篇文章中,作者對ESB的基礎知識進行了詳細的介紹,本文將著重對IBM最新的應用服務器WebSphere 6中對ESB的支持進行實例化的介紹,希望通過具體的例子讓讀者更快,更方便的利用WebSphere 6的提供的基礎設施向SOA(Service Oriented Architecture)進行遷移。
為了使本文更具有普遍性,在本文的樣例中,作者模擬了一般可能出現(xiàn)的場景并給出了具體的討論和實現(xiàn)。通過本文,讀者最終可以自己利用WebSphere 6的SIBus實現(xiàn)ESB, 而本文所有的源代碼和腳本將會提供給讀者作為詳細的參考。同時,本文中涉及的許多概念和基本操作流程本文所列的參考資料中都有詳細介紹,本文就不再詳細解釋了。
本樣例是一個簡化了的零部件價格查詢模塊,通常,一個制造企業(yè)所需要的零配件可能來自各個廠商,也有可能是自己制造的,同時,它所制造的零件也可能被它的內(nèi)部用戶或者外部用戶來查詢。由于零件的價格變化比較頻繁,所以這些價格的查詢需要是即時的價格。下面是這個樣例的Use case圖:
2. 利用WebSphere 6中的SIBus實現(xiàn)ESB
在本樣例中,零部件的供應廠商的變化是頻繁的,各個廠商的報價系統(tǒng)有可能是多種多樣的,而且本模塊還需要為企業(yè)內(nèi),企業(yè)外的客戶進行服務,如何構造一個靈活的,可擴展的體系結構來適應復雜變化的環(huán)境已達到隨需應變的需求?SOA是解決這個問題的好方法!
首先,我們需要用WSDL來定義零部件價格查詢的服務,這個WSDL文件相當于一個契約(Contract),它定義了這個服務的具體交互方式,所有的服務請求者和服務提供者都遵循這個契約,但是具體服務的實現(xiàn)方式,交互協(xié)議并不需要緊耦合在WSDL中,而且作為SOA的目標之一,使用者是無需知道服務者的端點地址和綁定方式的。
接著,我們需要把這個服務架構在ESB上。在SOA中,ESB是不可缺少的,核心的基礎設施,因為服務將最終在其上進行整合。WebSphere 6中全新的Service Integration Bus(SIBus)組件是實現(xiàn)ESB概念良好的選擇,它的提出是為了更明確的為服務集成,服務整合提供支持,關于SIBus中的一些基本概念和它的配置、管理,用戶可以從WAS6 Info Center得到幫助,本文將注重如何利用SIBus來實現(xiàn)ESB的基本功能。
首先我們來看看在SIBus上,我們怎么架構這個應用,為了使讀者有個整體的把握,我們先來看看整體的架構圖:
下面就讓我們來看看SIBus為實現(xiàn)ESB都做了哪些基礎工作,每個基礎工作又都為我們的樣例應用提供了怎樣的支持:
為了實現(xiàn)本文的樣例,我們需要定義好零部件查詢服務WSDL,然后實現(xiàn)內(nèi)部服務,開發(fā)需要的消息中介,發(fā)布這個應用,接著需要對內(nèi)外部服務進行整合,最后把整合后的服務發(fā)布出去。為了本文樣例的演示,還需要開發(fā)模擬的外部服務,模擬的內(nèi)部客戶和外部客戶來使整個流程運轉(zhuǎn)起來,下面我們就詳細介紹各個部分的實現(xiàn)方法和關鍵點:
首先,對內(nèi)部服務,我們按照JSR 109的方式定義為Web Service。JSR 109規(guī)范定義的服務有兩種基本方式,在Web Module中的Java Bean的方式和在EJB Module中的EJB方式,本文的樣例中對外部服務采用了第一種方式來實現(xiàn),對內(nèi)部服務采用了第二種方式來實現(xiàn),這為的是給讀者提供全面的參考,同時也更符合實際的場景。
內(nèi)部服務的具體實現(xiàn)可以參考樣例中InsideService_JAR模塊。這里需要指出的是當使用Java2WSDL來產(chǎn)生ejb綁定的WSDL的時候,WebSphere 6引入了新的EJB的綁定方式,這為的是省去SOAP方式的序列化和反序列化的過程,直接通過RMI-IIOP來進行更高效的通信,下面就是內(nèi)部服務WSDL中綁定部分的定義:
<?xml version="1.0" encoding="UTF-8"?> <wsdl:definitions targetNamespace="http://partsinfo.com" xmlns:generic="http://www.ibm.com/ns/2003/06/wsdl/mp" xmlns:ejb="http://www.ibm.com/ns/2003/06/wsdl/mp/ejb" xmlns:impl="http://partsinfo.com" …> …… <wsdl:binding name="PartsInfoEjbBinding" type="impl:PartsInfo"> <ejb:binding/> <wsdl:operation name="getPartPrice"> <ejb:operation methodName="getPartPrice"/> <wsdl:input name="getPartPriceRequest"> </wsdl:input> <wsdl:output name="getPartPriceResponse"> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="PartsInfoService"> <wsdl:port binding="impl:PartsInfoEjbBinding" name="PartsInfo_SEIEjb"> <generic:address location= "wsejb:/com.insidecompany.PartsInfoHome?jndiName=……"/> </wsdl:port> </wsdl:service> </wsdl:definitions> |
請注意這里的EJB綁定方式和它的端點地址的表達方式,這種綁定和WSIF的EJB綁定是不一樣的,WSIF在WebSphere 6中已經(jīng)不再被建議使用。讀者可以參考Info Center獲得Multi-protocol JAX-RPC詳細的信息和Java2WSDL的具體語法。
對外部服務,我們用一個Web模塊來模擬,它是一個按照JSR 109方式定義在Web Module中的以Java Bean方式實現(xiàn)的Web Service,具體實現(xiàn)可以參考樣例中的OutsideService_WAR模塊。需要注意的是:外部服務WSDL接口定義的方法和內(nèi)部服務是不一樣的,外部服務WSDL定義的服務方法是:getPartPriceOfOutService,內(nèi)部服務WSDL定義的方法是: getPartPrice。
SIBus的消息中介框架是我們實現(xiàn)消息路由和格式轉(zhuǎn)換的基礎,開發(fā)者可以利用Rational Application Developer來開發(fā),部署各種消息中介。我們這里將簡要的介紹一下實現(xiàn)的關鍵,具體細節(jié)請參考Mediation_JAR模塊中的QueryDispatcher類和Adaptor類:
在本文樣例中,我們將根據(jù)零件的編號的前綴來決定它屬于哪個零部件廠商,從而把價格查詢請求轉(zhuǎn)發(fā)到該零部件廠商的服務所對應的端口目標上。具體的選擇規(guī)則我們是通過配置目標上下文屬性的方式來提供給消息中介的,詳細情況可以請參考4.3中的說明。那么選擇好了目的地后,怎么路由消息到那個目的地,而不是原來缺省的地址呢?我們來看看SIBus是怎樣處理消息的分發(fā)的:
在SIBus中,消息的路徑是直接放在消息中的,路徑分為前進路徑和返回路徑,每個路徑都是一個SIBus上目標地址(Destination)的列表,消息傳送引擎所做的工作就是從前進路徑列表中取出第一個地址,然后把消息轉(zhuǎn)發(fā)到這個地址所對應的目標。所以如果想更改消息的前進路徑,我們只需把路徑列表取出來,然后更改這個列表就可以了。下面的代碼演示了如何把把消息轉(zhuǎn)發(fā)到已選出的端口目標portDestination上。
…… List frp = msg.getForwardRoutingPath(); //取得前進路徑列表 SIDestinationAddress destination = //根據(jù)portDestination生成SIBus的目標地址類 SIDestinationAddressFactory .getInstance() .createSIDestinationAddress( portDestination, false); frp.add(0, destination); //把目標地址插在第一個位置 msg.setForwardRoutingPath(frp); //把前進路徑放回消息體 …… |
在本文樣例中,外部零件廠商的服務接口和內(nèi)部零件廠商的接口并不一樣,所以我們需要在對外部服務請求發(fā)出去之前進行一定的消息格式轉(zhuǎn)換,把消息轉(zhuǎn)換成外部服務的格式才行,當然,這種轉(zhuǎn)換必須在邏輯上是可行的才可以實現(xiàn)。我們先來看看SIBus中的消息格式:
在SIBus中,各種消息格式將統(tǒng)一在SDO(Service Data Object)接口之上,我們只需通過SDO接口就可以對數(shù)據(jù)進行各種操作,包括格式轉(zhuǎn)換,而無需使用各種不同形式的API,這無疑將大大減輕了開發(fā)者的負擔。回到本例,我們需要對消息格式進行轉(zhuǎn)換,具體的說就是需要變換操作的名稱,從getPartPrice轉(zhuǎn)到getPartPriceOfOutService,下面是本文樣例中如何對消息格式進行變換的關鍵代碼:
…… if("getPartPrice".equals(operationName)){ //對前進消息進行中介 String serviceDestination="dest:ESB_SHOWOFF:http://partsinfo.com:PartsInfoService"; String graphFormat="SOAP:"+serviceDestination+", http://partsinfo.com,PartsInfoService,PartsInfo"; String requestValue=info.getDataObject("body").getString("itemID"); DataGraph graphNew=SdoRepositoryCache.instance().createDataGraph(serviceDestination); DataObject root=graphNew.createRootObject(graph.getRootObject().getType()); info=null; info=root.createDataObject("info"); info.setString("operationName", "getPartPriceOfOutService"); info.setString("messageName", "getPartPriceOfOutServiceRequest"); info.setString("messageType", "input"); DataObject body=info.createDataObject("body", //生成新的數(shù)據(jù)類型 "http://partsinfo.com","getPartPriceOfOutServiceRequest"); body.setString("itemID",requestValue); …… |
需要注意的是:
現(xiàn)在,內(nèi)部服務,外部服務都有了,消息處理中介也有了。下面我們就要通過新建一個出站服務來把他們整合在一起。我們需要給出站服務提供一個完整WSDL定義,來表示可以通過出站服務的數(shù)據(jù)類型,對本文樣例來說,它需要涵蓋內(nèi)部和外部所有數(shù)據(jù)類型的定義,具體定義可以參考InsideClient_WAR模塊中的PartsInfo.wsdl。我們在這個WSDL中定義了三個可以使用的端口:PartsInfo,PartsInfo_SEIEjb和PartsInfo_SIB,也就是有三個服務提供者可以選擇,他們分別對應HTTP,wsejb和sib類型的綁定。同時,在新建出站服務的時候,我們可以同時把服務選擇消息中介綁定到出站服務目標上,而消息轉(zhuǎn)換的消息中介則需要在完成新建出站服務后手工綁定到外部服務所對應的端口目標上。我們可以通過管理控制臺或者wsadmin命令行的方式配置出站服務,詳細的腳本和參數(shù)請參考附件中的腳本ConfigSamples.jacl,最終配置結果在管理控制臺中顯示如下:
最后一步,我們需要把企業(yè)內(nèi)部的服務發(fā)布出去,讓外部的客戶能夠訪問到我們的服務。我們可以通過新建一個入站服務來把SIBus內(nèi)部的服務目標通過HTTP(S)或者(Secure)JMS Listener為外部用戶提供統(tǒng)一的訪問入口點。
新建入站服務的時候,我們需要提供一個模板WSDL來定義這個入站服務可以接收的服務請求,在這個模板WSDL中,我們只需定義我們需要向外部提供服務的端口類型就可以了,綁定類型和端點地址都不需要提供,實際上這些都是由Endpoint Listeners來具體決定的,我們一般稱這種WSDL為non-bound WSDL,具體內(nèi)容請參考InsideClient_WAR模塊中的PartsInfo_Templet.wsdl。配置入站服務的參數(shù)請參考附件中的腳本ConfigSamples.jacl,最終配置結果在管理控制臺中顯示如下:
在本文的樣例中,我們在一個企業(yè)應用(WAS6ESB.ear)中模擬了各種角色,下表總結了這個應用的各個部分的和它們所扮演的具體角色,具體的部署后的結構圖可以參考前面的整體架構圖:
這個應用是基于Ant來構建的,讀者也可以使用WebSphere Server Toolkit或者Rational Application Developer來輔助開發(fā)。打包文件中已經(jīng)包含了Build好的結果,各種腳本在Scripts目錄下,對具體腳本的功能請參考目錄下的ReadMe.txt。
在部署的過程中,以下幾點是需要注意和說明的地方:
1. WebSphere 6安裝好后缺省是沒有SIBus環(huán)境的,所以首先要建好SIBus,配置好Endpoint Listeners,讀者可以用管理控制臺來實現(xiàn),也可以用作者提供的腳本來自動配置(WasSetupSIBus.bat),不過讀者需要更改一下腳本中相關的目錄信息。建好SIBus后還需要重啟一下才能使它生效,要不然應用會報找不到引擎的錯誤。
2. 安裝企業(yè)應用WAS6ESB.ear的時候可以使用腳本InstallWAS6ESBApp.jacl來自動進行,出站,入站和消息中介的配置,讀者可以使用腳本ConfigSamples.jacl來自動進行,用戶也可以自己通過控制臺來完成,使用控制臺時需要的參數(shù)可以參考腳本中的相關信息。
3. 目標上的上下文配置信息可以被靈活的利用來給消息中介提供幫助,本文樣例中,我們就是通過在服務目標上加上上下文屬性來決定服務的選擇規(guī)則。我們加入了下列屬性值:
這里,我們用屬性的名稱來代表零件標號的前綴,屬性的值代表外部服務所對應的端口目標的名字,DispatcherMediation根據(jù)這些上下文屬性來選擇服務。這樣做的好處是當有新的服務提供者加入的時候,我們只要增加一個端口目標并在服務目標上下文屬性中加上規(guī)則就可以了。不過,WebSphere 6中沒有提供腳本配置目標上下文屬性的方法,所以這些屬性需要手工加入。
4. SIBus為我們提供了靈活的運行時配置的支持,我們可以在應用部署以后動態(tài)更改端口目標中的端點地址和綁定空間,在本文樣例中,我們就可以利用這個功能在新建完出站服務后更改各個端口目標的端點地址設置綁定方法而無需修改已有的應用和出站服務的配置,這個配置過程叫做Retarget。
5. 在SIBus中,所有流動的數(shù)據(jù)必須是有類型的,也就是必須有Schema來明確定義的,所以當我們需要更改消息格式的時候,需要把外部的服務的數(shù)據(jù)類型在新建出站服務的過程中的WSDL中有明確的定義,具體情況請參考新建出站服務過程中使用的WSDL文件。
6. 在內(nèi)部客戶端,我們提供了wsejb和sib兩種綁定方式的動態(tài)DII調(diào)用的實現(xiàn),因為這些都是私有的綁定方式,所以需要加一些特有的屬性。DII方式比較靈活,不需要用WSDL2Java工具生成各種靜態(tài)的輔助類,而靜態(tài)方式的代碼比較簡單,用戶不需要知道具體綁定和地址的細節(jié),關于wsejb靜態(tài)綁定的方式讀者可以參考WebSphere 6本身的例子,本文樣例中就不再贅述了,sib沒有靜態(tài)綁定的支持。
7. 外部客戶端應用可以用任何標準的JAX-PRC客戶端來模擬,對服務的具體WSDL可以從如下地址獲得:http://localhost:9080/sibws/wsdl/ESB_SHOWOFF/PartsInfoInboundService,ESB_SHOWOFF和PartsInfoInboundService分別是Bus的名字和入站服務的名稱,SIBus同時還提供發(fā)布相應的入站服務的WSDL到zip文件或者UDDI的功能,用戶可以從管理控制臺獲得相關信息。客戶端實現(xiàn)可以參考本文樣例中的testWebService.java。
8. 內(nèi)部客戶端可以通過訪問:http://localhost:9080/InsideClient/getPartPrice.jsp來測試,輸入查詢零件的編號,如果編號以outside開頭,表明是外部零件,其他的都認為是內(nèi)部零件。服務將返回編號中的數(shù)字,比如: 輸入outside300,將返回零件價格300。輸入inside400,將返回400。
9. 樣例中提供的三個端口目標中,SIB類型的端口目標只是起參考作用,真正使用的時候需要綁定一定的消息中介來做路由,因為sib綁定現(xiàn)階段只支持發(fā)送請求到服務目標。
作為新一代實現(xiàn)ESB的核心組件,SIBus自身有著很多的先天優(yōu)勢,以長遠的眼光來看,它將是架構SOA理想的基礎設施,它的優(yōu)勢體現(xiàn)在:
本文介紹了利用WebSphere 6中的SIBus實現(xiàn)ESB的基本步驟和方法,并提供了一個詳細樣例來幫助讀者更快的進入角色,WebSphere 6中的SIBus還有著很多高級特性這里沒有介紹,我們將在本系列以后的文章中一一介紹。而本系列的下一篇文章將介紹經(jīng)典的,基于WebSphere 5,MQ系列的組件來實現(xiàn)ESB的方法,這些方法都是經(jīng)過實際項目驗證過的,有著充分的企業(yè)級應用的背景,所以在現(xiàn)階段WebSphere 6的SIBus環(huán)境還沒有被大規(guī)模應用的情況下,了解這些內(nèi)容同樣也有著重要的意義,歡迎大家閱讀。
周志榮,IBM SOA Design Center 工程師,從事WebSphere相關的工作,對J2EE,SOA,Linux和數(shù)據(jù)庫技術有著濃厚的興趣,聯(lián)系方式: zhouzr@cn.ibm.com。