首先ddd的战术这个讲法是不太好的。ddd书中说的是战术性建模(tactical modeling)。其意思是在战术层面的建模,那当然有战略层面的建模啦。以后会专门讲。
domain object是ddd区别于其他建模/设计方法的一个部分。他定义了概念帮助我们去建立model。如果没有接触过ddd,一开始会觉得有点莫名其妙,为什么要定这种规矩,这种规则?
entity是一类可识别的可追踪的对象。
说简单了,它必须有identifier,再简单点id(可识别,可被追踪)。
另外它是有可变对象,mutable。但即使状态变化了之后,entity还是原来的entity。(好绕口)
现实一点的例子。一个人家“王帝”,他改名交了“王皇”,名字虽然变了,但还是同一人。当然现实世界里我们很难去寻找一个identifier(识别码),如何定义一个不变的identifier会是个哲学问题,但编程时就简单多了,直接搞个id就就行。
与entity对应的一个概念叫value object
它是一个值,是不可变的,immutable。没有identifier,也不需要被追踪!
比如java中的字符串和value object的感念很相近。字符串生成之后就是不可变的。而且也没有什么id来识别它。
什么时候使用entity,什么时候使用value object
具体问题具体分析
比如我们需要对地址这个东西建模。如果我们关心的是地址的履历之类的信息,过去30年前这个地址可能叫霞飞路,现在可能叫淮海路,而且需求是我们必须知道霞飞路,淮海路指的是一个地址。那很可能我们需要的是entity。这个entity可能还要开发change()的方法来改变路名。
但如果我们做的是一个送货软件。地址只是表示一个目的地而已,霞飞路和淮海路在我看来就是不同的,那就说明,你不必对地址本身的变化进行追踪(送货地址变了,对你很重要。但霞飞路改名成淮海路对你不重要。)。那value object就够了。
你可能觉得何必这么费事搞个表示地址的value object, 搞个字符串不就行了?
首先这不一定是一个关于ddd的问题。淡然很多情况下,我们可以的确用java的基础类来建模的。但从面向对象的封装角度来说,我们可以考虑创造专门的类。
如果我们创建value object,它是可以拥有行为的。比如Address是一个value object。它可以拥有getCountry(), getProvince(), getCity()等方法。
Aggregate,集合。是有多个(也可以是一个)entity,value object组成的对象。
Aggregate可以看作一个树状结构的东西。根是一个entity。Aggregate的一个作用是保持domain object的关联性的正确。
Repository是用来存放一个aggregate的object。(是不是听起来像DAO?)
Repository的存在,让我们感觉我们存放在它里面的aggregate就好像on memory一样,不必去关心它具体是和rdb对接还是其他的形式。
请注意的是Repository对应的是aggregate而不是entity!虽然我们在实际偏码时不会去专门写aggregate的类,有关具体实现,之以后的文章会写。简单地讲根entity就可以代表aggregate。
active record pattern也是很常见的一种模式。大概是建立于数据表的一一映射的类,然后有各自的DAO类。乍看起来和domain object的做法是一样的。
1. 映射关系
active record一般以一个类对应一个数据表为前提
repository则没有这个限制。当一个aggregate需要对应多个数据表时,那repository自然就对应多个数据表。repository隐藏了数据层的物理实现。
这里想多提一点。domain model的建立,称作理论设计比较合适。它尽量不关心物理的实现,只求对domain的正确反映。
而data model的设计,可以称作物理实现。是以数据永久化为目的。与ddd相关联,自然就是如何将domain object永久化。
2. domain object主张充血模式
active record常常会导致制造大量的于数据表对应的类,这些类一般不会有行为,而描述业务逻辑的职责就会到很多service类上。
domain object则希望类有自己的行为,而不是开放好多setter,让外部的类来控制自己的状态变化。
entity: stateful, mutable.
value object: immutable
aggregate: entity和value object的集合
repository: stateless,储存aggregate
之后的文章准备写一些例子来更好地说明这些概念。