【问题标题】:How should Transactions be handled in the context of DDD在 DDD 的上下文中应该如何处理事务
【发布时间】:2018-11-05 16:52:21
【问题描述】:

在 DDD 的上下文中,当您处理域事件时,事务应该在哪里开始和结束?

Infrastructure.Layer 有 UoW 实现

/// => configured via DI as Per Request
class UnitOfWork : IUnitOfWork
{
     ITransaction _transaction;
     static ISessionFactory _factory; 
     ISession _session

     UnitOfWork()
     {
         _session = _factory.OpenSession();
         _transaction = _session.BeginTransaction();  ///=> start transaction 
     }

     void Commit()
     {
          try
             _transaction.Commit();
          catch
            _transaction.Rollback(); 
          finally
            Dispose();
     } 
}

Application.Layer 用例处理程序

class SomeAppServiceUseCaseHandler : IUseCaseHandler
{
      IUnitOfWork _uow;
      ISomeRepo _repo;

      AppService(IUnitOfWork uow, ISomeRepo repo)
      {
          _uow = uow;
          _repo = repo;
      }

      void UseCaseHandler(Request request)
      {

         SomeAggregate agg = _repo.GetAggregate(request.Id) 

                       agg.DoSomethingToChangeState();

         _repo.UpdateAgg(agg);

         _uow.Commit(agg);  ///=> commit changes for this transaction success
      }
}

并且在 Domain.Layer 中,该方法还将 Domain.Event 添加到聚合的域事件列表中。

SomeAggregate : AggregateRoot
{
   DoSomethingToChangeState()
   {
       .... do something

       var someObject;
       base.AddEvent(new SomethingHappenedEvent(someObject)));
   }
}

Application.Layer 具有 Domain.Event 处理程序

class SomethingHappenedEventHander : Handler<SomethingHappenedEvent>
{
    IRepo repo;
    IUnitOfWork _uow;

    DomainEventHander(IRepo repo, IUnitOfWork uow)
    {
        _repo = repo;
        _uow= uow;
    }

    HandleEvent(someObject)
    {
         AnotherAggregate agg = new AnotherAggregate ();
                          agg.DoSomeCommand(someObject);

         _repo.Create(agg);
         _uow.Commit();  ///=> commit changes for same transaction fail, should rollback prev transaction as well
    }
}

我觉得这不对

  1. 谁应该发布活动?从我所见,UoW 应该在 Commit() 方法中这样做,但我认为这是不对的,我认为 UoW 不应该这样做,但我看不出还有谁可以。

  2. 如果在链中的某个点出现故障,我已经提交了一些数据,如果沿途出现故障,我很可能不想这样做。

那么这两种情况应该如何正确处理呢?

【问题讨论】:

  • DDD 规定每个事务只能修改一个聚合。
  • 是的,确实如此,但是应该如何处理多个聚合更新呢?我认为我不应该在 Application.Layer UseCaseHandler 中开始事务
  • 在 Saga/流程管理器中。如果您需要回滚聚合,则边界有问题,或者您需要将其建模为业务流程

标签: architecture transactions domain-driven-design software-design unit-of-work


【解决方案1】:

正如康斯坦丁所说,每个命令只能更新一个聚合。为什么?因为通过在事务中更新多个聚合,您会降低系统的吞吐量。您的数据库事务边界越大,您在写入数据时就越有可能面临争用,因此您希望尽可能地保持事务的细粒度。

关于您的问题:

  1. UnitOfWork 当然可以保存更改。或者,您的 Repo 类可以使用新方法 IRepo.Save(Aggregate) 执行相同操作:

    _repo.Save(agg);
    
  2. 您的担忧是有效的。您可以(并且可能应该)将您的 UnitOfWork 移出 Handler/UseCaseHandler 级别的范围,这样您就没有在每个处理程序中提交 UoW 的样板代码。然后,您可以将两个聚合保存在一个数据库事务中。更好的方法是采用一种允许您从故障中恢复的架构。你可以:

    1. 处理命令并在事件存储中生成事件。 (UoW 会这样做)
    2. 轮询事件存储中的新事件并将它们发布到队列中。
    3. 从队列中读取事件并将它们分派给任何注册的处理程序。
    4. 在事件存储中保存新事件(同样使用 UoW),然后重复该过程。

    如果上述任何步骤失败,处理将从该点开始并确保工作完成。使用这种方法,您的应用程序更具弹性,并且可以正确应用事务边界。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-14
    • 2017-10-02
    • 1970-01-01
    • 2012-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多