在 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
}
}
我觉得这样不对
谁应该发布活动?据我所知,UoW 应该在 Commit() 方法中这样做,但我认为这是不对的,我觉得 UoW 不应该那样做,但我看不出还有谁可以。
如果链中的某个点出现故障,我已经提交了一些数据,如果沿途出现故障,我很可能不想这样做。
那么这两种情况应该如何正确处理呢?
正如 Constantin 所述,每个命令只应更新一个聚合。为什么?因为通过在一个事务中更新多个聚合,你降低了系统的吞吐量。您的数据库事务边界越大,您在写入数据时面临争用的可能性就越大,因此您希望尽可能细化事务。
关于您的问题:
UnitOfWork 当然可以保存更改。或者,您的 Repo class 可以使用新方法 IRepo.Save(Aggregate):
来做同样的事情
_repo.Save(agg);
您的担心是有道理的。您可以(并且可能应该)将您的 UnitOfWork 移出 Handler/UseCaseHandler 级别的范围,这样您就没有在每个处理程序中提交 UoW 的样板代码。然后,您可以将两个聚合保存在一个数据库事务中。更好的方法是采用允许您从故障中恢复的架构。你可以:
- 处理命令并在事件存储中生成事件。 (UoW 会这样做)
- 轮询事件存储中的新事件并将它们发布到队列中。
- 从队列中读取事件并将它们分派给任何已注册的处理程序。
- 将新事件保存在事件存储中(再次使用 UoW),然后重复该过程。
如果上述任何步骤失败,处理将从该点开始并确保工作完成。使用这种方法,您的应用程序更具弹性并且事务边界得到正确应用。
在 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
}
}
我觉得这样不对
谁应该发布活动?据我所知,UoW 应该在 Commit() 方法中这样做,但我认为这是不对的,我觉得 UoW 不应该那样做,但我看不出还有谁可以。
如果链中的某个点出现故障,我已经提交了一些数据,如果沿途出现故障,我很可能不想这样做。
那么这两种情况应该如何正确处理呢?
正如 Constantin 所述,每个命令只应更新一个聚合。为什么?因为通过在一个事务中更新多个聚合,你降低了系统的吞吐量。您的数据库事务边界越大,您在写入数据时面临争用的可能性就越大,因此您希望尽可能细化事务。
关于您的问题:
UnitOfWork 当然可以保存更改。或者,您的 Repo class 可以使用新方法 IRepo.Save(Aggregate):
来做同样的事情_repo.Save(agg);
您的担心是有道理的。您可以(并且可能应该)将您的 UnitOfWork 移出 Handler/UseCaseHandler 级别的范围,这样您就没有在每个处理程序中提交 UoW 的样板代码。然后,您可以将两个聚合保存在一个数据库事务中。更好的方法是采用允许您从故障中恢复的架构。你可以:
- 处理命令并在事件存储中生成事件。 (UoW 会这样做)
- 轮询事件存储中的新事件并将它们发布到队列中。
- 从队列中读取事件并将它们分派给任何已注册的处理程序。
- 将新事件保存在事件存储中(再次使用 UoW),然后重复该过程。
如果上述任何步骤失败,处理将从该点开始并确保工作完成。使用这种方法,您的应用程序更具弹性并且事务边界得到正确应用。