WCF 示例 – 第四章 – 事务管理器






4.66/5 (22投票s)
服务器端服务的通用事务和异常管理器
![]() |
![]() |
|
第三章 | 第五章 |
Sharpy 项目
![]() |
我目前正在为一个 Metro 应用程序开发一个新项目:Sharpy。我打算在 WCF 示例文章中讨论的模式用于 Sharpy 应用程序的服务端;目标是演示 Metro 应用程序的开发与我们迄今为止看到的应用程序类型有多么相似。目前还没有代码,但希望很快会有所改变。希望您喜欢。 文章在这里。 |
系列文章
WCF 示例是一系列文章,描述了如何设计和开发使用 WCF 进行通信和 NHibernate 进行持久化的 WPF 客户端。 系列引言 描述了文章的范围,并从高层次讨论了架构解决方案。
章节概述
在“第一章 - 基线”中,定义了 在本章中,我们将讨论需要一个中央管理组件来以透明的方式处理消息,而对我们的服务来说是透明的。还需要管理业务事务,我们将看到这两个需求如何在同一个地方得到满足。 本章将介绍几个类和接口。事务管理器定义在 Domain 程序集中,我们服务的基类也声明在此程序集中。 我们还将创建内存中 Naive 程序集中的实现。 本章的源代码可以在 Codeplex 更改集 67474 中找到。 |
![]() |
事务管理器
服务必须在事务中执行,因此如果抛出异常,我们可以回滚所有更改以确保数据完整性。事务管理器公开了一个非常基础的接口来满足此要求。
![]() |
值得注意的是,我们的接口实现了 TResult ExecuteCommand<TResult>
(Func<IRepositoryLocator, TResult> command)
where TResult : class, IDtoResponseEnvelop;
|
我们将创建 ITransManager
接口的两个实现,一个用于 NHibernate,另一个用于内存实现。本质上,内存实现将不支持事务。尽管某些功能对于这两个实现都是通用的,因此可以声明一个基类。
![]() |
这个基类中有几个有趣的方面值得更详细的讨论。首先,正如我们提到的, |
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected bool IsDisposed = false;
protected virtual void Dispose(bool disposing)
{
if (!disposing) return;
// free managed resources
if (!IsDisposed && IsInTranx)
{
Rollback();
}
Locator = null;
IsDisposed = true;
}
#endregion
因此,我们的 Dispose 实现确保在事务阶段的实例调用 dispose
方法时回滚事务。我们稍后将看到此设计如何为使用我们的事务管理器实例提供一种方便的方式。
public abstract class TransManagerBase
:ITransManager
{
protected bool IsInTranx;
public IRepositoryLocator Locator { get; set; }
#region ITransManager Members
public TResult ExecuteCommand<TResult>
(Func<Repository.IRepositoryLocator, TResult> command)
where TResult : class, Common.Message.IDtoResponseEnvelop
{
try
{
BeginTransaction();
var result = command.Invoke(Locator);
CommitTransaction();
CheckForWarnings(result);
return result;
}
catch (BusinessException exception)
{
if (IsInTranx) Rollback();
var type = typeof(TResult);
var instance = Activator.CreateInstance
(type, true) as IDtoResponseEnvelop;
if (instance != null) instance.Response.AddBusinessException(exception);
return instance as TResult;
}
catch (Exception e)
{
throw;
}
}
...
#endregion
}
几行代码中发生了许多事情。在正常情况下
- 启动事务
- 事务管理器将 RepositoryLocator 的实例传递给给定的函数
- 调用函数并存储结果
- 此时事务已提交
- 然后我们检查警告(稍后章节将详细介绍此方法)
- 结果返回给调用实例
如果在调用给定的函数时抛出业务异常
- 如有必要,回滚事务
- 创建传递类型的最后一个实例
- 代码返回带有业务异常详细信息的“空白”实例
最后,如果在调用命令时抛出其他类型的异常,则会有一个 catch
异常部分。在当前实现中,我们不做任何事情,异常会被重新抛出。这可能是一个放置少量 Log4Net 代码的好地方。
事务工厂
![]() |
工厂的主要作用是在创建新管理器时“注入”一个 |
ServiceBase 类
此时,我们已准备好在服务中使用我们新引入的类。到目前为止,我们的服务不是事务性的,它们还持有一个 RepositoryLocator
实例。
public class CustomerService
:ICustomerService
{
public IRepositoryLocator Repository { get; set; }
#region ICustomerService Members
public CustomerDto CreateNewCustomer(CustomerDto dto)
{
var customer = Customer.Create(Repository, dto);
return Customer_to_Dto(customer);
}
...
#endregion
}
服务持有 RepositoryLocator
实例的事实可能会在我们的应用程序尝试管理后端数据库事务时引起麻烦。这就是我们新的 TransFactory
类成为绝佳解决方案的地方。
首先,创建一个新的 abstract
类,它为我们所有的服务提供通用功能,这就是 ServiceBase
类。
public class ServiceBase
{
public ITransFactory Factory { get; set; }
protected TResult ExecuteCommand<TResult>
(Func<IRepositoryLocator, TResult> command)
where TResult : class, IDtoResponseEnvelop
{
using (ITransManager manager = Factory.CreateManager())
{
return manager.ExecuteCommand(command);
}
}
}
在四行代码中,我们有很多好的、有趣的事情在发生。您可能认识 ExecuteCommand
的签名,它与我们新朋友 TransManagerBase
类中同名的方法完全相同。正如我们之前提到的,TransManager
实现的 Dispose
模式在我们应用“using
”语句时,使我们的代码非常整洁。请记住,工厂创建管理器并“注入”一个 repositorylocator
实例,以便在管理器调用命令时可以使用它。
本章有一些对理解应用程序设计至关重要的内容。您可能想花一些时间调试本章的测试,以充分理解这里正在发生的事情。
此时,我们需要重构 CustomerService
。我们希望此服务继承自新的 abstract
类,并删除 RepositoryLocator
实例。我们将使用“ExecuteCommand
”基类方法重构服务方法;此时,匿名方法和 lambda 表达式非常方便。
为了清晰和维护的目的,将命令逻辑移到另一个 private
方法是一个好主意。这种方法便于在 Visual Studio 中调试这些表达式,因为在匿名方法上设置断点可能会很棘手。所以我们的服务方法最终如下所示。
![]() |
public class CustomerService
:ServiceBase, ICustomerService
{
#region ICustomerService Members
public CustomerDto CreateNewCustomer
(CustomerDto dto)
{
return ExecuteCommand(locator =>
CreateNewCustomerCommand(locator, dto));
}
private CustomerDto CreateNewCustomerCommand
(IRepositoryLocator locator, CustomerDto dto)
{
var customer = Customer.Create(locator, dto);
return Customer_to_Dto(customer);
}
...
#endregion
}
|
内存实现
目前,我们只实现事务管理器和工厂的内存实现。这些实现中有几个方面不同,内存事务管理器不具备事务功能,这与其类名有些讽刺。它的作用只是管理警告和异常。此实现的另一个方面是工厂始终返回相同的事务管理器实例。这是通过覆盖基类中的 Dispose
方法来实现的,因此不会终止 Locator。
public class TransManagerEntityStore
: TransManagerBase
{
#region Overrides of TransManager
/// Need to override this method because
/// we want to keep the transmanager in the
/// Entity Store implementation as instances
/// are stored in memory
protected override void Dispose(bool disposing)
{
if (!disposing) return;
// free managed resources
if (!IsDisposed && IsInTranx)
{
Rollback();
}
//Locator = null;
IsDisposed = true;
}
#endregion
}
正如我们提到的,事务管理器工厂的内存实现返回相同的 TransManager
实例。
public class TransManagerEntityStoreFactory
: ITransFactory
{
private TransManagerEntityStore TransManager;
#region Implementation of ITransFactory
public ITransManager CreateManager()
{
if (TransManager != null) return TransManager;
TransManager = new TransManagerEntityStore
{ Locator = new RepositoryLocatorEntityStore() };
return TransManager;
}
#endregion
}
测试
作为我们服务方法重构的结果,我们的测试需要进行一些修改。过去,测试会创建一个服务实例并设置一个 RepositoryLocator
,现在我们需要实例化一个事务管理器工厂。
[TestClass]
public class CustomerServiceTests
{
public CustomerService Service { get; set; }
public CustomerDto CustomerInstance { get; set; }
[TestInitialize()]
public void CustomerServiceTestsInitialize()
{
Service = new CustomerService
{ Factory = new TransManagerEntityStoreFactory() };
}
...
}
章节总结
本章我们在设计上做出了一个根本性的改变。服务过去持有 RepositoryLocator
实例,但现在服务不再了解存储库。
服务仅需要一个提供事务管理器的 Factory
实例。在我们的实现中,当创建管理器时,工厂会指示将使用哪个存储库定位器,而无需我们的服务参与其中。这是我们设计的一大改进。
下一章将介绍一种新型的定位器(或者我们最好称之为管理器)。我们需要一种机制,以便服务能够轻松地获取工厂的实例。