这篇,主要讲解领域建模的过程,先了解以下几个建模的要素。
关联
对象之间的关联,使得建模与实现的交互变得更为复杂。对于模型中每个可以遍历的关联,在软件中都有一个具有同样属性的机制。
现实中有很多关联都是多对多的,其中很多关联自然而然是双向的。这些普通的关联,会导致实现和维护变得复杂。此外,它们也很少能表示出关系的本质。
至少有3种方式,可以使得关联更容易控制:
- 规定一个遍历方向
- 添加一个限定符,以便有效地减少多重关联
- 消除不必要的关联
尽可能对关系约束是非常重要的。当应用程序不需要双向遍历时,可以指定一个遍历方向,以便减少相互依赖性,并简化设计。
举例1:客户王某在可得官网下单买了一副眼镜。订单有标识是哪个客户下单的,客户也可以找到所有自己下的订单。客户实体关联订单列表,没有太大的意义,消除此不必要的关联,仅保留“订单标识哪个客户下单”这个关联。
实体(Entity)
很多对象,不是通过它们的属性去定义的,而是通过一连串的连续事件和标识来定义的。
具有唯一标识,有生命周期概念。在生命周期内,可以发生状态改变。在外部,可以通过实体标识,来获取实体本身,即唯一性标识。
自身状态的变化,通过自身提供的行为(用面向对象的概念,指方法)来改变。
注意:此实体不是指对象关系映射(ORM)中的用于关系型数据库持久化的实体。
唯一标识生成
-
用户提供标识:通过用户输入的信息,作为唯一标识。这种方式,如果存在改变标识的可能性,则不应该使用
-
应用程序生成唯一标识:如UUID、雪花算法、基于第三方服务的编号生成(性能相对不是太好,但对于可读性较好)
-
持久化机制生成唯一标识:如数据库的自增ID。在高并发情况下,会有性能瓶颈。而且在使用
领域事件
时,无法在第一时间拿到唯一标识,所以这种方式一般不推荐使用。
标识稳定性
不应该去改变实体的唯一标识。如果存在这种情况,那么应选择其他方式作为标识,而原定义的标识,则通过唯一性约束来解决重复问题。
发现实体及其本质特征
举个电商库存管理的例子:
1)一个仓库,从物理划分上,有几栋楼组成,每栋楼有N个楼层,每个楼层划分了多个区域;
2)一个区域下,可以放置多个货架,每个货架可根据需要划分多个货位;
3)一个货位上可以放置1个或多个盒子,盒子是作为存放商品的容器;
4)一个仓库,为了扩容,会盖新的楼;
5)一个楼层,出于管理考虑,会调整区域的划分;
6)仓库管理员,可以按需要,将货架禁用掉,以挪作他用,或者重新调整货架的货位布局;
从以上可以分析出的实体:仓库、区域、货架;仓库具有创建楼、楼层、区域的行为;区域拥有放置货架的行为;货架可以被禁用/启用,可以重建货位布局;
值对象(Value Object)
实体
的引入,可能会带来副作用。如果实体A被实体B引用,有可能会通过实体B的修改,而导致实体A的修改,且往往这不是想要的结果,这就是副作用。这个时候就要引入 值对象
的概念。
相对 实体
而言,最大的区别是状态不可改变。判断2个值对象是否相同,是通过判断值对象内部各个属性值是否相同作为依据。
修改实体中的值对象类型的属性,只需要创建新的值对象,然后替换即可。
注意:能使用值对象,就不要使用实体。引入实体可能带来副作用之外,还增加了系统跟踪其状态的负担。
服务(Service)
当领域中的某个重要的过程或转化操作不属于Entity
或Value Object
的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为Service。定义接口时,应使用模型语言,并确保操作名称是Ubiquitous Language
中的术语。
引入领域层服务,有助于在应用层和领域层之间,保持一条明确的界限。
注意:在建模期间,要避免将新的业务操作,全都定义为领域服务。应该尽可能思考,是否应该属于某个实体,避免导致实体的贫血设计。
良好的领域服务有以下特征:
- 与领域概念相关的操作不属于
Entity
或Value Object
的一个自然的部分 - 接口是根据领域模型的其它元素定义的
- 操作是无状态的
备注:此处的Service,属于领域层,亦称为领域服务(Domain Service),是为了和应用服务加以区分。
模块
对于当前限界上下文,进行二次分类的划分,是可选的。如果当前的限界上下文所涵盖的聚合根比较多,并且可以从业务语义上能清晰得进行划分,那么这个时候就需要引入模块的概念了。
具体开发语言上的实现,Java的称之为Package,C#的称之为Namespace。
聚合(Aggregate)
将实体和值对象划分为聚合并围绕着聚合定义边界。选择一个实体作为每个聚合的根,并仅允许外部对象持有对聚合根的引用。作为一个整体来定义聚合的属性和不变量,并把其执行责任赋予聚合根或指定的框架机制。
由1-N个实体和0-N个值对象聚合而成的,存在一致性边界的特殊的 实体
,从业务操作角度考虑,往往是一个提供操作入口的实体。
举个例子:修改员工的教育背景信息。 操作入口是“员工信息”,通过“员工信息”修改“教育背景”。那么这个“员工信息”就是一个聚合根。
规则
-
一致性边界
-
设计最小边界
-
通过标识引用外部聚合根
-
边界外使用最终一致性
打破规则的理由
-
处于用户界面便利
-
缺乏技术
-
全局事务
-
查询性能