SOA 最佳实践:BPEL 指南 |
第 3 部分:实现动态 BPEL 流程 了解如何通过在运行时操作端点引用实现动态绑定。
Web 服务和面向服务体系结构 (SOA) 使业务流程可以通过与其他业务流程和应用程序进行交互而轻松扩展。BPEL 流程通过合作伙伴链接定义此交互。这些链接定义了接口(消息和操作)、传输协议,并且最重要的是定义了要使用的每个服务的位置。 在大多数基本流程设计中,合作伙伴链接是静态的;它们引用开发人员在设计时指定的一个外部流程。该方法适用于高度定向或高度限制的系统。而在大型系统中,业务流程更为复杂。它们与多个外部服务交互,并定义了多个合作伙伴链接,而且其中的某些合作伙伴链接在设计时可能是不可知的。因此,必须在业务流程内部构建所有可能的调出以及用于确定要使用的合作伙伴链接的逻辑,这将使该流程不必要地复杂化。此外,随着合作伙伴链接的增加,最终的流程变得越来越难以处理,这是因为对合作伙伴链接的任何更改都要求修改整个业务流程。 幸运地是,BPEL 语言支持合作伙伴链接的 动态绑定 概念。动态绑定使开发人员可以通过配置或运行时的输入添加新服务。该方法不需要在设计时预计和管理所有父子关系。 在 BPEL 指南的这一部分中,我将概述一个有效的策略,即让系统在运行时动态管理合作伙伴链接,从而使 BPEL 流程不受合作伙伴关系链接变化的影响。此外,我还将介绍如何按顺序或并行地动态调用多个 BPEL 流程。 动态绑定概述 与传统编程领域的面向对象的分析和设计相似,合作伙伴链接的动态绑定可以实现代码模块化以及流程之间的运行时绑定。该方法的好处包括:
使用动态流程还可以为在高级流程的开发与配置和维护之间存在职责划分的组织带来重要好处。开发小组可以负责了解 BPEL 实现以及流程组件创建的细节,随后,业务分析员或支持小组等领域专家便可以将这些组件组装到单个工作流中,而不必详细了解合作伙伴链接、命名空间、WSDL、XPATH 以及其他技术细节。 构建动态 BPEL 流程 正如我在前面所介绍的,合作伙伴链接描述业务流程或其他服务的接口。BPEL 流程使用合作伙伴链接中存储的信息调用这些外部服务。 合作伙伴链接使用 WSDL 中的 portTypes 定义构成服务接口的操作和消息类型。如图 1 所示, portTypes 还间接定义了用于与服务(绑定)和服务的位置(服务)进行通信的传输。
图 1 工作中的 portTypes 在静态 BPEL 流程中,合作伙伴链接信息在设计时定义。但在某些情况下,开发人员并不知道所有合作伙伴链接信息,或者需要在运行时更改所有合作伙伴链接信息以满足数据或其他动态要求。 来看一个贷款处理情景的示例。在该示例中,您需要根据输入数据(如地理区域、贷款额或信用记录)选择贷款提供商。该数据在运行时是不可知的,并且如果涉及很多可能的贷款提供商,则可能很难将该流程的模型构建为静态地管理您可使用的所有不同服务。 动态地选择提供商则解决了这个问题。WS-Addressing 标准提供了一个称作端点引用的机制,使您可以在 WSDL 中选择一个可用服务,甚至还可以在运行时定义新服务。业务流程静态地依赖于 portType 中定义的接口信息,而端点引用(将绑定映射到服务)使您可以动态重新定义服务位置。本质上而言,端点引用是 WSDL 中定义的静态服务元素的动态替换项。在很多情况下,只要服务全部与标准接口一致,流程设计者便可以不必决定要调用哪些服务。Oracle BPEL 流程管理器附带的 DynamicPartnerLink 示例为了解这些主题提供了一个不错的切入点。我们将逐步研究该示例;然后,您将了解如何从头构建一个动态流程。(注意:建议您在使用该示例之前熟悉一下标准 LoanFlow 教程。) 了解 DynamicPartnerLink 示例 DynamicPartnerLink 示例是了解合作伙伴链接和端点引用这两个基本概念的十分好的资源。它允许您从三个贷款服务提供商(United、Star 和 American)中指定一个,并根据流程输入动态调用相应的服务。 在本文中,您将使用 Oracle BPEL 流程管理器 10.1.2 的 GA 版本中提供的示例;您将在目录 [BPEL_HOME]\samples\references\DynamicPartnerLink 中找到它。我使用 10.1.2 版的补丁 1 开发并测试了本文介绍的代码。 注意:当您初次加载和部署 Dynamic Partner Link 示例时,请不要在 Oracle JDeveloper 可视化设计器中对代码进行任何修改,只按原样部署它即可。如果要进行并保存这样的更改,JDeveloper 将通过引入换行符基于它的标准 XML 布局重新格式化 BPEL 代码。JDeveloper 修改 <EndpointReference> 数据内部的 <Address> 和 <ServiceName> 标记,在 </Address> 和 </ServiceName> 结束标记之前添加一个换行符。附加到这些元素内部的数据的换行符用于中断绑定。如有必要,可以删除服务和地址上位于结束标记之前的换行符来更正该问题。稍后,我将介绍另一个方法来填充不受 JDeveloper 所应用格式影响的端点引用。 从控制台启动该示例时,将要求您填写贷款流程演示的标准贷款应用程序数据(SSN、电子邮件等)以及“provider”域。在 provider 字符串中指定以下值之一: united、 american 或 star。分别使用这三个值运行该示例,查看它的运行情况。该流程将动态调用相应的贷款提供商。也可以在 provider 字符串中使用某个其他值或根本不使用值来试用该示例。 要了解该动态流程如何工作,首先必须分析 DynamicPartnerLink.bpel 文件。该文件中第一个比较有意义的项是贷款服务合作伙伴链接: <partnerLink name="LoanService" partnerLinkType=" services:LoanService" myRole="LoanServiceRequester" partnerRole="LoanServiceProvider"/>该链接指定了一个通用贷款服务名称和类型 ( services:LoanService),而没有定义特定的贷款服务(如 UnitedLoan)。 LoanService 合作伙伴链接是在 LoanService.wsdl 文件中定义的;导入该文件的方法是将它添加到 bpel.xml 文件的 <partnerLinkBindings> 部分中,如下所示: <partnerLinkBinding name="LoanService"> <property name="wsdlLocation">LoanService.wsdl</property> </partnerLinkBinding>在 LoanService.wsdl 文件中您将发现,每个可用的贷款供应商都在这个 WSDL 文件中定义为 <service>,如下所示。 <service name="StarLoan"> <port name="LoanServicePort" binding="tns:LoanServiceBinding"> <soap:address location="http://localhost:9700/orabpel/default/StarLoan"/> </port> </service> <service name="UnitedLoan"> <port name="LoanServicePort" binding="tns:LoanServiceBinding"> <soap:address location="http://localhost:9700/orabpel/default/UnitedLoan"/> </port> </service> <service name="AmericanLoan"> <port name="LoanServicePort" binding="tns:LoanServiceBinding"> <soap:address location="http://localhost:9700/orabpel/default/AmericanLoan"/> </port> </service>必须应认识到,并不存在“真正”名为“LoanService”的服务。而 LoanService 只是一个模板,不过从中可以选择真正的贷款提供商服务(UnitedLoan、AmericanLoan、StarLoan)。只要真正的服务全都支持模板 WSDL 中定义的同一接口(相同的数据类型、消息、角色、端口和合作伙伴链接类型),该方法便会有效。由于此处的更改可能会彻底影响许多流程,因此必须仔细地定义该模板。 LoanService.wsdl 文件定义了所有服务选项,父流程可以从中选择要动态调用的选项。该模型要求在添加每个新服务时重新部署 WSDL 文件。与修改父流程以使每个新服务都包含新合作伙伴链接和路由逻辑相比,该方法有了很大的改进。(稍后,您还将了解如何将服务端点与 WSDL 文件解除关联。) 返回到 DynamicPartnerlink.bpel 文件,我们要了解的下一个特性是 partnerReference 变量: <variable name="partnerReference" element="wsa:EndpointReference"/>该变量的类型为 EndpointReference。它包含一个命名空间 wsa,后者在该文件的顶部定义为 xmlns:wsa="http://schemas.xmlsoap.org/ws/2003/03/addressing"WS-Addressing 标准提供了 EndpointReference 类型的模式。可以使用 <assign> 将此类变量指定到合作伙伴链接以便修改地址和服务信息,这样就可以在运行时修改合作伙伴链接了。 DynamicPartnerLink 流程基本上由一个切换组成。它检查调用方传入的“provider”字符串。然后,它将 EndpointReference xml 数据结构指定给 partnerReference 变量,该变量包含与您请求的服务相关的信息。切换后,将 partnerReference 变量指定给 LoanService 合作伙伴链接,随后将调用该合作伙伴链接。 下面是在输入字符串(服务提供商)为“united”时如何完成该任务: <assign> <copy> <from> <EndpointReference xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing"> <Address>http://localhost:9700/orabpel/default/UnitedLoan</Address> <ServiceName xmlns:ns1="http://services.otn.com">ns1:UnitedLoan</ServiceName> </EndpointReference> </from> <to variable="partnerReference"/> </copy> </assign><from> 与 </from> 标记之间的所有内容均为指定给 partnerReference 变量的文字 XML。将 partnerReference 变量指定给 LoanService 合作伙伴链接时,该数据将覆盖此合作伙伴链接中指定的地址和服务。 您已经了解了如何使用 LoanService 合作伙伴链接和和 LoanService.wsdl 调用在运行时选择的服务,下面就可以开始构建一个动态流程了。 创建一个动态 BPEL 流程 现在,我们从头创建一个动态 BPEL 流程。 1. 创建一个新的 BPEL 项目。 2. 从 DynamicPartnerLink 示例中导入 LoanService.wsdl 文件。 将 LoanService.wsdl 文件从 DynamicPartnerLink 示例复制到 MyDL 项目的工作目录中(默认情况下为 [BPEL_HOME]\integration\jdev\jdev\mywork\Workspace1\MyDL)。(该方法不但可以节省您的时间,而且还免去了您创建自己的动态 WSDL 和子流程服务的麻烦。)然后,在 Applications Navigator 中右键单击 MyDL 项目,选择 Add to Project...。从该目录中选择 LoanService.wsdl 文件,然后单击 OK。 尚未将 LoanService.wsdl 文件添加到 bpel.xml 文件中。您将在该流程的后面部分中(即实现 EndpointReference 变量时)执行该操作。 3. 创建贷款服务合作伙伴链接模板。
图 2“Create Partner Link”对话框 4. 创建调用和接收动作来外调 DynamicLoanService 合作伙伴链接。 将 invoke 和 receive 动作分别从组件模板拖到流程(位于 receiveInput 和 callbackClient 动作之间)中。将一个箭头从 invoke 拖到 DynamicLoanService 合作伙伴链接并创建输入变量。对 receive 执行相同的操作。这些变量应分别命名为 loanInput 和 loanOutput。 5. 配置输入数据 loanInput。 通常,您将修改 MyDL.wsdl 文件来从用户那里获得贷款输入数据。此处为简单起见,您只需硬编码一个 assign 来填充 loanInput 变量。将 assign 置于 receiveInput 动作之后并创建一个将值“123456789”(这是一个字符串,而非数字,因此不要忘了为它加上引号)放入 loanInput 的 SSN 元素中的复制规则: <assign name="PopulateSSN"> <copy> <from expression="'123456789'"/> <to variable="loanInput" part="payload" query="/ns2:loanApplication/ns2:SSN"/> </copy> </assign>6. 创建 partnerReference 变量。 在 Structure 窗口中,展开 Variables 树,然后展开 Process 并选择 Variables 项(参见图 3)。
图 3 展开“Variables”树 右键单击 Variables 并选择 Create Variable...。将变量名设为“partnerReference”,并将类型设为“Element”。单击元素框旁边的手电筒图标显示类型选择器。在 Project WSDL Files → LoanService.wsdl → Inline Schemas → schema 下找到类型“EndpointReference”(参见图 4)。
图 4 选择“EndpointReference” 7. 设置 partnerReference 变量。 在 DynamicLoanService invoke 前面创建另一个 assign。使用此 assign 设置 partnerReference 变量。最初,您将它硬编码为 UnitedLoan 服务,但您将在下个部分中把它变为动态。 此处,您可以通过重新格式化 EndpointReference xml 数据避免在 DynamicPartnerLink 示例中遇到的问题。创建一个用这个空 EndpointReference 填充 partnerReference 变量的复制规则: <EndpointReference xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing" xmlns:ns1="http://services.otn.com"> <Address/> <ServiceName/> </EndpointReference>在复制规则的“from”块中,在输入以上信息之前确保选择类型“XML Fragment”。由于在把 partnerReference 变量复制到 DynamicLoanService 合作伙伴链接时将其视为单独的 XML 文档,因此执行此复制才能为 partnerReference 建立命名空间信息。否则,在试图将 partnerReference 变量指定给合作伙伴链接时将发生一个空指针异常。 现在,您可以使用标准复制规则填充 partnerReference 变量的 ServiceName 和 Address 元素。确保根据空白端点引用中的定义为服务指定同一命名空间 ( ns1)。<assign> 应如下所示: <assign name="SetupPartnerlink"> <copy> <from> <EndpointReference xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing" xmlns:ns1="http://services.otn.com"> <Address/> <ServiceName/> </EndpointReference> </from> <to variable="partnerReference"/> </copy> <copy> <from expression="'ns1:UnitedLoan'"/> <to variable="partnerReference" query="/ns3:EndpointReference/ns3:ServiceName"/> </copy> <copy> <from expression="'http://localhost:9700/orabpel/default/UnitedLoan'"/> <to variable="partnerReference" query="/ns3:EndpointReference/ns3:Address"/> </copy> </assign>还要注意的是,直到使用 partnerReference 变量时,才会把 LoanService.wsdl 文件添加到 bpel.xml 文件中(以便可以访问 EndpointReference 模式)。 8. 将 partnerReference 变量复制到 DynamicLoanService 合作伙伴链接中。 在 DynamicLoanService 的 SetupPartnerlink 动作和 <invoke> 之间创建一个新的 <assign>。创建一个新副本并对它进行设置(如图 5 所示)。
图 5 创建一个新的复制规则 完成这些步骤后,将创建一个动态 BPEL 流程。该流程已经把地址指定硬编码了。可以用运行时收集的信息替换第 7 步中的最后两个复制规则来修正此种情况。图 6 中显示了 BPEL 流程示意图。
图 6 新的 BPEL 流程 提高动态流程的效率 正如您在前一个示例中看到的,LoanService.wsdl 列出了在运行时动态调用的所有可用服务。可以通过在每次添加新服务时消除修改业务流程的需要来丰富此业务流程的动态机制。新服务在 WSDL 中定义,并重新部署 WSDL 以启用新服务。 您可以将此动态机制再提高一个层次:WSDL 驱动的方法要求在设计时就知道新服务的位置,但您可以使流程更独立于 WSDL。该方法不需要在每次添加服务时重新部署 WSDL。 在运行时消除地址相关性。 服务地址可能经常变化,但您可以使动态流程在运行时不受这些变化的影响。如果只指定一个服务名称而未指定地址,则将从 WSDL 中检索服务地址。为了进行演示,我们从模板复制规则的 XML 片段中删除地址的 stub (<Address/>)。由于您要很快恢复此地址信息,因此在执行此操作之前备份 MyDL.bpel 文件。此外,从 SetupPartnerLink <assign> 语句中删除操作该地址的复制规则。 SetupPartnerlink <assign> 现在应如下所示: <assign name="SetupPartnerlink"> <copy> <from> <EndpointReference xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing" xmlns:ns1="http://services.otn.com"> <ServiceName/> </EndpointReference> </from> <to variable="partnerReference"/> </copy> <copy> <from expression="'ns1:UnitedLoan'"/> <to variable="partnerReference" query="/ns3:EndpointReference/ns3:ServiceName"/> </copy> </assign>现在,再次部署并运行 MyDL 流程。尽管缺少地址,它仍可以成功地调用 UnitedLoan 子流程。可以通过在 BPEL 控制台中查看流程树视图来验证该动作。其结果是,可以通过只部署一个新的 WSDL(它将包含已修改的服务地址信息)来修改动态流程的行为。需要权衡的是,添加新服务将需要修改和重新部署 WSDL。 独立于 WSDL 服务。 在某些情况下,除了有许多要管理的服务以外,您可能还遇到这样的情况:服务地址经常变化,或要避免对 WSDL 文件进行频繁地更新。允许流程在运行时指定端点引用的地址即可解决该问题。 返回前一个版本的 MyDL.bpel 文件(该文件包含地址操作复制规则)。从模板 XML 片段和 ServiceName 复制规则中删除服务信息,而不是删除地址信息。<assign> 现在应如下所示: <assign name="SetupPartnerlink"> <copy> <from> <EndpointReference xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing" xmlns:ns1="http://services.otn.com"> <Address/> </EndpointReference> </from> <to variable="partnerReference"/> </copy> <copy> <from expression="'http://localhost:9700/orabpel/default/UnitedLoan'"/> <to variable="partnerReference" query="/ns3:EndpointReference/ns3:Address"/> </copy> </assign>运行该示例时,即使在未指定服务名称的情况下,此流程仍正确调用 UnitedLoan 服务。您可以创建只包含一个虚拟服务的 DynamicPartnerLink WSDL,并且即便某些服务不在 WSDL 中,只要在运行时知道这些服务的地址就可以调用它们。如果由于某种原因未指定地址,则它将使用 WSDL 中默认服务的地址。因此,一个好办法就是让该服务指向实际的 BPEL 流程(一个可能记录错误或发送通知的流程)。 构建异常处理框架时就可以使用该技巧。如果有多个提供给定服务的可用地址(如本地服务器和远程冗余服务器),则可以在对主服务器的调用失败时使用异常处理程序覆盖端点引用中的地址信息并重试服务调用切换到第二个地址。 调用多个动态流程。 在某些情况下,可能需要将单个数据集按顺序或并行传递给多个子流程。您可以使用一个或多个 while 循环实现此类行为。 我们来看一个简单的示例。贷款服务提供商的办公时间可能是按一周的日期安排的。该信息存储在数据库中。贷款请求于星期一到达,当查询数据库时,它返回一个可用贷款服务提供商(United 和 Star)列表。要处理此贷款,需要按顺序或并行调用 United 和 Star 子流程。数据库查询返回以下结果: <dbOutput> <part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="response- headers">null</part> <part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="DynamiclinksCollection"> <n:DynamiclinksCollection xmlns:n=http://xmlns.oracle.com/pcbpel/adapter/db/top/MyDynamicLink xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Dynamiclinks> <address>http://localhost:9700/orabpel/default/UnitedLoan</address> <day>monday</day> <uid>1</uid> </Dynamiclinks> <Dynamiclinks> <address>http://localhost:9700/orabpel/default/StarLoan</address> <day>united</day> <uid>4</uid> </Dynamiclinks> </n:DynamiclinksCollection> </part> </dbOutput>为了按顺序调用这些服务,您应创建一个 while 循环。该 while 循环从集合中获得地址并对每个服务执行一个动态调用/接收。 <!-- first setup the counter variable "i" --> <assign name="CounterReset"> <copy> <from expression="1"/> <to variable="i"/> </copy> </assign> <!-- while loop goes until all link collection notes are done --> <while name="LoanLoop" condition="bpws:getVariableData('i') <= count(bpws:getVariableData('dbOutput','DynamiclinksCollection', '/ns3:Dynamiclin ksCollection/Dynamiclinks'))"> <sequence name="Sequence_1"> <!-- reset the endpoint with the usual xml fragment --> <assign name="ClearEndpoint"> <copy> <from> <EndpointReference xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing" xmlns:ns1="http://services.otn.com"> <Address/> </EndpointReference> </from> <to variable="partnerReference"/> </copy> </assign> <!-- set the address in the endpoint variable based on the current node --> <assign name="SetEndpoint"> <copy> <from variable="dbOutput" part="DynamiclinksCollection" query="/ns3:DynamiclinksCollection/Dynamiclinks [number(bpws:getVariableData('i'))]/address"/> <to variable="partnerReference" query="/wsa:EndpointReference/wsa:Address"/> </copy> </assign> <!-- copy the endpoint variable into the partner link --> <assign name="DoPartnerlink"> <copy> <from variable="partnerReference"/> <to partnerLink="LoanService"/> </copy> </assign> <!-- invoke the partner link --> <invoke name="Invoke_2" partnerLink="LoanService" portType="ns2:LoanService" operation="initiate" inputVariable="loanInput"/> <!-- be sure to increment your counter or you have an infinite loop --> <assign name="CounterIncrement"> <copy> <from expression="bpws:getVariableData('i')+1"/> <to variable="i"/> </copy> </assign> </sequence> </while>在以上示例中,您调用了异步服务。但可以并行调用这些异步服务,方法是从 <invoke> while 循环中删除 <receive> 并为它提供一个自己的 while 循环。在运行 <receive> 捕获每个调出流程的响应之前,这些响应将排队。receive 任务将按照响应的返回顺序收集这些响应。该方法将避免短时间运行的任务发出的响应排在长时间运行的任务发出的响应之后。 在收集了所有异步响应之前,建议不要在 <receive> while 循环外部继续运行。 结论 您在本文已经了解到,通过使用端点引用进行动态绑定,BPEL 流程可以变得更为灵活并快速适应不断变化的业务条件。通过将业务逻辑与合作伙伴地址分离,可以增强流程的自适应性和可移植性。
Sean Carey 是 SPS Commerce(一家在托管 EDI 领域中处于领先地位的公司)的软件设计师。Sean 在关键任务电子商务实现方面具有 7 年多的工作经验,并在软件设计方面具有 15 年的行业经验。 将您的意见发送给我们 |