《基于Jazz技术构建企业级Web2.0应用》系列第一部分:数据模型设计与持久化
《基于Jazz技术构建企业级Web2.0应用》系列第二部分:服务层设计与实现
【IT168 专稿】
本系列的第一、二部分分别介绍了基于Jazz的数据存储模型和业务服务的实现。本文作为本系列的第三部分,将结合第一部分中介绍的“PetStore用户购买宠物”用例的实现,介绍如何利用Jazz提供的Process构件来完成业务流程的管理。Process作为一个Jazz核心构件,用于定制Project Area、Team Area相关的准则和流程,支持软件项目团队制定符合自身需要的、包含了非常好的实践的流程和规范。Process的一个作用是为其它Jazz构件提供支持,从而实现其中某些操作的AOP,本文将对此进行详细介绍。
Jazz AOP介绍
Jazz Process机制提供了operation作为切入点的表示(注意:除此之外,operation还是Jazz进行权限控制的手段,此处不做详细介绍)。任何一个Jazz构件都可以通过operation来封装一定的业务逻辑,从而允许Process在这段业务逻辑的执行过程中施加某些影响。影响的方式包括两种:前置通知和后续行为。在一个operation执行之前执行的前置通知称为advisor,在一个operation执行之后执行的后续行为称为participant。一个operation可以对应多个advisor和participant。当一个operation要被执行时,Process机制将首先执行它对应的所有advisor,如通过则执行operation,如operation执行正确则执行其所有的participant。需要注意的是,Jazz operation分为客户端operation和服务器端operation两种,分别运行在Jazz服务器和客户端(Rational Team Concert)上。服务器端operation及其advisor、participant的执行是在一个事务中进行的,任何错误都将导致整个过程回滚(即便是最后一个participant抛出异常);而客户端operation及其advisor、participant的执行不是在一个事务中,因而如果某个participant执行出错,将只会停止后续participant的执行,而不会导致前面操作结果的取消。
PetStore AOP设计
从AOP的角度观察,PetStore中“用户购买宠物”这一行为应当作为一个切入点被抽取出来,以支持围绕该行为前后的事件,如在用户购买成功之后向用户发送一封email,告知已付费金额。
在本文中,我们在PetStore中增加一个服务,以实现“用户购买宠物”这一功能,并通过引入Jazz Process控制机制,实现对“用户购买宠物”这一行为的AOP,包括:
1.增加一个RPC服务ICustomerService,其中的buyProduct方法负责向用户提供购买宠物的业务接口。
2.定义和实现一个服务器端的operation "buy",用于封装实现“用户购买宠物”的业务逻辑,它将被ICustomerService的实现类调用以实现buyProduct方法。
3.定义和实现一个针对operation"buy"的participant "buyParticipant",负责在用户购买宠物成功后向其发送一封email,告知已付费金额。
用户购买宠物的执行流程如图1所示。

图1. 购买宠物操作的时序图
注意:上图中的IServerProcess是由Jazz Process提供,负责服务器端operation的执行,下面将在实现过程中对其进行进一步介绍。
PetStore AOP实现
一个Jazz构件若需要Jazz Process的支持(或者说“向Process开放”),需要完成以下几个方面:
· 通过configurationPoints扩展点定义operation。operation是Jazz Process进行AOP的切入点。
· operation的实现。通常将业务逻辑(如对其它服务的调用)封装在一个com.ibm.team.process.common.advice.AdvisableOperation的run方法中,并通过Process构件提供的com.ibm.team.process.service.IServerProcess中的adviceAndExecute方法调用。注意:operation的定义与实现的绑定是在AdvisableOperation构造方法中,将参数operationId赋值为operation定义中的id。
· 通过operationAdvisors和operationParticipants扩展点定义针对operation的前置通知(advisor)和后置行为(participant)。
· advisor和participant的实现(分别实现com.ibm.team.process.common.advice.runtime. IOperationAdvisor和com.ibm.team.process.common.advice.runtime. IOperationParticipant接口)。
· 通过process定义文件将operation和它的advisors、participants关联起来(分别通过operation的preconditions和follow-up actions字段)。
下面将详细介绍如何在PetStore中利用Jazz Process机制实现上面设计的AOP功能。
第一步,添加对Process bundle的依赖
在插件项目com.ibm.petstore.service中添加对两个process bundle com.ibm.process.common和com.ibm.process.service的依赖。
第二步,定义和实现operation "buy"及其participant "buyParticipant"
在com.ibm.petstore.service的plugin.xml中通过configurationPoints扩展点定义operation "buy":
清单1. operation定义
<extension
point="com.ibm.team.process.service.configurationPoints">
<operation
id="com.ibm.petstore.service.buy"
name="Buy Pet">
</operation>
</extension>
注意:这里较为重要的是id的定义。在下面的步骤中将通过对此id引用这里定义的operation。
用一个继承自com.ibm.team.process.common.advice.AdvisableOperation的类BuyPetOperation实现operation "buy",在其run方法中封装用户付费的业务逻辑:
清单2. BuyPetOperation
public class BuyPetOperation extends AdvisableOperation {
private int accNo;
private String pw;
private int price;
private String email;
public BuyPetOperation(ITeamArea teamArea,
int accNo, String pw, int price, String email) {
super("com.ibm.petstore.service.buy", null, teamArea);
this.accNo=accNo;
this.pw=pw;
this.price=price;
this.email=email;
}
@Override
public IOperationReport run(IBehaviorConfiguration configuration,
IProgressMonitor monitor) throws TeamRepositoryException {
PayUtil payUtil=new PayUtil();
try {
payUtil.login(accNo, pw);
payUtil.pay(price);
payUtil.logout();
} catch (PayUtilException e) {
throw new TeamRepositoryException(e);
}
return null;
}
public String[] getActions() throws TeamRepositoryException {
return new String[] { "buy" };
}
public String getEmail() {
return email;
}
public int getPrice() {
return price;
}
}
注意:在BuyPetOperation的构造方法中通过引用plugin.xml中operation定义的id值将实现类与operation的定义绑定,该id值将作为参数被传给AdvisableOperation的构造方法以完成operation实例的构造。
在com.ibm.petstore.service的plugin.xml中通过operationParticipants扩展点定义针对operation "buy"的participant "buyParticipant":
清单3. participant定义
<extension
point="com.ibm.team.process.service.operationParticipants">
<operationParticipant
class="com.ibm.petstore.service.process.BuyPetParticipant"
id="com.ibm.petstore.service.buyParticipant"
name="Buy Pet Participant"
operationId="com.ibm.petstore.service.buy">
</operationParticipant>
</extension>
这里发生了两件事:1)定义了此participant的id;2)通过operationId标识此participant所针对的operation。
用一个实现IOperationParticipant接口的类BuyPetParticipant实现"buyParticipant",在其run方法中实现向用户发送email的功能:
清单4. BuyPetParticipant
public class BuyPetParticipant implements IOperationParticipant {
public void run(AdvisableOperation operation,
IProcessConfigurationElement participantConfig,
IParticipantInfoCollector collector, IProgressMonitor monitor)
throws TeamRepositoryException {
String email=((BuyPetOperation)operation).getEmail();
int price=((BuyPetOperation)operation).getPrice();
this.sendEmail(email,
"From PetStore: Amount "+price+" is paid by your account.");
}
public void sendEmail(String email, String content){
……
System.out.println("log: mail sent to \""+email+
"\" with content \""+content+"\".");
}
}
注意:同operation类似,一个participant也在其run方法中封装核心业务逻辑。在Jazz Process执行一个participant时,将把其对应的operation对象作为参数传递给run方法,本例中通过operation对象获取用户的email和购买宠物的价格信息。
在PetStore的process定义文件中将operation "buy"和participant "buyParticipant"关联起来:
清单5. operation和participant的关联
<process-specification xmlns="http://com.ibm.team.process">
<project-configuration>
……
</project-configuration>
<team-configuration>
<permissions>
……
</permissions>
<behavior>
<role id="default">
<operation id="com.ibm.petstore.service.buy">
<preconditions>
</preconditions>
<followup-actions>
<followup-action id="com.ibm.petstore.service.buyParticipant"
name="Buy Pet Participant"
description="Buy Pet Participant contains sending a email to notify user pay amount.">
</followup-action>
</followup-actions>
</operation>
</role>
</behavior>
</team-configuration>
</process-specification>
注意:在process定义中通过id引用一个已定义的operation,并在follow-up actions字段中通过id引用它对应的所有participant。类似的,如果它有对应advisor,则需要在preconditions字段中指明。
第三步,定义和实现ICustomerService
在公共bundle com.ibm.petstore.common中定义服务接口com.ibm.petstore.common.service. ICustomerService:
清单6. ICustomerService定义
public interface ICustomerService {
public void buyProduct(IProduct product,int accountNo,String password,String email) throws TeamRepositoryException;
}
在com.ibm.petstore.common的plugin.xml中添加ICustomerService的声明:
清单7. ICustomerService声明
<service
kind="RPC"
name="ICustomerService"
uri="com.ibm.petstore.common.service.ICustomerService"
version="1">
</service>
在服务bundle com.ibm.petstore.service中添加类com.ibm.petstore.service.CustomerService实现ICustomerService接口:
清单8. CustomerService
public class CustomerService extends AbstractService implements ICustomerService {
public void buyProduct(IProduct product, int accountNo, String password,
String email) throws TeamRepositoryException {
IProcessServerService processServerService =
getService(IProcessServerService.class);
ITeamArea teamArea = getTeamArea("PetStoreUserTeam");
if(teamArea==null)
throw new TeamRepositoryException(
"petstore user team doesn't exist");
IServerProcess serverProcess = processServerService
.getServerProcess(teamArea);
BuyPetOperation buyOperation = new BuyPetOperation(teamArea, accountNo,
password, product.getPrice(), email);
serverProcess.adviseAndExecute(buyOperation);
}
……
}
注意:在buyProduct方法中通过IServerProcess的adviceAndExecute方法调用了operation"buy"。
在com.ibm.petstore.service的plugin.xml中将ICustomerService及其实现类绑定:
清单9. ICustomerService与实现类的绑定
<serviceProvider
componentId="com.ibm.petstore"
implementationClass="com.ibm.petstore.service.CustomerService">
<provides>
<providedService
interface="com.ibm.petstore.common.service.ICustomerService">
</providedService>
</provides>
<prerequisites>
<requiredService
interface="com.ibm.team.repository.common.service.IQueryService">
</requiredService>
<requiredService
interface="com.ibm.team.repository.service.IRepositoryItemService">
</requiredService>
<requiredService
interface="com.ibm.team.process.service.IProcessServerService">
</requiredService>
</prerequisites>
</serviceProvider>
注意:这里对于IProcessServerService的依赖。
第四步,为PetStore定义、部署process,并创建Project Area、Team Area
启动Jazz服务器,在Rational Team Concert中创建一个到到本地存储库的连接。
在连接中创建一个Process模板"petstore process definition":

图2. 创建Process模板
在打开的"petstore process definition"中选择"Process Configuration Source",配置如下:
清单10. PetStore Process定义
<?xml version="1.0" encoding="UTF-8"?>
<process-specification xmlns="http://com.ibm.team.process">
<project-configuration>
<initialization>
</initialization>
<permissions>
<role id="default">
<project-operation id="com.ibm.team.process.server.saveProjectArea">
<action id="createTeamArea"></action>
</project-operation>
</role>
</permissions>
</project-configuration>
<team-configuration>
<permissions>
<role id="default">
<operation id="com.ibm.team.process.server.saveTeamArea">
<action id="teamArea">
<action id="any"/>
</action>
</operation>
<operation id="com.ibm.petstore.service.buy">
<action id="buy"/>
</operation>
</role>
</permissions>
<behavior>
<role id="default">
<operation id="com.ibm.petstore.service.buy">
<preconditions>
</preconditions>
<followup-actions>
<followup-action id="com.ibm.petstore.service.buyParticipant"
name="Buy Pet Participant"
description="Buy Pet Participant contains sending a email to notify user pay amount.">
</followup-action>
</followup-actions>
</operation>
</role>
</behavior>
</team-configuration>
</process-specification>
在连接中创建一个Project Area"PetStorePrj",并选择使用process模板"petstore process definition"。

图3. 选择Process模板
在"PetStorePrj"中创建一个Team Area"PetStoreUserTeam"。
注意:由于process定义中已经将执行operation"buy"的权限赋予所有用户(role id="default"),这里无需对PetStoreUserTeam进行额外配置。
执行结果
用户选择购买某个宠物后,系统将向其发送一封email,图4为log信息。

图4. 执行结果
总结
Process是Jazz的一个核心构件,它的一个重要作用是为其它构件提供业务流程管理支持。本文详细阐述了Jazz Process中 AOP的一些基本概念和使用方法,并结合PetStore应用介绍了如何在基于Jazz的开发中利用Process机制实现基于AOP的业务流程管理。
参考资源
关于Jazz Project Area、Team Area及Process等基本概念,请参考Getting Started with Jazz Project Areas and Process。
关于Jazz Process的更多内容请参考Process开发指南。