文章
作者:Clemens Utschig-Utschig
2008 年 9 月发布
在过去几年,事件驱动的体系结构 (EDA) 已经成为众人瞩目的、可满足高性能和可伸缩性要求的方法。在满足那些要求方面,松散耦合的体系结构名声不佳,主要是因为它专注于 Web 服务技术,还因为 XML 的处理要求。本入门教程旨在指导您学习设计事件驱动的 SOA 的基本知识,并介绍 EDA 的基础概念,以及如何能将这些概念应用于 SOA。
一个典型的以流程为中心的 EDA 案例可在每个机场中找到:从办理登机手续到起飞的业务流程。
尽管在现实当中,所描述的流程有许多次要的用例,如机票费计算和机组人员分配,这一教程主要着重几个关键的要求:
实际上,我们中大部分人都遇到过无法满足那些要求的情况,如托运的行李没有运送至目的机场,或紧急登机门更改误报导致延缓登机。
事件可以描述为状态变化,如当您托运行李包至行李系统首次检查它时之间的变化( 已托运 至 途中)。
根据数据生命周期中其定义状态更改来思考,而不是基于历史信息(如实体通过的应用程序)来计算实体状态,需要重新思考传统的面向消息的应用设计模型。
很明显,事件并不是新事务,在运输和零售市场中已使用多年,如 RFID 传感器的使用或跟踪物品的运送。但是,将它们与 SOA 相结合是较新的一个概念。
事件设计是 SOA 界人员的一个常见难题。在消息(或实体)着重于包含描述性数据(如名称)时,事件应当完全专注于描述变化。
下面的示例使用 flightRecord 实体阐述了这一差别。
<?xml version="1.0" encoding="windows-1252" ?>
<flightRecord xmlns="urn:flightRecordSystem" >
<customer>
<name>Clemens Utschig</name>
<dob>12-29-1981</dob>
<address>
<street>1535 Eddy St., Apt 517</street>
<city>San Francisco</city>
<zip>CA 94115</zip>
<country>USA</country>
</address>
</customer> <freqentFlyer carrier="LH" number="1234567891"/> <flightroute segments="1"> <flight segment="1" departureDate="11-15-2005" checkinNo="123" seat="33A" specialMeal="no">LH458</flight> </flightroute> <checkedBags> <bag seq="1" weight="24" metric="pounds" guid="1234567891-1" status="checked"/> </checkedBags>
</flightRecord>
上面定义的 flightRecord 及其所有信息是系统将从业务流程开始(如果不是更早)至结束过程中使用的数据。如前面所讨论,事件反应了实体状态的变化,如行李 (1234567891-1) 在通过安检时的变化。
<
bagEvent xmlns="urn:bagTracking"> <header> <timestamp>11-15-2005 10:23 AM PDT</timestamp> <system type="SCREENING">SFO-1</system> </header> <body> <bag guid="1234567891-1" flightCode="LH458">SECURITY OK</bag> </body> </
bagEvent>
上面的事件没有包含其他实体数据,除了包含用于唯一识别其所属的实体的键。
尽管有人认为一个事件要包含先前的状态,如“CHECKED”,但我们应强烈反对这一做法。事件应当是变化的单一、无依赖的表示。因此,事件的聚合应当由基础架构来完成。
主要设计考虑因素。 在面向服务/流程的设计中,实施通常要遵循预先确定的(设计的)流程。在运行时,这一流程负责评估不同条件(入站消息、条件),这些不同的条件可能意味着消息在(静态)流程中选择不同的路径。
考虑到 EDA,清楚的区分流程的静态部分和基于聚合事件的条件,这一点很重要。在这里,架构师将决定移入服务/流程部分的内容,以及在流程外发布和聚合的事件。
让我们从上面选取一个具体的要求,了解其所适合的地方。
从乘客托运行李包时,这些行包将开始通过行李系统。它们应当最终到达正确的飞机上,很可能是正确的行李仓中,且显然应当准时无误。如果行李包含危险品,乘客将不能登机。
以下的评估条件表将帮助确定要求建模的位置和方式。
| 事件类型 | 流程实施 | 基于事件的实施 |
| 事件可以发生多次(如座位变更) | 只有最后的位置才对 | |
| 因为该事件(如行李包含危险品),流程中的消息选择了另一条路径。 | 事件系统回调入流程实例中 | 几个事件的聚合 |
| 一系列(单向)的通知,无直接影响(如乘客离开检入处进入安检处) | 调用服务 | 业务活动监视 (BAM) 中的报告 |
| 调出至服务/编排 | 仅在流程中! | |
| 宏观:来自不同流程实例的几个事件导致了最终的结果(如所有乘客已经通过了安检) | 事件系统回调入流程实例中 | 一系列微小的单一实例,结果基于聚合 |
钟摆的摆向通常是很明显的 — 不管其是摆向流程或仅是一系列事件。但是,在某些情况中,事件评估将导致流程实例需要了解的新事件。
实施考虑因素 现在您已经了解流程中要建模的内容和作为事件建模并在外部聚合的内容的决策标准,下一步是实施。
首先,需要定义服务级模式,反应 flightRecord 类型。其目标是跟踪整个流程中飞行记录的进度,从开始(乘客检入)到飞机起飞的时间(包括飞机上的行李)。
<xsd:complexType name="flightRecordType"> <xsd:annotation> <xsd:documentation>
The type definition for a flight record, with a customer, an ff account,
its checked bags, and the flight route </xsd:documentation> </xsd:annotation> <xsd:sequence> <!--
we can't check in a passenger whose information we don't have --> <xsd:element name="
customer" type="customerType" minOccurs="1" maxOccurs="1"/> <!--
a flight route can be credited to an ff account --> <xsd:element name="
frequentFlyer" type="frequentFlyerType" /> <!--
the collection of checked bags --> <xsd:element name="
checkedBags" type="checkedBagsType" minOccurs="1" maxOccurs="1"/> <!--
the flight route, which contains the segments --> <xsd:element name="
flightRoute" type="flightRouteType" minOccurs="1" maxOccurs="1"/> </xsd:sequence> <xsd:attribute name="
ticketNumber" type="xsd:string"/> <xsd:attribute name="
status" type="xsd:string"/> </xsd:complexType>
下一步是基于之前讨论的要求定义系统发送出的事件。应当基于必要的事件处理对这些系统进行建模,以支持这些要求。在 CustomerTravelProcess 的情况中,这些要求是:
需要构思两类事件模型:一个用于单个行李包的事件......
<xsd:complexType name="bagEventType"> <xsd:sequence> <xsd:element name="header" type="baseEventHeaderType" minOccurs="1"/> <xsd:element name="body"> <xsd:complexType> <xsd:sequence> <xsd:element name="bagStatus" type="bagEventStatusType" minOccurs="1" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType>
...一个用于乘客的事件类型(通过机场或调换座位)。
一旦定义了事件后,下一步就是完成流程设计,这一设计以“公共数据模型”为中心,且能响应事件回调,如机场紧急取消航班。
在基于事件的设计中,第一步应当集中于作为输入传入流程中的内容。开发人员通常将很多的实体信息(如整个飞行记录)传送至服务。但是,在事件领域中,事件应当启动流程。因此,流程的输入为 customerEvent:
<message name="AirportCheckinToDepartureRequestMessage">
<part name="payload" element="ns1:customerEvent"/>
</message>
and the process is triggered by invocation of customerCheckedIn:
<operation name="customerCheckedIn">
<input message="client:AirportCheckinToDepartureRequestMessage"/>
</operation>
对于飞行中的实例事件,可在 BPEL 中将它们作为 eventHandlers 进行建模。 flightCancelled 的实施 stub 如下:
<eventHandlers>
<onMessage portType="client:AirportCheckinToDeparture"
operation="flightCancelled" variable="inputEventVariable"
partnerLink="client">
< .. />
</onMessage>
</eventHandlers>
在这两个示例中,具备有见识的架构师眼光的人将看到 BPEL 集中于推送(基于操作)模型,这增加了事情的难度。因此,我们的基于事件的引擎将需要调用特定的操作,而不仅是发送事件。
因为事件应当透明,不搞混 BPEL 流程设计,建议使用传感器(位于变量或活动中)。从性能角度看,传感器应当只发送事件通知,而不牵涉任何处理逻辑(或发出同步服务调用)
<sensor sensorName="ffStatusEvent" classname="com.example.BpelActivitySensorAgent" kind="activity" target="retrieveCustomerFFRecord"> <activityConfig evalTime="completion"> <variable outputDataType="customerFFStatusEventType" outputNamespace="urn:flightRecordSystem" target="$lCustFFStatusEventVar/ns1:customerStatusFFEvent"/> </activityConfig> </sensor>
一般来说,不鼓励在 BPEL 流程内部实施“事件聚合”,原因有几个(仅管在某些工作中是可能的)。BPEL 是一个编程语言,可用于将服务编排为工作流而不是评估一系列状态变化,供长期使用或用于处理大量的任务。
另一个问题是设计评估算法本身。鉴于是静态的,可以很容易地对定义的状态流程进行建模(比如 收到、 过程中),复杂的模式识别是绝对无法实现的,且流程不是尝试它的正确位置。
在同类最佳的方式中,这些事件评估应当在流程外完成。但一旦完成后 — 即如果检测到一个模式 — 可以通过事件通知流程完成其执行。
在本机场案例中, checkinToDeparture 流程以及其他系统(如安检系统)将触发事件。将对这些事件进行评估以确定某一乘客是否可以登机及其行李包的位置。最后一个事件发生(即机仓门关闭)后,将通知(几个乘客的)流程实例,然后继续处理。这不但使得后期的基于流程报告变得简单,还确保流程的一致性。
现在我们已经清楚地了解应当和不应当采用基于事件的情况,实施过程也变得非常轻量级。它从紧密耦合的编排过程转变为响应传入的事件的事件消费者。
为展示这一区别,我们看一下另一要求:
如果行李包含危险品,乘客将不能登机。
在行李包中包含危险品(武器或其他违禁品)的情况下,需要解决的几十种模式将交由复杂的事件处理引擎处理。但一旦出现模式匹配的情形,进一步的处理,如向乘客或管理部门通知该事件(“发现危险品”),将由流程进行。
本入门教程中的示例简单介绍了您在创建以流程为中心的 SOA 中,如何能利用事件驱动的体系结构的强大功能,以及利用模式识别、高端松散耦合以及 EDA 的其他重要方面。我们建议读者以本教程为基础深入学习 EDA。