存储库模式中的事务

时间:2009-02-22 16:10:36

标签: design-patterns domain-driven-design repository-pattern architectural-patterns

如何使用存储库模式以事务方式封装多个实体的保存?例如,如果我想根据订单创建添加订单并更新客户状态,但只有在订单成功完成时才会这样做?请记住,对于此示例,订单不是客户内的集合。他们是他们自己的实体。

这只是一个人为的例子,所以我并不关心订单是否应该在客户对象内部,甚至不在同一个有限的上下文中。我真的不在乎将使用什么底层技术(nHibernate,EF,ADO.Net,Linq等)。我只是想看看一些调用代码在这个公认的全有或全无操作示例中的样子。

6 个答案:

答案 0 :(得分:13)

今天早上启动我的电脑我遇到了正在处理的项目的确切问题。我有一些想法导致以下设计 - 评论将超过令人敬畏。遗憾的是,Josh建议的设计是不可能的,因为我必须使用远程SQL服务器,并且无法启用它所依赖的Distribute Transaction Coordinator服务。

我的解决方案基于对现有代码的一些简单更改。

首先,我的所有存储库都实现了一个简单的标记接口:

/// <summary>
/// A base interface for all repositories to implement.
/// </summary>
public interface IRepository
{ }

其次,我让所有启用事务的存储库实现以下接口:

/// <summary>
/// Provides methods to enable transaction support.
/// </summary>
public interface IHasTransactions : IRepository
{
    /// <summary>
    /// Initiates a transaction scope.
    /// </summary>
    void BeginTransaction();

    /// <summary>
    /// Executes the transaction.
    /// </summary>
    void CommitTransaction();
}

我的想法是,在我的所有存储库中,我实现了这个接口,并添加了直接引入事务的代码,具体取决于实际的提供程序(对于伪造的存储库,我已经创建了一个在提交时执行的委托列表)。对于LINQ to SQL,可以很容易地进行实现,例如:

#region IHasTransactions Members

public void BeginTransaction()
{
    _db.Transaction = _db.Connection.BeginTransaction();
}

public void CommitTransaction()
{
    _db.Transaction.Commit();
}

#endregion

这当然要求为每个线程创建一个新的存储库类,但这对我的项目来说是合理的。

如果存储库实现BeginTransaction(),则使用存储库的每个方法都需要调用EndTransaction()IHasTransactions。为了使这个电话更容易,我提出了以下扩展:

/// <summary>
/// Extensions for spawning and subsequently executing a transaction.
/// </summary>
public static class TransactionExtensions
{
    /// <summary>
    /// Begins a transaction if the repository implements <see cref="IHasTransactions"/>.
    /// </summary>
    /// <param name="repository"></param>
    public static void BeginTransaction(this IRepository repository)
    {
        var transactionSupport = repository as IHasTransactions;
        if (transactionSupport != null)
        {
            transactionSupport.BeginTransaction();
        }
    }

    public static void CommitTransaction(this IRepository repository)
    {
        var transactionSupport = repository as IHasTransactions;
        if (transactionSupport != null)
        {
            transactionSupport.CommitTransaction();
        }
    }
}

赞赏评论!

答案 1 :(得分:7)

我会考虑使用某种类型的Transaction Scope / Context系统。所以你可能有以下代码,大致基于.Net&amp; C#。

public class OrderService
{

public void CreateNewOrder(Order order, Customer customer)
{
  //Set up our transactional boundary.
  using (TransactionScope ts=new TransactionScope())
  {
    IOrderRepository orderRepos=GetOrderRespository();
    orderRepos.SaveNew(order);
    customer.Status=CustomerStatus.OrderPlaced;

    ICustomerRepository customerRepository=GetCustomerRepository();
    customerRepository.Save(customer)
    ts.Commit();   
   }
}
}

TransactionScope可以嵌套,所以假设您有一个跨越多个服务的操作,您的应用程序也会创建一个TransactionScope。现在在当前的.net中,如果您使用TransactionScope,他们就有可能冒险进入DTC,但这将在未来得到解决。

我们创建了自己的TransactionScope类,它基本上管理了我们的数据库连接并使用了本地SQL事务。

答案 2 :(得分:5)

使用Spring.NET AOP + NHibernate,您可以正常编写存储库类,并在自定义XML文件中配置事务:

public class CustomerService : ICustomerService
{
    private readonly ICustomerRepository _customerRepository;
    private readonly IOrderRepository _orderRepository;

    public CustomerService(
        ICustomerRepository customerRepository, 
        IOrderRepository orderRepository) 
    {
        _customerRepository = customerRepository;
        _orderRepository = orderRepository;
    }

    public int CreateOrder(Order o, Customer c) 
    {
        // Do something with _customerRepository and _orderRepository
    }
}

在XML文件中,您可以选择要在事务中执行的方法:

  <object id="TxProxyConfigurationTemplate" 
          abstract="true"
          type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data">

    <property name="PlatformTransactionManager" ref="HibernateTransactionManager"/>

    <property name="TransactionAttributes">
      <name-values>
        <add key="Create*" value="PROPAGATION_REQUIRED"/>
      </name-values>
    </property>
  </object>

  <object id="customerService" parent="TxProxyConfigurationTemplate">
    <property name="Target">
      <object type="MyNamespace.CustomerService, HibernateTest">
          <constructor-arg name="customerRepository" ref="customerRepository" />
          <constructor-arg name="orderRepository" ref="orderRepository" />
      </object>
    </property>

  </object>

在您的代码中,您将获得CustomerService类的实例,如下所示:

ICustomerService customerService = (ICustomerService)ContextRegistry
    .GetContent()
    .GetObject("customerService");

Spring.NET将返回CustomerService类的代理,该类将在调用CreateOrder方法时应用事务。这样,服务类中就没有特定于事务的代码。 AOP照顾它。有关详细信息,请查看Spring.NET的文档。

答案 3 :(得分:3)

您希望了解实施工作单元模式。有NHibernate的实现。一个是在Rhino Commons项目中,还有Machine.UoW。

答案 4 :(得分:3)

  

如何封装保存   一个以上的实体   交易方式使用   存储库模式?例如,什么   如果我想添加订单并更新   基于此的客户状态   订单创建,但只有这样做   订单成功完成?留在   请注意,对于这个例子,订单是   不是客户内部的集合。   他们是他们自己的实体。

它不是存储库的责任,它通常是在更高级别完成的。虽然你说你对特定技术不感兴趣,但我认为它值得限制解决方案,例如当NHibernate与Web应用程序一起使用时,你可能会考虑使用session-per request

因此,如果您可以在更高级别管理交易,那么我的两个选项将是:

  1. 提前检查 - 例如,在协调行为的服务中,通过询问订单/客户来决定是否要继续,如果要么他们不这样做,那么甚至不尝试更新其中任何一个。
  2. 回滚 - 只需继续更新客户/订单,如果事情无法通过回滚数据库事务而失败。
  3. 如果您选择第二个选项,那么问题是内存中对象会发生什么,您的客户可能会处于不一致状态。如果这很重要,而且我工作的情况不是因为对象只是为那个请求加载了,那么我会考虑前期检查是否可能,因为它比替代方案容易得多(回滚过去) -memory更改或重新加载对象。)

答案 5 :(得分:0)

可以将事务参数添加到要在事务中运行的方法的末尾,并将其默认值设置为null。因此,如果您不想在现有事务中运行该方法,则不要使用end参数或显式传递null。

在这些方法中,您可以检查参数是否为null以确定是否创建新事务或使用传入的事务。例如,可以将此逻辑推送到基类。

这使您的方法比使用基于上下文的解决方案更纯净,尽管后者对于通用库可能更好。但是,在一个独立的应用程序中,您知道在事务内需要链接哪些方法,并且不会全部被使用。

void Update(int itemId, string text, IDbTransaction trans = null) =>
   RunInTransaction(ref trans, () =>
   {
      trans.Connection.Update("...");
   });

void RunInTransaction(ref IDbTransaction transaction, Action f)
{
    if (transaction == null)
    {
        using (var conn = DatabaseConnectionFactory.Create())
        {
            conn.Open();

            using (transaction = conn.BeginTransaction())
            {
                f();

                transaction.Commit();
            }
        }
    }
    else
    {
        f();
    }
}

Update(1, "Hello World!");
Update(1, "Hello World!", transaction);

然后,您可以为服务层设置事务处理程序...

public void RunInTransaction(Action<IDbTransaction> f)
{
   using (var conn = DatabaseConnectionFactory.Create())
   {
      conn.Open();

      using (var transaction = conn.BeginTransaction())
      {
         f(transaction);

         transaction.Commit();
      }
   }
}

一种服务方法可能看起来像这样...

void MyServiceMethod(int itemId, string text1, string text2) =>
   transactionRunner.RunInTransaction(trans =>
   {
      repos.UpdateSomething(itemId, text1, trans);
      repos.UpdateSomethingElse(itemId, text2, trans);
   });

哪个很容易模拟用于单元测试...

public class MockTransactionRunner : ITransactionRunner
{
   public void RunInTransaction(Action<IDbTransaction> f) => f(null);
}