转摘自:https://www.ibm.com/developerworks/cn/webservices/ws-esb2/
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)进行迁移。
为了使本文更具有普遍性,在本文的样例中,作者模拟了一般可能出现的场景并给出了具体的讨论和实现。通过本文,读者最终可以自己利用WebSphere 6的SIBus实现ESB, 而本文所有的源代码和脚本将会提供给读者作为详细的参考。同时,本文中涉及的许多概念和基本操作流程本文所列的参考资料中都有详细介绍,本文就不再详细解释了。
1. 应用样例简介
本样例是一个简化了的零部件价格查询模块,通常,一个制造企业所需要的零配件可能来自各个厂商,也有可能是自己制造的,同时,它所制造的零件也可能被它的内部用户或者外部用户来查询。由于零件的价格变化比较频繁,所以这些价格的查询需要是即时的价格。下面是这个样例的Use case图:
2. 利用WebSphere 6中的SIBus实现ESB
在本样例中,零部件的供应厂商的变化是频繁的,各个厂商的报价系统有可能是多种多样的,而且本模块还需要为企业内,企业外的客户进行服务,如何构造一个灵活的,可扩展的体系结构来适应复杂变化的环境已达到随需应变的需求?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)。 - 对外发布内部的服务
对外部用户,我们把对服务目标的访问通过绑定在某个End Point Listener上来间接的提供,这样一个过程在SIBus中是通过新建一个入站服务的方式来完成(Inbound)。这样做的好处是统一了外部用户的访问点,方便了我们的管理,更好的安全性和更方便的性能调优。当外部用户需要访问某个入站服务的时候,他可以通过入站服务发布的WSDL来获得具体的访问地址和绑定方式。 - 企业内部对内部服务直接的访问
对于企业内部的用户,我们让他们通过API Attach的方式来访问我们抽象出的出站服务。这种API Attach的方式使企业内部客户可以通过标准JSR 109规定的方法来直接访问出站服务所对应的服务目标,SIBus为此提供了专有的绑定,这种方式比通过End Point Listeners的方式有着更高的效率,从而更适合内部用户使用。 - 消息灵活的中介
SIBus提供了消息中介的基础框架和编程模型,用户可以在SIBus上定制各种消息处理机制,SIBus为消息中介提供足够的信息让它来进行各种有效的工作。在本文的样例中,我们使用了消息中介来解决了下面两个问题:- 外部的服务并不一定完全和内部的需求所匹配。在本样例中,我们的外部服务所暴露的接口方法名字和我们内部服务不一样,为了解决这个问题,我们开发了相应的消息中介并将这个消息中介绑定在相应的端口目标上来实现消息格式的转换。
- 服务选择的问题,在本文样例中,我们有多个服务提供者存在,服务目标代表着其身后许多的端口目标及其相关的服务,我们仍然通过开放相应的消息中介来帮助我们实现我们需要的选择逻辑。
- 对ESB高级特性的支持
SIBus还为ESB提供了和J2EE紧密融合的安全机制,事务的传输机制,消息的QoS服务,还有和MQ消息系统的直接互连,虽然这些在本文样例中没有涉及,但在后面的文章中,我们将专门介绍这些高级特性。
3. 在SIBus上实现样例应用
为了实现本文的样例,我们需要定义好零部件查询服务WSDL,然后实现内部服务,开发需要的消息中介,发布这个应用,接着需要对内外部服务进行整合,最后把整合后的服务发布出去。为了本文样例的演示,还需要开发模拟的外部服务,模拟的内部客户和外部客户来使整个流程运转起来,下面我们就详细介绍各个部分的实现方法和关键点:
1.实现内部服务
首先,对内部服务,我们按照JSR 109的方式定义为Web Service。JSR 109规范定义的服务有两种基本方式,在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"?>
……
请注意这里的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的消息中介框架是我们实现消息路由和格式转换的基础,开发者可以利用Rational Application Developer来开发,部署各种消息中介。我们这里将简要的介绍一下实现的关键,具体细节请参考Mediation_JAR模块中的QueryDispatcher类和Adaptor类:
- 消息的路由:
在本文样例中,我们将根据零件的编号的前缀来决定它属于哪个零部件厂商,从而把价格查询请求转发到该零部件厂商的服务所对应的端口目标上。具体的选择规则我们是通过配置目标上下文属性的方式来提供给消息中介的,详细情况可以请参考4.3中的说明。那么选择好了目的地后,怎么路由消息到那个目的地,而不是原来缺省的地址呢?我们来看看SIBus是怎样处理消息的分发的:
在SIBus中,消息的路径是直接放在消息中的,路径分为前进路径和返回路径,每个路径都是一个SIBus上目标地址(Destination)的列表,消息传送引擎所做的工作就是从前进路径列表中取出第一个地址,然后把消息转发到这个地址所对应的目标。所以如果想更改消息的前进路径,我们只需把路径列表取出来,然后更改这个列表就可以了。下面的代码演示了如何把把消息转发到已选出的端口目标portDestination上。
…… List frp = msg.getForwardRoutingPath(); //取得前进路径列表 SIDestinationAddress destination = //根据portDestination生成SIBus的目标地址类 SIDestinationAddressFactory .getInstance() .createSIDestinationAddress( portDestination, false); frp.add(0, destination); //把目标地址插在第一个位置 msg.setForwardRoutingPath(frp); //把前进路径放回消息体 ……
- 消息的格式转换:
在本文样例中,外部零件厂商的服务接口和内部零件厂商的接口并不一样,所以我们需要在对外部服务请求发出去之前进行一定的消息格式转换,把消息转换成外部服务的格式才行,当然,这种转换必须在逻辑上是可行的才可以实现。我们先来看看SIBus中的消息格式:
在SIBus中,各种消息格式将统一在SDO(Service Data Object)接口之上,我们只需通过SDO接口就可以对数据进行各种操作,包括格式转换,而无需使用各种不同形式的API,这无疑将大大减轻了开发者的负担。回到本例,我们需要对消息格式进行转换,具体的说就是需要变换操作的名称,从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上的服务发布到企业外
最后一步,我们需要把企业内部的服务发布出去,让外部的客户能够访问到我们的服务。我们可以通过新建一个入站服务来把SIBus内部的服务目标通过HTTP(S)或者(Secure)JMS Listener为外部用户提供统一的访问入口点。
新建入站服务的时候,我们需要提供一个模板WSDL来定义这个入站服务可以接收的服务请求,在这个模板WSDL中,我们只需定义我们需要向外部提供服务的端口类型就可以了,绑定类型和端点地址都不需要提供,实际上这些都是由Endpoint Listeners来具体决定的,我们一般称这种WSDL为non-bound WSDL,具体内容请参考InsideClient_WAR模块中的PartsInfo_Templet.wsdl。配置入站服务的参数请参考附件中的脚本ConfigSamples.jacl,最终配置结果在管理控制台中显示如下:
4. 本文样例的一些说明
在本文的样例中,我们在一个企业应用(WAS6ESB.ear)中模拟了各种角色,下表总结了这个应用的各个部分的和它们所扮演的具体角色,具体的部署后的结构图可以参考前面的整体架构图:
这个应用是基于Ant来构建的,读者也可以使用WebSphere Server Toolkit或者Rational Application Developer来辅助开发。打包文件中已经包含了Build好的结果,各种脚本在Scripts目录下,对具体脚本的功能请参考目录下的ReadMe.txt。
在部署的过程中,以下几点是需要注意和说明的地方:
1. WebSphere 6安装好后缺省是没有SIBus环境的,所以首先要建好SIBus,配置好Endpoint Listeners,读者可以用管理控制台来实现,也可以用作者提供的脚本来自动配置(WasSetupSIBus.bat),不过读者需要更改一下脚本中相关的目录信息。建好SIBus后还需要重启一下才能使它生效,要不然应用会报找不到引擎的错误。
2. 安装企业应用WAS6ESB.ear的时候可以使用脚本InstallWAS6ESBApp.jacl来自动进行,出站,入站和消息中介的配置,读者可以使用脚本ConfigSamples.jacl来自动进行,用户也可以自己通过控制台来完成,使用控制台时需要的参数可以参考脚本中的相关信息。
3. 目标上的上下文配置信息可以被灵活的利用来给消息中介提供帮助,本文样例中,我们就是通过在服务目标上加上上下文属性来决定服务的选择规则。我们加入了下列属性值:
这里,我们用属性的名称来代表零件标号的前缀,属性的值代表外部服务所对应的端口目标的名字,DispatcherMediation根据这些上下文属性来选择服务。这样做的好处是当有新的服务提供者加入的时候,我们只要增加一个端口目标并在服务目标上下文属性中加上规则就可以了。不过,WebSphere 6中没有提供脚本配置目标上下文属性的方法,所以这些属性需要手工加入。
4. SIBus为我们提供了灵活的运行时配置的支持,我们可以在应用部署以后动态更改端口目标中的端点地址和绑定空间,在本文样例中,我们就可以利用这个功能在新建完出站服务后更改各个端口目标的端点地址设置绑定方法而无需修改已有的应用和出站服务的配置,这个配置过程叫做Retarget。
5. 在SIBus中,所有流动的数据必须是有类型的,也就是必须有Schema来明确定义的,所以当我们需要更改消息格式的时候,需要把外部的服务的数据类型在新建出站服务的过程中的WSDL中有明确的定义,具体情况请参考新建出站服务过程中使用的WSDL文件。
6. 在内部客户端,我们提供了wsejb和sib两种绑定方式的动态DII调用的实现,因为这些都是私有的绑定方式,所以需要加一些特有的属性。DII方式比较灵活,不需要用WSDL2Java工具生成各种静态的辅助类,而静态方式的代码比较简单,用户不需要知道具体绑定和地址的细节,关于wsejb静态绑定的方式读者可以参考WebSphere 6本身的例子,本文样例中就不再赘述了,sib没有静态绑定的支持。
7. 外部客户端应用可以用任何标准的JAX-PRC客户端来模拟,对服务的具体WSDL可以从如下地址获得:http://localhost:9080/sibws/wsdl/ESB_SHOWOFF/PartsInfoInboundService,ESB_SHOWOFF和PartsInfoInboundService分别是Bus的名字和入站服务的名称,SIBus同时还提供发布相应的入站服务的WSDL到zip文件或者UDDI的功能,用户可以从管理控制台获得相关信息。客户端实现可以参考本文样例中的testWebService.java。
8. 内部客户端可以通过访问:http://localhost:9080/InsideClient/getPartPrice.jsp来测试,输入查询零件的编号,如果编号以outside开头,表明是外部零件,其他的都认为是内部零件。服务将返回编号中的数字,比如: 输入outside300,将返回零件价格300。输入inside400,将返回400。
9. 样例中提供的三个端口目标中,SIB类型的端口目标只是起参考作用,真正使用的时候需要绑定一定的消息中介来做路由,因为sib绑定现阶段只支持发送请求到服务目标。
5. SIBus展望
作为新一代实现ESB的核心组件,SIBus自身有着很多的先天优势,以长远的眼光来看,它将是架构SOA理想的基础设施,它的优势体现在:
- 更方便的整合和部署:现在我们只需要WebSphere应用服务器就可以逐步向SOA进行迁移,而以往我们需要很多不同的产品来实现同样的目标。
- 更灵活消息中介:通过和J2EE编程模型更紧密地融合(消息中介本质上是Stateless Session Bean),我们可以更方便,更灵活地开发消息中介模块来实现基于消息内容的动态路由(Routing),消息的重构(Transformer)和动态的消息多点分发(Distributing)等功能,本文的样例对前两种ESB中典型的Pattern都提供了实现方法。
- 同应用服务器更紧密的集成:SIBus是纯Java实现,紧密集成在WebSphere Runtime中,从而更容易管理和配置,并且因为它基于WPM(WPM是Websphere 6中缺省的消息服务提供者),各种消息服务的高级特性都将自然的被支持。
- 良好的可扩展性和可服务性:因为SIBus在设计的时候充分考虑了企业范围内的部署,并充分利用了WebSphere本身的可扩展性和可服务性,所以它呈现在人们面前的是一个充分互连和充分容错的,对上层用户透明的总线,关于在企业级进行部署这方面更详细的内容,我们将在这个系列中的另一篇文章中专门进行介绍。
- 更多可以选择的SOA资产:在实现SOA的时候,因为有了SIBus,我们有了更多,更灵活的选择,很多典型的ESB Pattern将直接提供基于SIBus的Transform,这将大大提高开发者的效率。
6. 结束语
本文介绍了利用WebSphere 6中的SIBus实现ESB的基本步骤和方法,并提供了一个详细样例来帮助读者更快的进入角色,WebSphere 6中的SIBus还有着很多高级特性这里没有介绍,我们将在本系列以后的文章中一一介绍。而本系列的下一篇文章将介绍经典的,基于WebSphere 5,MQ系列的组件来实现ESB的方法,这些方法都是经过实际项目验证过的,有着充分的企业级应用的背景,所以在现阶段WebSphere 6的SIBus环境还没有被大规模应用的情况下,了解这些内容同样也有着重要的意义,欢迎大家阅读。