垂直市场解决方案(VMS)是NAVTEQ公司中的一个机构,负责为客户提供定制的解决方案,包括移动门户和导航系统。这些解决方案中包含了NAVTEQ 公司提供的服务以及第三方服务,以客户要求的方式交付组合服务和内容,这些方式包括Web services,WAP,portals等。
VMS在响应庞大的销售机会和预见未来客户需求方面面临着一些挑战。为了解决这些挑战就需要开发复合服务(composable services),这些服务可以快速地、方便地集成到整个客户解决方案中。用于服务集成的中间件基本上能解决这些问题。
在本文中,我将讨论如何使用JBoss中间件平台来构建这样的系统,尤其是使用JBoss ESB和jBPM(JBoss Business Process Management)。
在深入了解JBoss中间件产品的细节之前,我将快速地描述构建VMS解决方案的基本方式。
目前, NAVTEQ公司提供了这些解决方案所需的所有功能,包括功能强大的基于位置的服务,比如地理编码 (Geocoding)和路由,交通相关服务,比如交通事故信息,交通堵塞因子计算和所指定线路的交通报警,以及一般的电子商务功能,包括采购,授权和权限管理。
给客户直接暴露这些服务存在多种问题:
最后,我们采用如图1所示的总体架构来实现。
图 1 总体解决方案架构图
架构图的中间部分是VMS平台,它通过编排现有的后端功能实现了特定客户所需的服务。这个平台主要由三层组成:
下面我将介绍如何使用JBoss中间件平台实现这个架构。
JBoss ESB是一个开源的ESB,它基于RosettaNet ESB,支持服务的创建、部署和整合。
从架构上而言,可以将Jboss ESB中的一切都看作是服务。这些服务并非Web Services,而是ESB服务,这些ESB服务可以通过多种传输暴露出来。所有的ESB服务都有一个方法(doWork),可以通过下面的接口(由所有的服务共享)描述:
Message output = service.doWork(Message input)
在JBoss ESB中,ESB消息和SOAP消息类似,都由几个部分组成,包括标头(header),消息体(body),错误(fault),附件(attachments),等等。每个部分包括一个可序列化的java对象集合(map),通过集合中定义的name进行访问。这就意味着JBoss ESB消息并不是强类型的,在访问消息时需要注意(类型转换)。
服务定义的形式:
传统ESB是使用一系列拦截器(interceptor)构建服务,允许在服务调用管道(pipeline)中注入额外处理。不同的是,JBoss ESB将服务作为一个显式的管道(pipeline)来构建,管道包含一系列的动作(action),如图2所示,每个action实现了(潜在部分的)服务业务功能或者基础功能,比如在消息存储中存储服务消息。
图2 JBoss ESB服务
与基于拦截器的传统ESB不同的是,JBoss ESB可以很好地隔离“业务”(服务实现)和相关基础设施(拦截器)。JBoss ESB在可执行的管道中将二者连接起来。然而这并非毫无价值,在一个服务管道中包含所有action都可以简化服务处理的理解。
JBoss ESB中的服务action是一个java类,它必须实现接口类org.jboss.soa.esb.actions.ActionLifecycle
。服务的实现往往将服务功能分解成一系列的action,然后分别实现每个action。这类服务实现组织为JBoss ESB开发者提供了两个级别的复用——服务本身和可以在多个服务中复用的action。JBoss同样也提供了一个可复用action库。该库中包括如下类型的action1:
服务请求可通过多种传输传递。服务定义包括一系列传输/端点的引用。服务在端点(每个端点都有一个唯一指定的服务)上监听消息。服务定义中也可以通过指定(在传输上)处理服务请求的线程数来配置服务的吞吐量。
服务action调用的设置和顺序以及服务监听的端点都是在jboss-esb.xml文件中配置。可以给指定的服务配置一系列action(它们由JBoss或者特定的公司或者服务所提供)。这种支持action的外部XML配置,极大地提高了actions的复用。
正如上文中提到, JBoss ESB中的一切都服务,异步2调用是默认的调用模式。JBoss ESB同样也支持同步调用,同步调用可以使用相关性实现。ESB服务的同步和异步调用可以通过下面两种方式进行控制:
为了简化服务调用,Jboss ESB提供了org.jboss.soa.esb.client.ServiceInvoker类,该类为服务调用提供了一个非常简单的接口,调用时只需要提供一个目录/服务名称作为参数。
除了远程传输外,JBoss还提供了高效的本地(在同一个JVM中)调用机制,它是通过一个内存队列来实现的。当在服务调用中使用 org.jboss.soa.esb.client.ServiceInvoker时,如果该服务采用本地调用且采用本地部署,那么内存传输总会被选择用来优化性能。
如今,当人们谈论ESB时,经常会强调Web Services的处理。技术上而言,JBoss ESB中包含很多基于Web Services的组件,这些组件可以暴露和调用Web Services端点(比如,总线上SOAP的开启和关闭):
SOAPProcessor通过JBossESB托管的监听器支持调用JBossWS托管的Web Service端点。这意味着可以通过ESB为其它的ESB服务暴露Web service端点。它是基于一个轻量级的服务包装器(Service Wrapper)Web service(比如,JSR 181规范4的实现)而实现的,通过它可以调用目标服务(target Service)。这也意味着这些服务可以通过ESB所支持的传输通道(http, ftp, jms等)进行调用。在Web Service端点(使用JAXWS注解的java类)上配置的SOAP处理器(processor),实际上由Web Service端点处理SOAP请求。
SOAPClient使用WISE5客户端服务生成JAXWS客户端代码,并调用目标服务。通常是使用Web Service的WSDL的URL动态生成JAXWS客户端,以调用web service。
在开发的过程中,我们发现采用这种方式使用web service是很笨重的,所以需要采用如下策略:
org.jboss.soa.esb.client.ServiceInvoker
,并调用所需的服务。这在架构上和使用SOAPProcessor是一样的,但更通用一些,这允许服务功能可以暴露成SOAP、REST或者其它所需接口。通过XML文件可以配置服务监听器和服务执行管道。该XML配置文件由3个文件组成,这些是配置ESB服务6不可缺少的文件:
虽然jbm-queue-service.xml和deployment.xml文件可以直接通过XML进行编辑,但JBoss提供了工具简化ESB项目8的创建、部署和维护。图3 显示了jboss-esb.xml的编辑器。
图3 JBoss ESB工具
该工具以图形化的方式添加和配置监听器、服务和动作(包括动作的参数)。
最重要的服务动作是数据转换。JBoss ESB使用Smooks9实现数据转换。Smooks的基本原理是使用多种类型的数据源,并从数据源中生成事件流。然后对事件流采用访问者模式(Visitor pattern)生成不同类型的数据。10它支持多种不同的数据源和数据类型,这意味着支持多种转换类型,包括(但不仅限这些):
为了简化Smooks的使用,JBoss ESB提供了特定的action11,可以通过配置直接调用Smooks——并将转换作为服务管道的一部分。
为什么是Smooks? 技术上而言,可以直接通过java代码实现数据转换,那么为什么要使用Smooks呢?(记住:Smooks是基于XML配置文件的,编写XML文件往往比直接通过Java代码实现还要慢12……) 数据转换中使用Smooks的好处:
|
JBoss ESB工具为可视化定义Smooks转换提供了图形化编辑器。
图 4 Smooks图形编辑器
数据转换创建完后,可以通过Smooks的执行报告(图 5)以视图的形式展现该过程中的所有执行步骤。该视图极大程度上简化了Smooks转换的debug过程。
图 5 Smooks执行报告
常见的Smooks转换设计错误 设计Smooks转换时,最重要的是要记住转换不是上下文敏感的。也就是说,比如,有如下一个XML文档: <a> <b> <c>12345</c> </b> <d> <c>12345</c> </d> </a> 当使用“c”作为选择器时,它会被调用两次。第一次是“b”元素里面的“c”,第二次是“d”元素中的“c”,即使是使用选择器"b"也会调用两次。 为了避免出现这些问题,需要使用源文档中具有唯一名称的选择器。 |
JBoss jBPM是一个灵活的,可扩展的流程语言框架13。jPDL是建立在该公共框架上的一种流程语言,它将业务流程图形化的表示成任务(tasks),异步通信的等待状态(wait states),计时器(timers),自动化动作(automated actions)和其它组件。
jPDL最大程度地减少了对其它lib库的依赖,可以像使用java库一样简单地使用。另外,也可以用在对吞吐量非常重要的J2EE集群应用服务器的环境中。它还支持多种数据库,可部署在任何应用服务器上。
与BPEL不同的是,BPEL与Web services是紧耦合的(在BPEL中,每个活动都必须作为Web service14而实现),而JBPM更像是一个组件框架15,允许直接调用Java处理器(类似于ESB服务调用管道)。
如果已经存在一个服务执行管道,那为什么还需要额外的服务编排机制呢? 看起来,服务管道和服务编排之间存在很多重叠。本质上,服务编排是一个有序的action编排。但它不支持最常见的编排功能,比如,决定(decisions),条件转换(conditional transitions)和并行执行(parallel execution)。虽然在技术上可以实现这些功能,并作为管道定义中的一部分,但实现起来并不容易。我们更倾向于将基本的业务功能和额外的基础设施(包括,数据转换,执行监控等)结合在一起时使用服务管道,而编排服务时使用jBPM。 部署所需要考虑的问题。如果在很多用例中都用到了同一个动作,那么可以将该动作分离出来,作为一个单独的服务,因此部署时只用部署一次,并可以用于jBPM的服务编排中。 |
JPDL主要由节点和执行上下文组成。JPDL定义了如下类型的节点:
执行上下文(execution context)和HTTP的session有些类似,它包含一系列的命名对象——它与先前介绍的ESB消息体有些类似。任何节点都可以访问执行上下文,可以读、写上下文的变量。执行状态被持久化到数据库中——当JPDL流程处于等待状态时,它的上下文(上下文变量)被存储到数据库中。当流程再次被激活时,从数据库中读取上下文变量的值,重新创建上下文。
节点中装载JPDL流程(在服务编排中)。节点只是执行一个动作处理器——一个实现了org.jbpm.graph.def.ActionHandler 接口的类。节点动作处理器和服务动作处理器相似,都是可配置的,只是使用不同的配置方法。任何定义在动作处理器中的public/private变量都可以通过流程定义进行配置。
异步执行 通过配置节点的节点类型来指定异步执行。注意,JPDL中的异步执行并不是使用线程实现的,而采用的是队列机制。如果节点执行采用异步调用,那么流程的当前状态会被持久化到数据库中。通过org.jbpm.job.executor.JobExecutor类可以继续执行流程。运行这个类的标准配置方法是在单独的WAR中配置org.jbpm.job.executor.JobExecutorServlet servlet。在本文中,我们不使用这种方法。而是通过从JPDL中调用ESB服务来实现异步执行(详细内容如下)。 |
Decision节点是另一种节点,它支持自定义实现。只需要实现接口org.jbpm.graph.node.DecisionHandler即可。当实现Node功能时,可以像配置Action处理器一样配置Decision处理器。
在JPDL中使用Fork/Join节点可以实现并行执行。与Fork和Join节点相连接的路径可以并行实现。
Fork/Join执行 JPDL中的Fork并不是基于线程实现的。也就是说当transitions离开Fork节点时,Fork的实现并不为它们创建线程。只为每个transition创建令牌。因为流程执行是单线程的,所有的令牌将顺序执行。实现Fork/Join并行执行的一种方法是通过从JPDL中调用ESB服务(详细内容如下)。 |
循环在服务编排中使用得非常广泛,但JPDL却不支持它。顺序的循环可以通过使用Decision节点和计算循环变量实现,更为复杂(在服务编排中非常普遍)的场景是并行循环——运行时计算transition路径数量的Fork/Join。在JPDL中并不支持这种模式,但是定义两个自定义的处理器就可以很简单地实现这个功能(见附录中的列表1,列表2)。
这些处理器的实现都是基于JPDL Fork/Join而实现的。Start处理器创建多个令牌,并启动它们。End处理器等待所有的子令牌完成,然后transition到下个节点。
流程执行时会产生异常。JPDL中可以为每个流程节点定义异常处理器。异常处理器也是一种action处理器,当节点执行时出现异常,它将会被调用。附录中列举了一个简单的异常处理器的例子。该异常处理器输出异常信息,然后在目的变量中将异常信息过渡到特定的节点。
JPDL的流程定义并不是作为JBoss应用部署的,而是保存在流程数据库中。
JBoss ESB和JBPM都是非常强大的软件平台,但是如果将ESB/JBPM整合起来,那么功能更强大16。
两种ESB/JBPM整合类型:
JBoss/JBPM整合提供了一个特殊的action处理器——BpmProcessor,它通过调用jBPM的命令API与jBPM交互。它以一个流程定义名称作为参数,就可以创建和启动一个流程实例。此时,流程在单线程中异步执行,也就是说当流程运行时,服务会返回一个应答给服务调用者。
这种整合也实现了两个jBPM action处理器类——EsbActionHandler和EsbNotifier。EsbActionHandler是一种request- reply类型的action,它将消息发送到服务总线上,然后等待响应。其架构如图6所示。EsbActionHandler将请求消息发送给用户服务,并将流程变为等待状态。当用户服务执行完后,它会调用一个特殊的JBPM服务,该服务通知等待流程继续执行。
图 6 JBPM/ESB整合架构
每种集成类型都为流程中的多个提供了异步执行(比如,fork/join或者并行循环的执行见上文)。
而EsbNotifier只需要将消息发送给服务,可以继续自己的流程。它与JBossESB的交互本质上是异步的,当服务执行时,不会阻塞流程实例的运行。
如上所述,BpmProcessor异步的调用业务流程,这在服务编排时,并不总是可取的;流程执行生成的内容会用于服务响应。另外,ESB/JBPM集成还支持如下场景17。
BpmProcessor支持一个额外的配置参数——reply-to-originator。当这个参数为true时,BPMProcessor 会将服务调用的ReplyTo信息保存在新创建的流程实例的JBPM执行上下文中。 EsbNotifier将reply-to-originator作为notifier的目的地,使用ReplyTo传递流程执行的结果。为了达到这种效果,服务的调用流程必须定义为MEP的Oneway方式。
当在业务流程中调用服务时,异常并不仅仅出现在节点执行(服务调用)中,而且会出现在被调用服务的执行中。这就需要一种特殊的异常处理器。该处理器作为 BpmProcessor的一部分而定义,它可以控制服务调用的transition——在成功或者异常时的不同transition。
JPDL是一种基于XML的语言,可以通过XML表示。为了简化业务流程的创建和分析,JBoss提供了一个Eclipse插件,通过它可以可视化的创建和维护JPDL流程(如图7所示)。
该编辑器即支持图形化的业务流程视图,也支持XML格式的业务流程视图。也可以直接在Eclipse中将流程部署到JBoss服务器。
图 7 JPDL编辑器
JBoss ESB/jBPM整合为基于已有企业资源创建面向服务的解决方案提供了一个非常强大的、可扩展的和灵活的平台。它为这些解决方案的实现提供了所有主要组件,包括:
本文所介绍的JBoss中间件是基于NAVTEQ构建的个别VMS解决方案的原型。该原型基于特定领域模型,用于管理用户、位置和路由,还包括一些ESB 服务,这些服务都是对现有Web service的包装,并且服务直接在SOA平台中实现。它也包括数个使用jBPM实现的组合(还有分层次的组合18) 服务。
我们正在提高JBoss中间件的性能和稳定性——基于解决方案和使用监控和管理SOA解决方案的JBoss Operations Network19(JON)。
非常感谢我的NAVTEQ同事们,尤其是Robert Camp,Ian Mondragon和Jeffrey Herr,还有JBoss解决方案架构师们,尤其是Ray Ploski和Aaron Pestel,他们实现了本文中提到的原型并描述了他们的结果。
package com.navteq.jbpm.parallel; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.jbpm.graph.def.ActionHandler; import org.jbpm.graph.def.Node; import org.jbpm.graph.def.Transition; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.graph.exe.Token; /**
* specifies configurable parallel execution behaviour.
*
*/
public class ParallelStart implements ActionHandler { private static final long serialVersionUID = 1L; // The name of the variable holding loop count private String loopCount; // The name of the variable current count (stored in the token variable scope) private String currentCount; // The list of the array variables. Each array has to be of the of the loop count
// size. An appropriate element is stored in the token context scope private Map
Listing 1 Start Parallel execution handler
package com.navteq.jbpm.parallel; import java.lang.reflect.Array; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.hibernate.LockMode; import org.hibernate.Session; import org.jbpm.JbpmContext; import org.jbpm.graph.def.ActionHandler; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.graph.exe.Token; public class ParallelEnd implements ActionHandler { private static final long serialVersionUID = 1L; /** specifies wether what type of hibernate lock should be acquired. null value defaults to LockMode.force */ String parentLockMode; // The name of the variable holding loop count private String loopCount; // The name of the variable current count (stored in the token variable scope) private String currentCount; // The list of the array variables. Each array has to be of the of the loop count // size. An appropriate element is stored in the token context scope private Map
Listing 2 End Parallel execution handler
package com.navteq.jbpm.actionHandlers; import org.jbpm.graph.def.ActionHandler; import org.jbpm.graph.def.Node; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.graph.exe.Token; public class ExceptionActionHandler implements ActionHandler { private static final long serialVersionUID = 1L; private String destination; @Override public void execute(ExecutionContext context) throws Exception { Token token = context.getToken(); Node sourceNode = token.getNode(); Throwable throwable = context.getException(); System.out.println("Caught Exception " + throwable.getMessage() + " in node " + sourceNode.getName()); Node targetNode = context.getProcessDefinition().getNode(destination); token.setNode(targetNode); token.signal(); }
列表 3 异常处理器
查看英文原文:Using JBoss ESB and JBPM for Implementing VMS Solutions。
译者简介:
陈义,计算机应用技术专业硕士研究生,一直专注于SOA、BPM、ESB、EAI和MOM的研究及应用。热衷于开源SOA项目,有志致力于 Mule、ServiceMix、ODE、ActiveBPEL、ActiveMQ、OpenJMS、Camel、CXF、XFire以及Tuscany 在中文社区的研究和推广工作。您可以通过honnom (at) 163.com联系到他。