65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.66/5 (22投票s)

2010年7月15日

CPOL

7分钟阅读

viewsIcon

68520

服务器端服务的通用事务和异常管理器

Previous Next
第三章 第五章

Sharpy 项目

我目前正在为一个 Metro 应用程序开发一个新项目:Sharpy。我打算在 WCF 示例文章中讨论的模式用于 Sharpy 应用程序的服务端;目标是演示 Metro 应用程序的开发与我们迄今为止看到的应用程序类型有多么相似。目前还没有代码,但希望很快会有所改变。希望您喜欢。 文章在这里

系列文章

WCF 示例是一系列文章,描述了如何设计和开发使用 WCF 进行通信和 NHibernate 进行持久化的 WPF 客户端。 系列引言 描述了文章的范围,并从高层次讨论了架构解决方案。

章节概述

在“第一章 - 基线”中,定义了 CustomerService 的草稿版本。在“第三章 - 响应”中,对服务进行了重构,以便业务警告和异常始终可以在客户端获得。重构包括为我们服务中使用的 DTO 提供一些附加功能(IDtoResponse)。但我们没有涵盖服务如何管理业务警告和异常。

在本章中,我们将讨论需要一个中央管理组件来以透明的方式处理消息,而对我们的服务来说是透明的。还需要管理业务事务,我们将看到这两个需求如何在同一个地方得到满足。

本章将介绍几个类和接口。事务管理器定义在 Domain 程序集中,我们服务的基类也声明在此程序集中。

我们还将创建内存中 Naive 程序集中的实现。

本章的源代码可以在 Codeplex 更改集 67474 中找到。 eDirectory 解决方案的最新代码可以在 Codeplex 中找到。

事务管理器

服务必须在事务中执行,因此如果抛出异常,我们可以回滚所有更改以确保数据完整性。事务管理器公开了一个非常基础的接口来满足此要求。

值得注意的是,我们的接口实现了 IDispose 接口,这将提供一种简洁的方式来确保事务得到妥善管理。“ExecuteCommand”泛型方法返回 IDtoResponse 接口的实例;需要传递一个需要 RepositoryLocator 实例并返回 IDtoResponse 接口实例的函数给该方法。

TResult ExecuteCommand<TResult>
    (Func<IRepositoryLocator, TResult> command)
    where TResult : class, IDtoResponseEnvelop;

我们将创建 ITransManager 接口的两个实现,一个用于 NHibernate,另一个用于内存实现。本质上,内存实现将不支持事务。尽管某些功能对于这两个实现都是通用的,因此可以声明一个基类。

这个基类中有几个有趣的方面值得更详细的讨论。首先,正如我们提到的,IDispose 模式为我们的事务设计提供了一个简洁的解决方案;其次,“ExecuteCommand”方法是我们设计中的关键,让我们来看看它们。

#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 代码的好地方。

事务工厂

工厂的主要作用是在创建新管理器时“注入”一个 repositorylocator 实例。然后,管理器负责将 repositorylocator 传递给调用的命令,但它不负责创建它。此方法非常符合 NHibernate 指南。我们稍后将描述的简单实现与 NHibernate 实现不同,因此在稍后的章节中涵盖 NHibernate 实现之前,我们不会完全理解此模式。

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 表达式非常方便。

重构前的 Customer Service

重构后的 Customer Service

为了清晰和维护的目的,将命令逻辑移到另一个 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 实例。在我们的实现中,当创建管理器时,工厂会指示将使用哪个存储库定位器,而无需我们的服务参与其中。这是我们设计的一大改进。

下一章将介绍一种新型的定位器(或者我们最好称之为管理器)。我们需要一种机制,以便服务能够轻松地获取工厂的实例。

© . All rights reserved.