我是DDD的新手,所以我只是了解它的基本概念。有人可以指导我更多地介绍DDD中的聚合对象吗?特别是,为什么要使用“聚合”对象,以及在设计聚合时将应用哪些关键原则?
谢谢,
最佳答案
让我们从头开始。很久以前,在一个遥远的星系中,有一个带有ACID transactions的SQL数据库。我们真正感兴趣的是ACID首字母缩略词的原子性和一致性。例如,如果您有两个更改a1 -> a2
和b1 -> b2
,并且在这样的事务中进行更改,则这些更改必须是原子的,并且您将只有两个有效的状态选项:a1, b1
和a2, b2
。因此,如果a1 -> a2
更改失败,则整个事务都会失败。这称为立即一致性。
相反,不存在不符合ACID的SQL数据库。通过这些数据库,您的更改不是原子的,您可以具有多个状态:a1, b1
,a2, b1
,a1, b2
,a2, b2
,具体取决于更改的顺序或更改失败。这称为最终一致性。
如果您的分布式系统具有涉及多台计算机的复杂更改,则有两个选择。您可以使用最终的一致性,这将非常快,但是数据在多台计算机上将不一致。或者,您可以使用与2 phase commit的即时一致性,并且更新将非常慢,但是计算机之间的数据将保持一致。
用DDD表示,聚合是一个一致性边界:内部会立即更改,外部会最终更改。坚持同一示例,您想使用命令更改两个内容:a1 -> a2
和b1 -> b2
。如果这些内容在同一汇总x
中,则可以在同一事务中更改它们:(x.a1, x.b1) -> (x.a2, x.b2)
使用立即一致性。如果这些内容在不同的汇总中:x
,y
,则无法在同一事务中更改它们,因此必须使用最终的一致性:x.a1 -> x.a2
和y.b1 -> y.b2
是将要提交的两个事务彼此独立。
根据弗农的书,DDD有一条规则。您不能在单个交易中更改多个汇总。如果这样做,则可能是代码臭味,这是不同地选择一致性边界的标志,或者换句话说,是不同地设计聚合。
因此,通过设计聚合,您必须牢记这些一致性边界。如果不这样做,将导致并发问题。例如,您在汇总a
上具有b
和x
属性。这些属性彼此独立,因此(x.a1, x.b2)
和(x.a2, x.b1)
是有效状态。如果John想要在两个并发请求中更改x.a1 -> x.a2
,而Jane想要更改x.b1 -> x.b2
,则尽管有以下两种情况,但其中一个请求将失败:(x.a1, x.b1) -> (x.a2, x.b1) -> (x.a2, x.b2)
和(x.a1, x.b1) -> (x.a1, x.b2) -> (x.a2, x.b2)
将导致相同的状态(x.a2, x.b2)
并将y
属性移到其中来解决此问题。因此,在两个事务中的更改将分别为b
和x.a1 -> x.a2
,这不会造成麻烦。
如果您有两个聚合y.b1 -> y.b2
和x
并且属性y
和x.a
不能独立更改,则为反示例。因此y.b
和x.a1, y.b2
状态无效。这是将这两个聚合合并为一个并使用立即一致性而不是最终一致性的标志。
您的系统很有可能在多台计算机上运行。较大的组成部分(如有限上下文)将最终保持一致,而较小的组成部分(如值对象,实体)将立即保持一致。因此,您可以在没有分布式事务和两阶段提交的情况下在多台计算机上部署有限的上下文,这将产生一个快速而可靠的系统。另一方面,由于您使用聚合,聚合只能具有有效状态。
请注意,我不是该主题的专家,我只是读了一本书。 =]
1年后:
我发现一个非常good article about aggregates。根据它,您应该在不变量周围设置一致性边界,以防止违反合同。因此,如果您有不变量的列表,则可以使用它们来定义一致性边界。总体边界将相似。理想情况下,它们包括每个不变式,但是如果它们变得太大,则会导致太多的并发异常,因此在复杂的情况下,它们不能包括某些不变式。
关于java - 在域驱动的设计中,为什么要使用“聚合”对象?在设计聚合时将应用哪些关键原则?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34662578/