引言
ESB作為SOA的基礎設施,在構建SOA的過程中起著舉足輕重的作用,本文是ESB系列文章中的第二篇。在第一篇文章中,作者對ESB的基礎知識進行了詳細的介紹,本文將著重對IBM最新的應用服務器WebSphere 6中對ESB的支持進行實例化的介紹,希望通過具體的例子讓讀者更快,更方便的利用WebSphere 6的提供的基礎設施向SOA(Service Oriented Architecture)進行遷移。
為了使本文更具有普遍性,在本文的樣例中,作者模擬了一般可能出現的場景并給出了具體的討論和實現。通過本文,讀者最終可以自己利用WebSphere 6的SIBus實現ESB, 而本文所有的源代碼和腳本將會提供給讀者作為詳細的參考。同時,本文中涉及的許多概念和基本操作流程本文所列的參考資料中都有詳細介紹,本文就不再詳細解釋了。
1. 應用樣例簡介
本樣例是一個簡化了的零部件價格查詢模塊,通常,一個制造企業(yè)所需要的零配件可能來自各個廠商,也有可能是自己制造的,同時,它所制造的零件也可能被它的內部用戶或者外部用戶來查詢。由于零件的價格變化比較頻繁,所以這些價格的查詢需要是即時的價格。下面是這個樣例的Use case圖:
2. 利用WebSphere 6中的SIBus實現ESB
在本樣例中,零部件的供應廠商的變化是頻繁的,各個廠商的報價系統有可能是多種多樣的,而且本模塊還需要為企業(yè)內,企業(yè)外的客戶進行服務,如何構造一個靈活的,可擴展的體系結構來適應復雜變化的環(huán)境已達到隨需應變的需求?SOA是解決這個問題的好方法!
首先,我們需要用WSDL來定義零部件價格查詢的服務,這個WSDL文件相當于一個契約(Contract),它定義了這個服務的具體交互方式,所有的服務請求者和服務提供者都遵循這個契約,但是具體服務的實現方式,交互協議并不需要緊耦合在WSDL中,而且作為SOA的目標之一,使用者是無需知道服務者的端點地址和綁定方式的。
接著,我們需要把這個服務架構在ESB上。在SOA中,ESB是不可缺少的,核心的基礎設施,因為服務將最終在其上進行整合。WebSphere 6中全新的Service Integration Bus(SIBus)組件是實現ESB概念良好的選擇,它的提出是為了更明確的為服務集成,服務整合提供支持,關于SIBus中的一些基本概念和它的配置、管理,用戶可以從WAS6 Info Center得到幫助,本文將注重如何利用SIBus來實現ESB的基本功能。
首先我們來看看在SIBus上,我們怎么架構這個應用,為了使讀者有個整體的把握,我們先來看看整體的架構圖:
下面就讓我們來看看SIBus為實現ESB都做了哪些基礎工作,每個基礎工作又都為我們的樣例應用提供了怎樣的支持:
- 整合內部和外部服務
從這個框架中,我們可以看到,不管是外部服務還是內部服務,我們都把它們抽象成與端點地址和綁定方式無關的,統一的一個入口點: 服務目標(Service Destination),由它來在SIBus中代表零件查詢這個服務。各個具體的服務提供者在SIBus中抽象成端口目標(Port Destination),由它來代表一個具體的綁定方式和服務訪問點。SIBus為端口目標提供了多種綁定方式的支持,從而使它可以以HTTP(S), (Secure)JMS的方式的傳輸Web Service請求,對WS-Security和JAX-RPC Handler的配置都可以在端口目標級別進行,以適應各個服務提供者不同的要求。這樣一個整合的過程在SIBus中是通過新建一個出站服務的方式來完成(Outbound)。
- 對外發(fā)布內部的服務
對外部用戶,我們把對服務目標的訪問通過綁定在某個End Point Listener上來間接的提供,這樣一個過程在SIBus中是通過新建一個入站服務的方式來完成(Inbound)。這樣做的好處是統一了外部用戶的訪問點,方便了我們的管理,更好的安全性和更方便的性能調優(yōu)。當外部用戶需要訪問某個入站服務的時候,他可以通過入站服務發(fā)布的WSDL來獲得具體的訪問地址和綁定方式。
- 企業(yè)內部對內部服務直接的訪問
對于企業(yè)內部的用戶,我們讓他們通過API Attach的方式來訪問我們抽象出的出站服務。這種API Attach的方式使企業(yè)內部客戶可以通過標準JSR 109規(guī)定的方法來直接訪問出站服務所對應的服務目標,SIBus為此提供了專有的綁定,這種方式比通過End Point Listeners的方式有著更高的效率,從而更適合內部用戶使用。
- 消息靈活的中介
SIBus提供了消息中介的基礎框架和編程模型,用戶可以在SIBus上定制各種消息處理機制,SIBus為消息中介提供足夠的信息讓它來進行各種有效的工作。在本文的樣例中,我們使用了消息中介來解決了下面兩個問題:
- 外部的服務并不一定完全和內部的需求所匹配。在本樣例中,我們的外部服務所暴露的接口方法名字和我們內部服務不一樣,為了解決這個問題,我們開發(fā)了相應的消息中介并將這個消息中介綁定在相應的端口目標上來實現消息格式的轉換。
- 服務選擇的問題,在本文樣例中,我們有多個服務提供者存在,服務目標代表著其身后許多的端口目標及其相關的服務,我們仍然通過開放相應的消息中介來幫助我們實現我們需要的選擇邏輯。
- 對ESB高級特性的支持
SIBus還為ESB提供了和J2EE緊密融合的安全機制,事務的傳輸機制,消息的QoS服務,還有和MQ消息系統的直接互連,雖然這些在本文樣例中沒有涉及,但在后面的文章中,我們將專門介紹這些高級特性。
|
|
3. 在SIBus上實現樣例應用
為了實現本文的樣例,我們需要定義好零部件查詢服務WSDL,然后實現內部服務,開發(fā)需要的消息中介,發(fā)布這個應用,接著需要對內外部服務進行整合,最后把整合后的服務發(fā)布出去。為了本文樣例的演示,還需要開發(fā)模擬的外部服務,模擬的內部客戶和外部客戶來使整個流程運轉起來,下面我們就詳細介紹各個部分的實現方法和關鍵點:
1.實現內部服務
首先,對內部服務,我們按照JSR 109的方式定義為Web Service。JSR 109規(guī)范定義的服務有兩種基本方式,在Web Module中的Java Bean的方式和在EJB Module中的EJB方式,本文的樣例中對外部服務采用了第一種方式來實現,對內部服務采用了第二種方式來實現,這為的是給讀者提供全面的參考,同時也更符合實際的場景。
內部服務的具體實現可以參考樣例中InsideService_JAR模塊。這里需要指出的是當使用Java2WSDL來產生ejb綁定的WSDL的時候,WebSphere 6引入了新的EJB的綁定方式,這為的是省去SOAP方式的序列化和反序列化的過程,直接通過RMI-IIOP來進行更高效的通信,下面就是內部服務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中已經不再被建議使用。讀者可以參考Info Center獲得Multi-protocol JAX-RPC詳細的信息和Java2WSDL的具體語法。
2.實現外部服務
對外部服務,我們用一個Web模塊來模擬,它是一個按照JSR 109方式定義在Web Module中的以Java Bean方式實現的Web Service,具體實現可以參考樣例中的OutsideService_WAR模塊。需要注意的是:外部服務WSDL接口定義的方法和內部服務是不一樣的,外部服務WSDL定義的服務方法是:getPartPriceOfOutService,內部服務WSDL定義的方法是: getPartPrice。
3.實現對消息的路由和格式轉換
SIBus的消息中介框架是我們實現消息路由和格式轉換的基礎,開發(fā)者可以利用Rational Application Developer來開發(fā),部署各種消息中介。我們這里將簡要的介紹一下實現的關鍵,具體細節(jié)請參考Mediation_JAR模塊中的QueryDispatcher類和Adaptor類:
- 消息的路由:
在本文樣例中,我們將根據零件的編號的前綴來決定它屬于哪個零部件廠商,從而把價格查詢請求轉發(fā)到該零部件廠商的服務所對應的端口目標上。具體的選擇規(guī)則我們是通過配置目標上下文屬性的方式來提供給消息中介的,詳細情況可以請參考4.3中的說明。那么選擇好了目的地后,怎么路由消息到那個目的地,而不是原來缺省的地址呢?我們來看看SIBus是怎樣處理消息的分發(fā)的:
在SIBus中,消息的路徑是直接放在消息中的,路徑分為前進路徑和返回路徑,每個路徑都是一個SIBus上目標地址(Destination)的列表,消息傳送引擎所做的工作就是從前進路徑列表中取出第一個地址,然后把消息轉發(fā)到這個地址所對應的目標。所以如果想更改消息的前進路徑,我們只需把路徑列表取出來,然后更改這個列表就可以了。下面的代碼演示了如何把把消息轉發(fā)到已選出的端口目標portDestination上。
……
List frp = msg.getForwardRoutingPath(); //取得前進路徑列表
SIDestinationAddress destination = //根據portDestination生成SIBus的目標地址類
SIDestinationAddressFactory
.getInstance()
.createSIDestinationAddress(
portDestination,
false);
frp.add(0, destination); //把目標地址插在第一個位置
msg.setForwardRoutingPath(frp); //把前進路徑放回消息體
……
|
- 消息的格式轉換:
在本文樣例中,外部零件廠商的服務接口和內部零件廠商的接口并不一樣,所以我們需要在對外部服務請求發(fā)出去之前進行一定的消息格式轉換,把消息轉換成外部服務的格式才行,當然,這種轉換必須在邏輯上是可行的才可以實現。我們先來看看SIBus中的消息格式:
在SIBus中,各種消息格式將統一在SDO(Service Data Object)接口之上,我們只需通過SDO接口就可以對數據進行各種操作,包括格式轉換,而無需使用各種不同形式的API,這無疑將大大減輕了開發(fā)者的負擔。回到本例,我們需要對消息格式進行轉換,具體的說就是需要變換操作的名稱,從getPartPrice轉到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", //生成新的數據類型
"http://partsinfo.com","getPartPriceOfOutServiceRequest");
body.setString("itemID",requestValue);
……
|
需要注意的是:
- 我們對前進的消息進行消息變換的時候,對返回的消息也要進行反向的消息變換才能使服務請求者得到所期望的結果。
- 我們需要一些WebSphere內部的接口(SdoRepositoryCache)來生成新格式的DataGraph,因為所有的數據類型都是在SIBus中的SdoRepository定義的。
- 用戶需要熟悉這些內部接口和SDO的API,不過,本文樣例給出了很好的參考,基本解決了數據格式變換會碰到的問題。
4.整合內部、外部服務
現在,內部服務,外部服務都有了,消息處理中介也有了。下面我們就要通過新建一個出站服務來把他們整合在一起。我們需要給出站服務提供一個完整WSDL定義,來表示可以通過出站服務的數據類型,對本文樣例來說,它需要涵蓋內部和外部所有數據類型的定義,具體定義可以參考InsideClient_WAR模塊中的PartsInfo.wsdl。我們在這個WSDL中定義了三個可以使用的端口:PartsInfo,PartsInfo_SEIEjb和PartsInfo_SIB,也就是有三個服務提供者可以選擇,他們分別對應HTTP,wsejb和sib類型的綁定。同時,在新建出站服務的時候,我們可以同時把服務選擇消息中介綁定到出站服務目標上,而消息轉換的消息中介則需要在完成新建出站服務后手工綁定到外部服務所對應的端口目標上。我們可以通過管理控制臺或者wsadmin命令行的方式配置出站服務,詳細的腳本和參數請參考附件中的腳本ConfigSamples.jacl,最終配置結果在管理控制臺中顯示如下:
5.把SIBus上的服務發(fā)布到企業(yè)外
最后一步,我們需要把企業(yè)內部的服務發(fā)布出去,讓外部的客戶能夠訪問到我們的服務。我們可以通過新建一個入站服務來把SIBus內部的服務目標通過HTTP(S)或者(Secure)JMS Listener為外部用戶提供統一的訪問入口點。
新建入站服務的時候,我們需要提供一個模板WSDL來定義這個入站服務可以接收的服務請求,在這個模板WSDL中,我們只需定義我們需要向外部提供服務的端口類型就可以了,綁定類型和端點地址都不需要提供,實際上這些都是由Endpoint Listeners來具體決定的,我們一般稱這種WSDL為non-bound WSDL,具體內容請參考InsideClient_WAR模塊中的PartsInfo_Templet.wsdl。配置入站服務的參數請參考附件中的腳本ConfigSamples.jacl,最終配置結果在管理控制臺中顯示如下:
4. 本文樣例的一些說明
在本文的樣例中,我們在一個企業(yè)應用(WAS6ESB.ear)中模擬了各種角色,下表總結了這個應用的各個部分的和它們所扮演的具體角色,具體的部署后的結構圖可以參考前面的整體架構圖:
這個應用是基于Ant來構建的,讀者也可以使用WebSphere Server Toolkit或者Rational Application Developer來輔助開發(fā)。打包文件中已經包含了Build好的結果,各種腳本在Scripts目錄下,對具體腳本的功能請參考目錄下的ReadMe.txt。
在部署的過程中,以下幾點是需要注意和說明的地方:
1. WebSphere 6安裝好后缺省是沒有SIBus環(huán)境的,所以首先要建好SIBus,配置好Endpoint Listeners,讀者可以用管理控制臺來實現,也可以用作者提供的腳本來自動配置(WasSetupSIBus.bat),不過讀者需要更改一下腳本中相關的目錄信息。建好SIBus后還需要重啟一下才能使它生效,要不然應用會報找不到引擎的錯誤。
2. 安裝企業(yè)應用WAS6ESB.ear的時候可以使用腳本InstallWAS6ESBApp.jacl來自動進行,出站,入站和消息中介的配置,讀者可以使用腳本ConfigSamples.jacl來自動進行,用戶也可以自己通過控制臺來完成,使用控制臺時需要的參數可以參考腳本中的相關信息。
3. 目標上的上下文配置信息可以被靈活的利用來給消息中介提供幫助,本文樣例中,我們就是通過在服務目標上加上上下文屬性來決定服務的選擇規(guī)則。我們加入了下列屬性值:
這里,我們用屬性的名稱來代表零件標號的前綴,屬性的值代表外部服務所對應的端口目標的名字,DispatcherMediation根據這些上下文屬性來選擇服務。這樣做的好處是當有新的服務提供者加入的時候,我們只要增加一個端口目標并在服務目標上下文屬性中加上規(guī)則就可以了。不過,WebSphere 6中沒有提供腳本配置目標上下文屬性的方法,所以這些屬性需要手工加入。
4. SIBus為我們提供了靈活的運行時配置的支持,我們可以在應用部署以后動態(tài)更改端口目標中的端點地址和綁定空間,在本文樣例中,我們就可以利用這個功能在新建完出站服務后更改各個端口目標的端點地址設置綁定方法而無需修改已有的應用和出站服務的配置,這個配置過程叫做Retarget。
5. 在SIBus中,所有流動的數據必須是有類型的,也就是必須有Schema來明確定義的,所以當我們需要更改消息格式的時候,需要把外部的服務的數據類型在新建出站服務的過程中的WSDL中有明確的定義,具體情況請參考新建出站服務過程中使用的WSDL文件。
6. 在內部客戶端,我們提供了wsejb和sib兩種綁定方式的動態(tài)DII調用的實現,因為這些都是私有的綁定方式,所以需要加一些特有的屬性。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的功能,用戶可以從管理控制臺獲得相關信息。客戶端實現可以參考本文樣例中的testWebService.java。
8. 內部客戶端可以通過訪問:http://localhost:9080/InsideClient/getPartPrice.jsp來測試,輸入查詢零件的編號,如果編號以outside開頭,表明是外部零件,其他的都認為是內部零件。服務將返回編號中的數字,比如: 輸入outside300,將返回零件價格300。輸入inside400,將返回400。
9. 樣例中提供的三個端口目標中,SIB類型的端口目標只是起參考作用,真正使用的時候需要綁定一定的消息中介來做路由,因為sib綁定現階段只支持發(fā)送請求到服務目標。
5. SIBus展望
作為新一代實現ESB的核心組件,SIBus自身有著很多的先天優(yōu)勢,以長遠的眼光來看,它將是架構SOA理想的基礎設施,它的優(yōu)勢體現在:
- 更方便的整合和部署:現在我們只需要WebSphere應用服務器就可以逐步向SOA進行遷移,而以往我們需要很多不同的產品來實現同樣的目標。
- 更靈活消息中介:通過和J2EE編程模型更緊密地融合(消息中介本質上是Stateless Session Bean),我們可以更方便,更靈活地開發(fā)消息中介模塊來實現基于消息內容的動態(tài)路由(Routing),消息的重構(Transformer)和動態(tài)的消息多點分發(fā)(Distributing)等功能,本文的樣例對前兩種ESB中典型的Pattern都提供了實現方法。
- 同應用服務器更緊密的集成:SIBus是純Java實現,緊密集成在WebSphere Runtime中,從而更容易管理和配置,并且因為它基于WPM(WPM是Websphere 6中缺省的消息服務提供者),各種消息服務的高級特性都將自然的被支持。
- 良好的可擴展性和可服務性:因為SIBus在設計的時候充分考慮了企業(yè)范圍內的部署,并充分利用了WebSphere本身的可擴展性和可服務性,所以它呈現在人們面前的是一個充分互連和充分容錯的,對上層用戶透明的總線,關于在企業(yè)級進行部署這方面更詳細的內容,我們將在這個系列中的另一篇文章中專門進行介紹。
- 更多可以選擇的SOA資產:在實現SOA的時候,因為有了SIBus,我們有了更多,更靈活的選擇,很多典型的ESB Pattern將直接提供基于SIBus的Transform,這將大大提高開發(fā)者的效率。
6. 結束語
本文介紹了利用WebSphere 6中的SIBus實現ESB的基本步驟和方法,并提供了一個詳細樣例來幫助讀者更快的進入角色,WebSphere 6中的SIBus還有著很多高級特性這里沒有介紹,我們將在本系列以后的文章中一一介紹。而本系列的下一篇文章將介紹經典的,基于WebSphere 5,MQ系列的組件來實現ESB的方法,這些方法都是經過實際項目驗證過的,有著充分的企業(yè)級應用的背景,所以在現階段WebSphere 6的SIBus環(huán)境還沒有被大規(guī)模應用的情況下,了解這些內容同樣也有著重要的意義,歡迎大家閱讀。