为什么聚合中的每个数据更改都应该在事务中?

时间:2017-09-06 21:23:46

标签: transactions domain-driven-design

给定Order聚合,它由Order和OrderItems组成。订单是聚合根。下订单是唯一需要事务以实现数据一致性的方案。或者至少有很多场景涉及按顺序更改数据和不需要事务的orderItems。

在DDD之前,事务是在创建方法期间做出的决定。您通常会考虑此方法或行为是否需要在事务中以保持一致性。

DDD建议为聚合根公开的每个可能行为进行交易。这实际上可能导致更多的死锁。

我认为我不必在聚合根中实现每个数据更改行为都在事务中。

例如,如果您只更改运送详细信息,为什么还需要交易。如果您只删除订单商品,为什么还需要交易。因此对于方法Order.UpdateOrder和Order.DeleteOrderItem,我们不应该考虑事务。

我错过了什么吗?

3 个答案:

答案 0 :(得分:0)

是的,我想你错过了什么。如果你正确地设计你的聚合,特别是如果你把它们做得足够小,那么你就不会有无意义的交易,至少不会有很多交易。您的聚合应具有高度内聚性,因此同时修改的属性/状态应保持在同一聚合中;这是聚合点,用于保护对状态的并发访问。

如果您的聚合比它们应该更大,那么聚合force-field太宽而且没有内聚的命令会被无序序列化。

关于orders,不会有很多失败的交易,因为添加order item并同时更改order shipping details的可能性非常低。

无论如何,由于任何情况下的交易都不会发生死锁,但可能会发生交易失败。

答案 1 :(得分:0)

在最纯粹的形式中,您的存储库只有很少的方法:

public interface IAggregateRepository
{
    AggregateRoot Get(Guid id);
    void Save(AggregateRoot instance);
}

此方案中的用例/命令之间没有区别。您不一定知道是否需要交易。

另一个考虑因素是应该在集成/应用程序中处理事务。这使得事务处理进一步远离域交互。

可以让应用程序了解特定用例,并决定放弃某些命令的事务。但是,这可能是值得的更多努力。

最后一点是,交易并不总是只关于正在做什么,而是关于其他正在做什么。

我会谨慎行事,而是使用交易。如果有一个特定的理由不使用交易,那么这当然是一种选择,但这似乎更适合读取。

答案 2 :(得分:0)

如果您需要在系统中的某个位置仅更新运输详细信息,那么您可能会使用不同的聚合来使用不同的有界上下文,让我们将其称为ShippingRequest,只有两个字段OrderIdShippingDetails,最终可能会或可能不会映射到同一个基础表。然后,这个聚合由事务隐式覆盖,因为它只映射到一个表。

或者你可能不想关心所有这些有限的上下文,无处不在的语言和聚合,因为DDD不值得实现,而且一些传统的CRUD /事务脚本方法就足够了。

问题还在于,DDD经常被解释为像这样的错误例子。 Aggregate定义了一些一致性边界,一些代表业务规则的不变量。因此,在订单作为汇总的情况下,让我们说订单被分配了最大允许总数,不得超过该总数。比这变成了自然聚合,因为无论何时想要添加或删除项目,都必须检索整个聚合并将其更新为单个原子单元。