在 DDD 的上下文中应该如何处理事务

How should Transactions be handled in the context of DDD

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

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

正如 Constantin 所述,每个命令只应更新一个聚合。为什么?因为通过在一个事务中更新多个聚合,你降低了系统的吞吐量。您的数据库事务边界越大,您在写入数据时面临争用的可能性就越大,因此您希望尽可能细化事务。

关于您的问题:

  1. UnitOfWork 当然可以保存更改。或者,您的 Repo class 可以使用新方法 IRepo.Save(Aggregate):

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

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

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