SOA 最佳实践:BPEL 指南

第 7 部分:即时构建 BPEL 流程
作者:Jerry Thomas

通过 XQuery 将数据库中存储的参数转换为 BPEL XML 定义文件来即时生成 BPEL 流程。

 查看完整的“BPEL 指南”索引

 

本文相关下载:
 示例代码
 Oracle BPEL 流程管理器和 Designer
 Saxon XSLT 和 XQuery 分析器

 

2005 年 11 月发表

在本指南的第 3 部分中,您学习了如何使用端点引用操作合作伙伴链接,以实现动态的 BPEL 流程。该方法对于构建不受合作伙伴关系更改影响的流程确实很有用。但在许多流程中,除合作伙伴链接以外的各种其他变量也可能需要动态更改。

 

假设某个公司设计了一个用于管理企业房地产和设备的工作场所管理解决方案,帮助客户回答以下类型的问题:

  • 不同业务部门的空间使用情况如何?业务单元如何最有效地共享空间?
  • 房地产租赁何时到期?通过了解公司的发展规划,怎样重新商议租赁事项?
  • 如何进行最佳的规划和决策,将业务部门搬迁到其他楼层或扩大到街道对面的建筑中?

工作场所管理解决方案实现了业务流程的自动化,如跟踪办公室隔间是否被占用,如果该空间在三个月内保持闲置状态,则取消它与业务部门的关联。由于公司针对不同的客户部署此流程,因此必须根据客户的业务需要调整该流程 - 某个公司可能倾向于在将隔间与业务部门分离之前向物业经理发送电子邮件,而另一家公司可能倾向于等待更长一段时间,然后再采取行动。

 

这种情况下,为每个客户从头创建一个业务流程将需要花费大量的时间、金钱和资源。而且创建可重用的业务流程将需要深入了解 BPEL 结构。

 

相比之下,自适应业务流程可以根据特定企业不断变化的需要进行快速的定制,更快实现自动化、降低开发成本并缩短投放市场的时间。在该方法中,您将隐藏 BPEL 的复杂性,并使分析人员能够建立尽可能贴近“实际生活”的业务流程模型。

 

但该流程需要自定义业务流程设计器,利用它分析人员可以不用考虑 BPEL 的复杂性,从而专注于业务流表示。该自定义设计器将流程定义存储到数据库中。每当需要更新该流程时,业务分析人员便可以使用自定义设计器相应地更新该流程。

 

实际上,一旦将流程定义加载到数据库中以后,便可以在数据库中“即时”构建 BPEL 流程。随后,可以动态部署这个新构建的业务流程。

 

Oracle BPEL 流程管理器是该方法的理想工具;它能与第三方设计器很好地融为一体,并能够动态部署 BPEL 流程。例如,我所在的公司 CenterStone Software 便利用该方法提供一个解决方案,以快速实现工作场所业务流程的自动化并管理企业房地产和设备。我们的 eCenterOne 应用程序使用 Oracle BPEL,使物业经理能够快速开发和部署自定义业务流程。

 

在本文中,我将介绍自定义 BPEL 设计器如何将流程定义存储到数据库中。了解用于存储定义的数据库模式后,您将使用 XQuery 和 Saxon XQuery 分析器从示例业务流程中实时创建一个 BPEL 文件。最后,您将学习如何使用 Ant 脚本动态部署该流程。

 

方法

 

正如我在前面介绍的,即时构建流程有许多优势。图 1 显示了实时创建的业务流程的整个生命周期。

 

图 1

图 1 业务流程生命周期

 



构建实时流程分为几个步骤:分析人员使用自定义设计器以图形方式建立业务流程模型。

  1. 自定义设计器将流程定义存储到数据库中。
  2. BPEL Generator 读取并验证流程定义。然后,它利用数据库表示以及用于部署的相关文件生成 BPEL XML 文件。
  3. BPEL 流程动态部署到 Oracle BPEL 流程管理器服务器上。

我们将通过以下业务案例来完成每个步骤。

 

业务案例

 

来看一下前面介绍的房地产管理业务流程。该“隔间管理”业务流程涉及以下活动:

  1. 当办公室隔间被占用时启动业务流程(空间的 EmployeeCount 字段设置为 0)。
  2. 等待三个月(如果空间的 EmployeeCount 设置为 > 0,则退出该流)。
  3. 向物业经理发送电子邮件,表明该空间不再由其原来的业务部门控制。
  4. 更新该空间,将其与原属业务部门脱钩。

创建流程。创建流程是即时构建流程的第一步。通常,使用 Oracle Designer 或 Eclipse Designer 建立流程模型并且自动生成 BPEL 文件。

 

在我们的方法中,您将使用自定义设计器替换传统的 IDE。您将在数据库内部存储定义,而不是直接创建 BPEL 文件。

 

图 2 显示了如何在设计器中建立示例业务流程模型。

 

图 2
图 2 自定义流程设计器中的隔间管理业务流程


正如我在前面介绍的,Oracle BPEL 流程管理器引擎具备与提供的任何设计器/工具(后者可以生成有效的 BPEL 文件)集成的独特功能。

 

将定义存储到数据库中。创建业务流程后,下一步是将定义存储在数据库内部。数据库模式必须具备充分的灵活性以存储复杂的流程,并可以使用多个 BPEL 结构(如 flow、pick、invoke 和 scope)重新创建 BPEL 流程。

 

假设您设计了一个包含业务流程设计信息(对应于自定义设计器提供的实际视图)的数据模型。当业务分析人员使用设计器创建业务流程时,定义业务流程结构的信息将保存到数据库。注意,Oracle BPEL 流程管理器在部署(稍后将对其进行详细介绍)前不起作用。

 

图 3 包含用于将流程设计持久保存在数据库中的数据模型的高级 UML 视图。

 

图 3
图 3 用于存储隔间管理流程定义的简化数据模式


WorkflowProcess 对象存储流程设计;它包含参数和节点,用于定义流程中的活动的序列和类型。各种 WorkflowNode 对象是设计器支持的不同类型的流程活动的设计时产物。注意,它们并不是 BPEL 结构,而是随后映射到通常多个 BPEL 活动的自定义结构。使用自定义设计器部署后,CubeManagement 流程详细信息将按如下所示存储到数据库中。

 

WorkflowProcess

Name

 

ID

 

CubeManagement

 

1

 

WorkflowParameter

WorkflowProcess_ID

 

Name

 

Type

 

1

 

CubeParam

 

Space

 

WorkflowNode

Workflow
Process_ID

 

NodeId

 

NextNode

 

Type

 

E-mail

 

Parameter
_ID

 

UpdateField

 

UpdateValue

 

WaitTime

 

1

 

1

 

2

 

Start

 


 


 


 


 


 

1

 

2

 

3

 

Wait

 


 


 


 


 

3 months

 

1

 

3

 

4

 

Message

 

max
@big.com

 


 


 


 


 

1

 

4

 

5

 

Update

 


 

1

 

business
Unit

 

null:

 


 

1

 

5

 

Null

 

End

 


 


 


 


 


 

BPEL Generator 读取这些表以动态生成 BPEL 文件。下个部分介绍了该过程。

 

动态部署 BPEL

 

生成 BPEL 的过程中最复杂的任务涉及从数据库中读取流程设计并为流程生成 BPEL“包”。在该示例中,您将利用 XQuery 生成 BPEL。

 

XQuery 是一种强大、方便的 XML 数据处理语言。它不仅可以处理 XML 格式的文件,还可以处理结构(具有指定树和属性的嵌套结构)类似于 XML 的数据库数据。

 

以下文件(构成 Oracle BPEL 流程管理器的 BPEL 流程包)是动态生成的。

 

BPEL 文件

 

该 XML 文件是实际的业务流程;它遵循 BPEL 规范。

 

WSDL 文件

 

Web 服务 WSDL 文件描述了两个接口:

 

  • Oracle BPEL 流程管理器中托管的 BPEL 流程公开的接口 。该接口非常简单,包含一个用于启动流程实例的调用和一个回调接口。

     

  • eCenterOne 公开的接口。为了简单起见,该工具也包含一个接口,可用于处理从 Oracle BPEL 流程管理器中运行的流程发出的对服务器的任何调用以及关联的回调。

     

bpel.xml

 

一个部署描述符 XML 文件,用于向 Oracle BPEL 流程管理器描述 BPEL 流程。

 


它保存流程设置,其中包含特定于 Oracle BPEL 流程管理器的信息 以及以下信息。
  • 指定 eCenterOne Web 服务 位置的位置信息

     

  • 指定 当对 eCenterOne 的调用失败时应如何执行重试的信息 (重试)。

     

Ant build.xml

 

使用 Oracle BPEL 流程管理器的 bpelc Ant 任务部署 BPEL 流程。(稍后将对此进行详细介绍。)

 

 

下面,我们将主要介绍如何动态生成 BPEL 文件。该过程涉及迭代数据库中存储的节点并为每个节点生成相应的 BPEL 活动:

  1. 迭代数据库。
  2. 生成 BPEL 是在 Java 代码中实施的,此代码读取工作流流程的每个 WorkflowNode 并创建一个由要处理的所有节点组成的列表。
  3. 为每个节点创建 BPEL 活动。
  4. 向包含在 <scope<//> 活动中的一些 BPEL 活动中生成该列表中的每个工作流节点。因此,如果对 BPEL 活动的处理需要使用 BPEL 参数,则此参数只在该作用域中可见(不存在变量名冲突等问题)。必须为每个节点创建 XQuery 模板以生成一个相应的 BPEL XML 标记。XQuery 处理器将处理 XQuery 模板以构建包含节点和实例信息的 BPEL 并返回结果。
  5. 创建 BPEL 文件。
  6. 调用流程组合每个节点的 BPEL 活动并创建一个表示整个流程的主 BPEL 文件。为每个 WorkflowNode 生成的 <scope<//> 块可以通过该方法相互添加。
<process>
        <scope>
                <!a€” start workflow node related activities -!>
        </scope>
        <scope>
                <!a€” wait workflow node related activities -!>
        </scope>
        <scope>
                <!a€” mail workflow node related activities -!>
        </scope>
        <scope>
                <!a€” update workflow node related activities -!>
        </scope>
</process>

下面我们将更详细地分析第 2 步。每个节点都相当于一系列 BPEL 活动。这些信息全都存储在 XQuery 模板文件中。每个节点都有一个 XQuery 模板,其中包含该特定节点的所有 BPEL 结构。您已经创建了与隔间管理流程中的五个节点相对应的 start.xq、wait.xq、message.xq、update.xq 和 stop.xq。

 

下表描述了为示例流程中的 start、wait、message、update 和 stop 节点生成的 BPEL 活动。

 

隔间管理流程中的节点

 

等价的 BPEL 结构

 

Start

 

<sequence>

 


 


 


 


 


 

<receive>

 


 


 


 


 

<assign>

 


 


 


 


 

<scope>

 


 


 


 


 


 

<variables>

 


 


 


 


 

<sequence>

 


 


 


 


 


 

<assign>

 


 


 


 


 

<invoke>

 

Wait

 

<scope>

 


 


 


 


 


 

<variables>

 


 


 


 


 

<sequence>

 


 


 


 


 


 

<assign>

 


 


 


 


 

<invoke>

 


 


 


 


 

<pick>

 


 

消息

 

<scope>

 


 


 


 


 


 

<variables>

 


 


 


 


 

<sequence>

 


 


 


 


 


 

<assign>

 


 


 


 


 

<invoke>

 


 

update

 

<scope>

 


 


 


 


 


 

<variables>

 


 


 


 


 

<sequence>

 


 


 


 


 


 

<assign>

 


 


 


 


 

<invoke>

 


 

End

 

<scope>

 


 


 


 


 


 

<variables>

 


 


 


 


 

<sequence>

 


 


 


 


 


 

<assign>

 


 


 


 


 

<invoke>

 


 


 


 


 

<terminate>

 


 

 

注意如何向 BPEL 活动的层次结构中生成每个流程节点。例如,用于生成 message 活动的 BPEL 如下所示。它存储在 XQuery 文件 message.xq 中。

 

declare namespace ent = "http://www.centerstonesoft.com/schema/entity"; declare variable $nodeKey as xs:string external;
                               
<scope>
        <!-- generation for Message activity -->
        <variables>

        <!-- Define variables input, output and error variables-->
                <variable name="emailRequest" messageType="services:EntityAsyncMsgWSRequestMessage"<//>
                <variable name="emailResponse" messageType="services:EntityAsyncMsgWSResultMessage"<//>
                <variable name="fault" messageType="services:EntityFaultMessage" <//>
        </variables>

        <!--Begin the BPEL activities -->
        <sequence>

        <!--This is the first assign activity.It configures the input variable with 
<!-- identification of node ($nodekey) and function to be called -->
        <!-- by the external webService (bpelSendEmail). -->

                <assign>
                        <copy>
                                <from>
                                        <ent:EntityCollectionElem xmlns:ent="http://www.centerstonesoft.com/schema/entity" >
                                                <ent:Header<//>
                                                <ent:entityName<//>
                                                <ent:EntityElem>
                                                        <ent:entityKey>{ $nodeKey }</ent:entityKey>
                                                        <ent:method >
                                                                <ent:methodName>bpelSendEmail</ent:methodName>
                                                                <ent:parameter>
                                                                        <ent:PropName>thread</ent:PropName>
                                                                        <ent:Type>string</ent:Type>
                                                                        <ent:Value<//>
                                                                </ent:parameter>
                                                        </ent:method>
                                                </ent:EntityElem>
                                        </ent:EntityCollectionElem>
                                </from>
                                <to variable="emailRequest" part="payload"<//>
                        </copy>
                </assign>

        <!--Second and third assign activity configure the input variable -->
        <!--with identification of actual process instance -->

                <assign>
                        <copy>
                                <from variable="input" part="parameters" query="//ent:Header"<//>
                                <to variable="emailRequest" part="payload" query="//ent:Header"<//>
                        </copy>
                </assign>
                <assign>
                        <copy>
                                <from variable="threadKey" part="value"<//>
                                <to variable="emailRequest" part="payload" query="//ent:parameter[ent:PropName = 'thread']/ent:Value"<//>
                        </copy>
                </assign>

                <!--After configuring input variable with node, instance and function to be called, 
                       invoke the external service -->
                <invoke name="invoke" partnerLink="EntityAsyncMsgWSLink" 
                           portType="services:EntityAsyncMsgWS" operation="invoke" inputVariable="emailRequest"<//>

                <!--Check response from external service.If response indicates fault, throw exception. -->
                <!--If no response is received for 10 days, throw exception -->
                <pick name="deadlined_receive" >

                        <!--Set output variable if return result from external webservice is OK-->
                        <onMessage partnerLink="EntityAsyncMsgWSLink" portType="services:EntityAsyncMsgWSCallback" 
                              operation="onResult" variable="emailResponse">
                                <empty<//>
                        </onMessage>

                <!--On fault, check the fault code and throw  appropriate exception-->

                        <onMessage operation="onFault" partnerLink="EntityAsyncMsgWSLink" 
                              portType="services:EntityAsyncMsgWSCallback" variable="fault">
                                <switch>
                                        <case condition="bpws:getVariableData('fault', 'value',  
                                              '/EntityFault/faultcode') = 'MailException' ">
                                                <throw faultName="ent:MailException" faultVariable="fault" <//>
                                        </case>
                                        <otherwise>
                                                <throw faultName="ent:SystemException" faultVariable="fault" <//>
                                        </otherwise>
                                </switch>
                        </onMessage>

                        <!--If no response for 10 days, throw  exception-->
                        <onAlarm  for="'P10D'">
                                <throw faultName="ent:SystemException"<//>
                        </onAlarm>   
                </pick>
        </sequence>
</scope>

                            

查看该 XQuery 模板后,您将了解文档如何生成一个包含 message 流程节点实施的 <scope> 活动。<scope> 活动从 <variables> 部分开始,其中为请求以及正常响应或故障响应定义了变量。variables 部分之后是 <sequence>,其中使用 <assign> 语句序列生成请求。第一个 <assign> 写入该请求的样板;在该 <assign> 中,XQuery 参数 nodeKey 用于将节点标识传递给外部 Web 服务。

 

注意,nodeKey 是生成时间信息。第一个 <assign> 活动之后的两个 <assign> 活动处理运行时信息。第二个 <assign> 复制用于启动流程的初始请求中的标头信息。该标头信息标识 eCenterOne 和 Oracle BPEL 流程管理器流程实例 ID 以及该流程用于的客户。第三个 <assign> 活动复制流程的线程 ID。(正如前面指出的,该流程中的其他节点也存在类似的 XQuery 模板。)

 

XQuery 处理。下面,我们将简要了解一下 XQuery 处理器如何处理所有这些模板以及如何组合它们以构成一个 BPEL 文件。

 

下面的 generateActivity() 接受要为其生成 BPEL 活动的节点列表。对于每个节点,它将使用函数 XQuery.newXQueryFromResource 找到正确的 XQuery 模板。随后,Saxon 处理器将处理该 XQuery 模板并生成一个 XML 字符串。如果节点包含子节点,则将在 while 循环中以递归方式对其进行处理。

                               
/**
* Recursively generates an activity including any child activities.
* @param activity the node that is being generated, the value is null for the top-level
* process node
* @return typically the root element of the generated XML document
* @throws EntityException
 */
protected String generateActivity(WorkflowNode activity) throws EntityException
    {
        //Find the next node in the list to be processed
BpelActivityType activityType = getBpeBpelActivityType(activity);
        //Get the parameters to be passed to node
HashMap params = calculateParameters(activity, activityType);
try {
//Get corresponding XQuery file for the node
XQuery q = XQuery.newXQueryFromResource("bpelgen/" + activityType.getQuery());
//Call the Saxon XQuery Processor to run the query. 
XQueryResult result = q.run(null, params, null);
//Convert the result into a string 
String parentXml = result.serializeString();
if (logger.isDebugEnabled()) {
logger.debug("Generated for parent '" + activityType.getName() + "' is \n" + parentXml);
            }
//If there are child nodes, recursively invoke generate activity
if (_process.hasChildren(activity)) {
//Call java relational class
Iterator it = _process.getChildrenIterator(activity);
//Recursively call generateActivity for child nodes.For a€?Cube Management Processa€?, these will be 
        //start, wait, message, update & end
while (it.hasNext()) {
WorkflowNode childActivity = (WorkflowNode) it.next();
String childXml = generateActivity(childActivity);
// now use Saxon XSLT to add the document generated for the children to the result document
        // Get the Transformer used for this transformation
Transformer transformer = getPrependTransformer();
transformer.setParameter("inserted", DomUtils.getDocument(new ByteArrayInputStream(childXml.getBytes())));
StreamResult saveResult = new StreamResult(new ByteArrayOutputStream());
try {
StreamSource src = new StreamSource(new ByteArrayInputStream(parentXml.getBytes()));
           // Transform the XML Source
transformer.transform(src, saveResult);
                    }
finally {
transformer.clearParameters();
                    }
parentXml = ((ByteArrayOutputStream)saveResult.getOutputStream()).toString();
if (logger.isDebugEnabled()) {
BpelActivityType childType = getBpeBpelActivityType(childActivity);
logger.debug("Preprocessed for parent '" + activityType.getName() + "' and child '" +
childType.getName() + "' is \n" + parentXml);
                    }
                }
            }
// remove the <insert-here> node
Transformer transformer = getRemovePrependTransformer();
StreamResult saveResult = new StreamResult(new ByteArrayOutputStream());
StreamSource src = new StreamSource(new ByteArrayInputStream(parentXml.getBytes()));
transformer.transform(src, saveResult);
parentXml = ((ByteArrayOutputStream)saveResult.getOutputStream()).toString();
if (logger.isDebugEnabled()) {
logger.debug("Generated for '"+activityType.getName()+"' is \n" + parentXml);
            }
return parentXml;
        }
catch (SAXException ex) {
throw new EntityException("Failed to run transform "+activityType.getQuery(), ex);
        }
catch (TransformerException ex) {
throw new EntityException("Failed to run transform "+activityType.getQuery(), ex);
        }
catch (IOException ex) {
throw new EntityException("Failed to load query "+activityType.getQuery(), ex);
        }
    }

                            

generateActivity() 方法以字符串格式生成最终的 BPEL 文件。随后,另一个 Java 程序将它写入文件。可以下载示例代码文件中的所有 XQuery 模板以及隔间管理流程的 BPEL 文件。

 

使用 Ant 进行动态部署。最后一步是使用 Oracle BPEL 流程管理器部署工具编译和部署 BPEL 流程。对于部署,需要生成以下简单的 Ant build.xml 文件,然后执行它(对它支持的参数化 XML 文件再次使用 XQuery)。

<?xml version="1.0" encoding="UTF-8" ?>
<project default="main" basedir="C:/DOCUME~1/jerry/LOCALS~1/Temp/cs_bpelgen_simple24369" name="BpelDeploy">
<target name="main">
<bpelc keepGenerated="true" deploy="jt_dev" rev="1.0" home="c:\orabpel"<//>
</target>
</project>

最后一步是从应用服务器中以编程方式执行 Ant。为此,可以使用 Ant 任务 Execute(位于 org.apache.tools.ant.taskdefs 中)中的代码。该类为以编程方式执行命令行流程并处理正常和错误返回提供了行业级别的支持。

 

以下代码片段描述了如何使用 Ant Execute 任务中的代码实施 BPEL 流程部署。

String args[] = new String[1];
args[0] = "c:/orabpelpm/integration/orabpel/bin/obant.bat";
            
Execute exe = new Execute();
exe.setCommandline(args);
exe.setWorkingDirectory(_deploydir);
int retval = exe.execute();
if ( retval != 0 ) {
throw new ExecuteException("process failed to deploy"); 

在此代码片段中,Execute 类用于直接执行 Oracle BPEL 流程管理器命令行文件 obant。该批处理文件正确地设置 Windows 环境,然后对如上所示的 build.xml 文件调用 Ant。

 

该文件中的非零返回代码通常是由于 BPEL 编译器返回的错误引起的。但这些错误并不常见并且通常是由于流程实施中的某个内部错误引起的。

 

结论

 

CenterStone Software 使物业经理可以使用自定义设计器快速自定义业务流程。这些流程随后动态转换为 BPEL 并部署到 Oracle BPEL 服务器中。凡是在很大程度上依赖业务用户设计组织流程的企业都应好好考虑一下该方法。自定义设计器方法不但使用户不受 BPEL 复杂性的影响,而且还提供了一个独特的平台来即时生成自适应 BPEL 流程。这使企业可以快速实现 SOA 的好处。

 


Jerry Thomas Jerry Thomas [jthomas@centerstonesoft.com] 是 Centerstone Software(帮助世界上许多最大型的企业更高效地自动化和管理他们的房地产、设备、员工、资产、租赁和工作场所操作)的首席设计师。Thomas 专注于 CenterStone 的企业工作场所管理产品和 Web 服务、BPEL 以及系统基础架构。在加入 CenterStone 之前,Thomas 曾担任顾问并在 Riverton、ONTOS 以及 Hewlett-Packard 的开发部门担任要职。

 

将您的意见发送给我们