文章来源:http://msdn.microsoft.com/msdnmag/issues/07/06/CuttingEdge/default.aspx?loc=zh
本文代码
Windows® Workflow Foundation 作为 Microsoft® .NET Framework 3.0 的重要组成部分,提供了编程模型、运行时引擎以及一些工具,这些工具用来开发可以合并到基于 .NET 的应用程序的工作流,或作为服务向工作流可能涉及的所有客户端公开的工作流。那么,您在什么时候才真正需要自定义解决方案中的工作流呢?
当业务逻辑和规则受经常性变化的约束时,工作流肯定会有帮助。由于您对不同的客户的需求自定义了相同的应用程序,或正好因为该客户特别需要此类灵活性,就可能出现这种情况。工作流可能处在业务流程管理 (BPM) 服务器的后面,以协调向分布式复合应用程序的模块公开的业务服务。最终,工作流会有助于支持所有形式的长时间运行的跨公司业务逻辑的实现。
从开发人员的角度而言,工作流是一系列一起执行的,可以表示所需行为的活动。当要求为某个实际业务逻辑建模时,工作流肯定会结束处理事务性任务。那么,您将如何在 Windows Workflow Foundation 中编译事务性语义的代码呢?
几乎所有的企业系统内部都有工作流来处理特别复杂的问题,而几乎所有的复杂问题都包含一个事务。有两种主要类型的事务需要处理。其中一类包括了典型的短期事务,通常称为 ACID 事务。另一类则可被描述为全局的,长时间运行的业务范围事务。这种事务通常是业务流程的组成部分,并且可由多个 ACID 事务组成。这些 ACID 事务的成功或失败将会影响由工作流表示的较大业务流程的整体结果。
Windows Workflow Foundation 提供了特殊活动,以实现 ACID 和业务范围事务,这样不仅有效,而且还比较容易进行编码和更新。在本期专栏中,我会介绍为各种类型的事务性任务(您可能会将其置入 Windows Workflow Foundation 工作流)提供的活动集。
Windows Workflow Foundation 工作流可能包含一个或多个应视为原子任务的活动块。一旦您在最外部的事务块中封装这类活动,Windows Workflow Foundation 运行时就会确保所有包含的活动都成功或都失败。这类事务称为 ACID 事务,与预期行为的说明完全相同 — 即原子性、一致、独立且持久(见图 1)。
整个 Windows Workflow Foundation 工作流也可被视为单个的长时间运行的事务 (LRT)。在这种情况下,您可以使用工作流语义来描述业务流程,或其中的一部分,它涉及了多个服务、独特的软件平台和不同公司。LRT 事务可能需要花费数分钟,数日,甚至数周时间,才能知道结果。该流程可能会实现多步骤操作,并跨越多个公司的信息系统。通常,LRT 事务由一个或多个独立成功或失败的 ACID 事务组成。但是,由于该流程耗时较长且较为复杂,因此该流程中的进一步步骤可能会确定与前一个 ACID 事务的结果无法兼容的条件。在这种情况下,回滚已提交事务的结果已太晚,但是其影响必须要以某种方式获得补偿。
尽管“事务”这个词一般用于标记 ACID 和长时间运行流程,但是两者之间仍存在明显差异。“事务”这个词通常是指一系列要处理的操作,并视为工作的单一单位。该操作通常在几秒内就结束了,并且从不出现挂起,也不会无限制地等待用户输入或其他人为操作。它不需要持久性,被视为要么全有要么全无的操作。正如您将会发现的一样,有关构成典型事务的说明基本上就是 ACID 事务的说明。
那么什么才是真正的长时间运行的事务呢?从本质上来说,难道它就是在需要的时候持续更长时间,可以被挂起、恢复的 ACID 事务吗?长时间运行的事务的概念比“事务”这个术语要笼统得多。LRT 是一整套涉及松散藕合和独立系统的操作。
某些事务的长时间运行性质可能会使管理它们象管理 ACID 事务一样,变得更复杂或完全不切实际。ACID 事务经常会要求在事务期间锁定某些关键数据 — 这对于仅需要几秒钟的事务不是问题。但是,如果涉及特定商务交易的任何资源在整个处理期间不能保持锁定状态,那么该 ACID 模式就不适合。不同的操作必须作为独立的操作,使用某个周围逻辑来完成,以协调操作和结果,并且必须准备好在需要的时候对错误和故障进行补偿。协调逻辑可以在充当 BPM 服务器的 Windows Workflow Foundation 工作流中构建,也可以在之后用于最外部工作流的 Windows Workflow Foundation 复合活动中构建。
ACID 事务是短期事务,在这里提交还是回滚通常在几秒钟内就可以确定。回滚逻辑必须能够取消正在进行的操作以及可能组成这个操作的所有中间步骤,它还必须补偿前一个操作的影响。从用户的角度而言,似乎没有什么不同。在原子级遍历两个或更多表的存储过程中编码的典型数据库事务是 ACID 事务的完美示例。
ACID 事务可能是本地事务,也可能是分布式事务。本地事务涉及单个资源,例如您连接的数据库。分布式事务跨越多个异类资源,并要求有事务处理监视器。分布式事务处理协调器 (DTC) 是用于 Microsoft Windows 2000 和最新版本的 Windows 的事务处理监视器。
要在您的工作流中构建事务段,您可以使用 TransactionScope 活动(见图 2)。事务范围内组合的所有活动形成了能够实现典型 ACID 架构的工作单位。在所有子活动都成功完成后,将提交事务,且工作流继续进行。如果从任何子任务引发异常,TransactionScope 活动会自动回滚操作。
图 2 中显示的工作流示例会接受订单编号,并继续付款。资金首先从客户的帐户中取出,然后再划转至供应商的帐户。该操作必须是原子操作,这两个步骤不是都成功完成,就是都失败。在任何情况下,涉及的资源都必须以一致状态保留,这样就不会违背约束,并保证数据的完整性。
内部步骤对开发人员和最终用户都是透明的。理想情况下,外部软件组件不会在状态确定之前访问任何事务处理的资源的状态。因此,隔离需要可序列化的底层事务,它是数据库和类似资源管理器支持的最昂贵的一类的事务。
在 Windows Workflow Foundation 中,您将 TransactionScope 活动用作按顺序运行、且完全遵守 ACID 语义的子活动的容器。如果您在事务处理的作用域中使用 Parallel 活动,那么这些活动会在该活动内同时运行,但是对其他作用域会按顺序运行。
TransactionScope 活动提供了两个可以采用声明的方式来配置事务的属性:IsolationLevel 和 TimeoutDuration。IsolationLevel 属性的类型为 IsolationLevel — 一个在 System.Transactions 命名空间中定义的枚举。事务的隔离级别可以确定在事务完成之前,其他事务会具有哪个级别的内部数据访问权。图 3 详细介绍了采用最常使用级别的情况。
对于 IsolationLevel,它的默认值是 Serializable。这是最安全,也是代价最高昂的选项,但并非严格要求所有的情况都采用。可序列化事务会在事务期间锁定数据,从而防止其他同时运行的事务也访问同一数据。但是,TransactionScope 活动可以通过设置 30 秒的超时来减少使用这个选项。如果您担心并发操作,并需要在 SQL Server™ 2005 上运行事务,您可以选择新的快照隔离级别。不像其他的一些隔离级别,快照不限于只以编程方式来进行设置。它可以应用于 SQL Server 2005 数据库,并使用下列语句以管理员的身份对其进行配置:
ALTER DATABASE <name> SET ALLOW_SNAPSHOT_ISOLATION ON
TransactionScope 活动是围绕 .NET Framework 2.0 TransactionScope 类的实例而构建的包装,这是专门针对程序员设计出的最友好的类之一。它涵盖了 TransactionScope 对象中的一切内容,堪称完美。该对象还负责其他所有事情。它可以确定您是需要本地事务还是分布式事务,登记需要的分布式资源,以及继续其他本地处理。当代码到达一个无法以本地方式运行的点时,相应地,它也会提升至 DTC。
您可以登记具有实现了 ITransaction 界面的事务的所有对象。该列表包括 ADO.NET 2.0 数据提供程序,以及 Microsoft 消息队列 (MSMQ)。
当某个代码调用 TransactionScope 对象的 Complete 方法时,它会指示事务作用域内的所有操作都已成功完成。请注意,该方法不会实质终止分布式事务,因为在进行 TransactionScope 释放时,提交操作仍将执行。但是,调用 Complete 方法后,您就不能再使用分布式事务了。
TransactionScope 活动使事务性任务与 Visual Studio® 2005 设计器中的分组活动一样简单。请考虑一下图 2 显示的活动后面的代码。键码显示在图 4 中。
事务性代码包含三个 Code 活动。第一个 Code 活动可以运行数据库命令,并通过减去指定的数额来更新客户端帐户的余额。接着,这个数额会被添加到供应商帐户的余额中。如果给定了底层 TransactionScope 对象的特征,则可以轻松分配数据库。
更重要的是,资金的转移必须是原子操作。如果执行流到达了 TransactionScope 活动的最后,则事务会成功完成并提交。如果有任何异常在某个点引发,则事务会自动回滚任何工作。所有这些操作对工作流开发人员都是完全透明的。作为工作流开发人员,您不需要进行补偿,甚至错误处理。
如果订单编号是奇数,则示例代码会引发一个异常,因为代码在图 2 所示的最后一个活动后面:
void CheckConsistency_ExecuteCode(object sender, EventArgs e)
{
if (orderNo % 2 > 0)
throw new DiscontinuedProductException();
}
引发的异常是说明应用程序中特定情况的自定义异常对象。这足以触发底层 .NET TransactionScope 对象的回滚机制。图 5 显示了处于活动状态的应用程序以及您在回滚时收到的消息。图 6 说明了已提交的成功事务。
如果您需要捕获在工作流各个环节,特别是在 TransactionScope 活动各环节发生的异常,请切换到错误处理程序,查看和添加 FaultHandler 活动。然后您可以将此活动配置成捕获特殊异常类型,并按照所需的数量将活动添加至其作用域,以处理该异常。虽然这在 ACID 方案中没有多少意义,但是通常您在处理该异常时可能会运行另一个事务。错误处理程序的目标就是撤消发生异常的活动的部分和失败的工作。但是,就事务作用域而言,回滚是自动的,并且不需要写入特殊 SQL 命令来取消前一数据库操作带来的影响。
您必须知道,对 TransactionScope 活动的使用存在一些限制。首先,您不能使用 Suspend 活动挂起事务内的工作流。其次,TransactionScope 活动不能在另一 TransactionScope 活动内部,或任何实现了 ICompensatableActivity 界面的活动中进行嵌套。这些活动的示例是工具箱中的 CompensatableTransactionScope 和 CompensatableSequence 活动。
事务性工作流要求持久性服务,如果没有提供这些服务,则会引发异常。您可以使用如下客户端应用程序中的工作流运行时注册一个持久性服务:
string conn = “...”; workflowRuntime.AddService( new SqlWorkflowPersistenceService(conn));
SqlWorkflowPersistenceService 类是默认的持久性服务,并以 SQL Server 数据库为基础。当 ACID 事务完成后,或当工作流实例处于空闲状态或以编程方式被卸载后,会发生持久性服务。该工作流的运行时引擎会调用关于持久性服务的方法,以保存工作流实例的状态。该工作流的运行时引擎会确定执行持久性服务的时间和方式。该服务会负责实际保存和加载到选择的数据存储,以及从选择的数据存储保存和加载的工作流状态的情况。
SqlWorkflowPersistenceService 类使用的数据库不会在安装的时候被创建,但是脚本会被复制到客户机上,以便将来使用。该脚本的路径是:
%WINDOWS%\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL
您可以更改数据库的默认名称。但是,您不能更改子表的结构。要使用不同的数据库布局,您需要自定义持久性服务。在其核心,自定义持久性服务是从 WorkflowPersistenceService 继承的一个类。
使用 Windows Workflow Foundation 建模业务流程
ACID 事务只是现实中工作流的一小部分。多个 ACID 事务通常合并到一个单一的工作流中,表示特定业务流程。因此,可能会出现事务性活动成功完成,但之后会在工作流的其他活动中引发异常的情况。在这种情况下,无法自动回滚。既然跟踪操作后面存在有资源管理器,并且如果需要则可以取消它们,因此在事务的上下文中回滚是有可能的。尽管工作流不完全支持事务性语义,但是它们仍然支持事务性活动。
当您使用 Windows Workflow Foundation 为业务流程建模时,您可能会有分散在整个工作流中各种各样的事务块。Windows Workflow Foundation 提供了两类事务性活动:ACID 和可补偿事务。ACID 事务完全可由 TransactionScope 活动表示。它们提交或回滚,并能保存其结果。但是,如果您需要在更大流程的上下文中实现事务性任务,而在这里您不能锁定数据太长时间,并且必须能回滚过去的提交,这时情况又会如何呢?针对这种情况,您需要一个特殊类型的事务活动,即 CompensatableTransactionScope 活动。
CompensatableTransactionScope 支持补偿机制。补偿是您在某一时刻运行的任何逻辑,可以撤消、减少或补偿前一操作的影响。关键在于可补偿事务可能包含子 ACID 事务,一旦提交便不能再回滚。但是,万一进一步还是失败了,它们的影响就必须以某种方式加以补偿。补偿类似回滚,只是开发人员会调用它来编写用于补偿已完成工作的代码。不是所有的事务性任务都必须补偿 — 例如,可以用 ACID 事务有效表示的任务就不需要补偿。实际上,TransactionScope 活动不支持补偿。
图 7 展示了使用几个可补偿事务作用域的工作流示例。业务流程为一个订单的生命周期建模。一旦完成,该订单便会引起客户的信用卡要支付资金(活动名称为 Scope_ChargeCreditCard),然后将资金转到供应商的银行帐户(活动名为 Scope_PayOrder)。这两个活动都是事务性和可补偿活动。例如由于订购的产品不再提供,也许后来就在工作流中引发了异常。这个时候,会发生业务异常,并且到此时为止,所有完成的工作现在都必须撤消。所有已提交的 ACID 事务的影响和活动的非事务性序列都必须获得补偿。每个可补偿事务或序列活动都合并了某个逻辑,而这个逻辑正好起到了撤消部分或全部工作的作用。

图 7 可补偿事务
在 Windows Workflow Foundation 中,您可通过将活动的视图切换成补偿处理程序视图,为每个可补偿活动定义撤消代码(见图 8)。在补偿处理程序视图中,您可列出必须运行以取消该操作影响的所有活动。
补偿的概念与回滚的概念类似。但是,回滚纯粹是事务性操作,而补偿则应用于操作的事务性和非事务性序列。如果您比较图 7 和图 8,就会发现,两个可补偿事务作用域的用户界面有些细微差异。在图 8 中,您看到的不是直接码,而是补偿码。
有一个好问题,那就是为什么要为补偿而费心?难道使用自动回滚的一个大型 ACID 事务还不够好吗?当操作在同一数据库内或同一信息系统内发生时,ACID 事务是最合适的。当操作迅速结束时,它也是最合适的。当涉及不同的公司和服务时,根据 ACID 语义去定义该流程通常都有难度。由于它是独立、持久的,因此您必须使不同公司的所有资源在任务期间保持锁定状态。这通常是不切实际的,尤其在任务持续时间比较长的情况下。由于它是一致和原子性的,因此您需要特殊补偿码。
另一个好问题就是谁触发了补偿码。使用错误处理,您可以处理一个或多个由工作流引发的异常。这类异常的处理程序是另一个特殊活动,即 Compensate 活动(见图 9)。Compensate 活动触发了修复被业务异常置于危险的约束和业务规则的代码。您需要将 Compensate 活动绑定到工作流的可补偿活动中。您可以通过 Visual Studio 2005 Workflow Extensions 设计程序中的 TargetActivityName 属性来完成上述操作。如果您将 TargetActivityName 属性设置成某个特定事务或序列的名称,则只有该事务或序列会得到补偿。您可以添加多个补偿活动,并确定应补偿的顺序和粒度。如果您正好想要运行模仿经典 ACID 事务的回滚逻辑的补偿逻辑,那么请将 Compensate 活动绑定到整个工作流中。在这种情况下,补偿会从异常点到工作流的根以自下向上的方式进行。

图 9 Compensate 活动
补偿码可能会启动其他 ACID 事务,以便平衡工作流的影响。此外,补偿也可能失败。因此,为安全起见,您最好还是为补偿码准备合适的异常处理程序。
对 Windows Workflow Foundation 工作流的性能具有最大影响的原因之一就是持久性。一个工作流可以具有很多持久化点;一部分由程序员明确定义,另一部分由内置和自定义的活动明确要求。当在工作流实例中调用了 Unload 方法时,或当带有 PersistOnClose 属性的活动完成其工作时,如果工作流空闲,则会自动调用持久性服务。在 Windows Workflow Foundation 定义的两个内置事务性活动(TransactionScope 和 CompensatableTransactionScope)都要求在关闭时具有持久性。因此,有效的持久性服务,以及更重要的有效运行时环境对性能而言至关重要。
就事务性工作流的运行时机制而言,事务性工作流具有持久性和事务支持两个要求。在默认情况下,DefaultWorkflowCommitWorkBatchService 服务类可以用来通过 .NET TransactionScope 类管理事务。这样,便不知不觉地实现了动态升级到 DTC,并且只在需要时才进行。但是,特殊方案仍会存在,在这种情况下,为性能起见,您不应使用默认服务。
当您使用现成的持久性服务时,需要创建一个合适的数据库,可以是 SQL Server 2005,也可以是 SQL Server 2000。标准的数据库还包括标准跟踪服务的表和存储过程 — SqlTrackingService 类。假设存在这样一种情况,两种服务都启用,并且共享同一个数据库,这就意味着您会使用完全相同的连接字符串。持久性和跟踪数据始终被写入同一事务内。如果数据库是 SQL Server 2000,虽然如此,但也会出现升级到 DTC 的情况,除非您设法使这些服务可以共享同一个连接对象。这样,当持久性和跟踪数据准备输入同一 SQL Server 2000 数据库时,您应使用 SharedConnectionWorkflowCommitWorkBatchService,而非默认服务。您可以将这些服务添加到工作流运行时,如下所示:
workflowRuntime.AddService( new SqlWorkflowPersistenceService(connString)); workflowRuntime.AddService( new SqlTrackingService(connString)); workflowRuntime.AddService( new SharedConnectionWorkflowCommitWorkBatchService(connString));
只有在这个特殊方案中,SharedConnectionWorkflowCommitWorkBatchService 服务才可以优化工作流性能,因为它省去了到数据库和 DTC 事务的额外连接的系统开销。如果您的事务处理工作流不需要跟踪,或者如果它使用不同的数据库用于跟踪和持久性,则您最好不要与用于事务支持的默认服务保持一致。
在边注中,我会指出跟踪服务和持久性服务始终是在同一个事务内进行操作。跟踪服务具有名为 IsTransactional 的布尔属性。将该属性设置成 false 不会使跟踪服务变成非事务服务。更简单的是,这意味着只要调用相应的方法,跟踪数据就会被及时保存。当 IsTransactional 为 true(默认设置)时,该服务的 TrackData 方法会将数据添加到工作批。那时,该工作批就会在工作流的下一个持久化点翻新。
Windows Workflow Foundation 中的建模事务性任务实际上非常简单。如果您需要会迅速生成响应的 ACID 事务 — 即原子性、一致、独立且持久的事务,您应使用 TransactionScope 活动。在该活动内部,您可以调整随底层环境事务一起登记的访问对象的其他活动。另一方面,如果您需要协调很多松散藕合的服务,以建模可能要需要一段时间才能完成的任务,那么您最好不要使用 CompensatableTransactionScope。
请不要忘记,由于必需的持久性和内存需求,事务会产生固有成本。如果您只要补偿,那么应该选择相对轻型的 CompensatableSequence 活动。









浙公网安备 33010602011771号