1.2.3.2 AGGREGATES 聚合
在我们的个人银行模型中,正如您在前面看到的那样,一个Account(帐户)可以被认为是由一组相关的对象组成的。通常,这包括以下内容:
核心识别帐户的属性,如账号。
各种非识别的属性,如持有者的姓名、帐户的开户日期和销户日期(如果是一个已经销户的帐户)
到其他对象的引用,比如Address和Bank
一种可以可视化整个对象的图形的方法是把它想象成一个内在的一致性边界。当您有一个帐户被实例化了,所有的这些参与的对象和属性必须与领域的每一个业务规则相一致。比如说你的销户日期不能比开户日期还早。你不能有一个没有持有者姓名的账户。这些都是有效的业务规则,实例化的Account(帐户)必须让所有的组合对象都遵守这些规则。当您在这个图中识别出这一组参与对象之后,这个图将成为一个aggregate(聚合)。一个聚合可以包括一个或者多个的实体和值对象(以及其他的私有属性)。除了确保业务规则的一致性之外,在一个限界上下文中还常常将一个聚合作为模型中的事务边界。
聚合内的一个实体构成了Aggregate Root(聚合根)。它是整个图的守护者,作为聚合与客户交互的单一点。聚合根有两个目标要执行:
在聚合内确保业务规则和事务的一致性边界
防止聚合的实现泄漏到客户端,作为聚合所支持的所有操作的一个门面(facade)。
清单1.2展示了Scala中一个Account聚合的设计。它包含聚合根Account(它也是一个是一个实体),并且还有像Bank的实体和Address这样的值对象,来作为它的组成部分(作者在这里做了注解:在实际中,当你设计聚合时,你可能会出于对于性能和操作的一致性,而从聚合中优化许多组合实体,只保留聚合根和值对象在一起。比如你可能选择去持有一个Bank的ID,而不是去持有一整个Bank实体去作为Account聚合的一部分)。设计一个聚合并不是一个简单的任务,看一看Vaughn Vernon的“Effective Aggregate Design(有效的聚合设计)”这篇文章,从三部分讨论了设计一个好的聚合体所需要的各种注意事项(地址是:http://dddcommunity.org/library/vernon\_2011/)
trait Account {
def no: String
def name: String
def bank: Bank ----
>
引用到Bank实体
def address: Address ---
>
Address是一个值对象
def dateOfOpening: Date,
def dateOfClose: Option[Date]
//..
}
case class CheckingAccount( --
>
这是对于Accopunt的实现,它重写了其一些属性
no: String,
name: String,
bank: Bank,
address: Address,
dateOfOpening: Date,
dateOfClose: Option[Date],
//..
) extends Account
case class SavingsAccount(
//..
rateOfInterest: BigDecimal,
//..
) extends Account
trait AccountService {
def transfer(from: Account, to: Account, amount: Amount):Option[Amount]
}
PS:scala的样本类
在清单1.2里我们使用scala的样本类去建模一个Account聚合。样本类在sacala里提供了一种便利的方式,来设计从头就提供不可变形的对象。默认情况下,类获取的所有参数都是不可变的。因此,使用样本类,我们得到以易于使用的方式来定义一个聚合所带来的便利,以及不变性所带来的所有好处。
清单1.1和清单1,2使用了scala的特质。特质允许你在scala中定义模块,他们是可以被组合在一起的。特质的混合是一个小的抽象,可以与其他组件混合以形成更大的组件。
更多的scala的知识,参考官网吧,作者开始啰嗦了。
注意,我们已经在Scala中实现了一个Account聚合的基本契约以及以样本类形式出现的变体。正如前面的“PS:scala中的样本类”所指出的,样本类被方便地用于建模不可变的数据结构。这些都被称为代数数据类型,我们将在继续讨论的过程中更详细地讨论这些数据类型。但是让我们看一下前面提到的Account实体聚合的一个方面。
在1.2.2小节中,您看到可以更新实体的某些属性而不能改变他的标识符。这意味着一个实体是可修改更新的。但是这里我们将Account实体建模为一个不可变的抽象,这似乎是一个明显的矛盾吗?当然不是(小人得志的嘴脸,就你知道的多!!)!我们将允许对实体进行更新,但是以一种函数的方式,它并不会让实体变得可变。您的更新将生成一个带有修改属性值的新实例,而不是对对象本身进行变化。这样做的好处是,您仍然可以在进行更新生成同一实体的新实例的时候,继续将原始的抽象作为不可变的实体进行共享。在函数思维方式的背景下,你将尽可能地争取实体的不变性。这是指导你的模型设计的指导原则之一。在清单1.2还显示了一个示例领域服务(AccountService),它使用帐户聚合来实现两个帐户之间的资金转移。