这篇,主要讲解领域建模的过程,先了解以下几个建模的要素。

关联

对象之间的关联,使得建模与实现的交互变得更为复杂。对于模型中每个可以遍历的关联,在软件中都有一个具有同样属性的机制。

现实中有很多关联都是多对多的,其中很多关联自然而然是双向的。这些普通的关联,会导致实现和维护变得复杂。此外,它们也很少能表示出关系的本质。

至少有3种方式,可以使得关联更容易控制:

  • 规定一个遍历方向
  • 添加一个限定符,以便有效地减少多重关联
  • 消除不必要的关联

尽可能对关系约束是非常重要的。当应用程序不需要双向遍历时,可以指定一个遍历方向,以便减少相互依赖性,并简化设计。

举例1:客户王某在可得官网下单买了一副眼镜。订单有标识是哪个客户下单的,客户也可以找到所有自己下的订单。客户实体关联订单列表,没有太大的意义,消除此不必要的关联,仅保留“订单标识哪个客户下单”这个关联。

实体(Entity)

很多对象,不是通过它们的属性去定义的,而是通过一连串的连续事件和标识来定义的。

具有唯一标识,有生命周期概念。在生命周期内,可以发生状态改变。在外部,可以通过实体标识,来获取实体本身,即唯一性标识。

自身状态的变化,通过自身提供的行为(用面向对象的概念,指方法)来改变。

注意:此实体不是指对象关系映射(ORM)中的用于关系型数据库持久化的实体。

唯一标识生成

  • 用户提供标识:通过用户输入的信息,作为唯一标识。这种方式,如果存在改变标识的可能性,则不应该使用

  • 应用程序生成唯一标识:如UUID、雪花算法、基于第三方服务的编号生成(性能相对不是太好,但对于可读性较好)

  • 持久化机制生成唯一标识:如数据库的自增ID。在高并发情况下,会有性能瓶颈。而且在使用 领域事件时,无法在第一时间拿到唯一标识,所以这种方式一般不推荐使用。

标识稳定性

不应该去改变实体的唯一标识。如果存在这种情况,那么应选择其他方式作为标识,而原定义的标识,则通过唯一性约束来解决重复问题。

发现实体及其本质特征

举个电商库存管理的例子:

1)一个仓库,从物理划分上,有几栋楼组成,每栋楼有N个楼层,每个楼层划分了多个区域;

2)一个区域下,可以放置多个货架,每个货架可根据需要划分多个货位;

3)一个货位上可以放置1个或多个盒子,盒子是作为存放商品的容器;

4)一个仓库,为了扩容,会盖新的楼;

5)一个楼层,出于管理考虑,会调整区域的划分;

6)仓库管理员,可以按需要,将货架禁用掉,以挪作他用,或者重新调整货架的货位布局;

从以上可以分析出的实体:仓库、区域、货架;仓库具有创建楼、楼层、区域的行为;区域拥有放置货架的行为;货架可以被禁用/启用,可以重建货位布局;

值对象(Value Object)

实体 的引入,可能会带来副作用。如果实体A被实体B引用,有可能会通过实体B的修改,而导致实体A的修改,且往往这不是想要的结果,这就是副作用。这个时候就要引入 值对象 的概念。

相对 实体 而言,最大的区别是状态不可改变。判断2个值对象是否相同,是通过判断值对象内部各个属性值是否相同作为依据。

修改实体中的值对象类型的属性,只需要创建新的值对象,然后替换即可。

注意:能使用值对象,就不要使用实体。引入实体可能带来副作用之外,还增加了系统跟踪其状态的负担。

服务(Service)

当领域中的某个重要的过程或转化操作不属于EntityValue Object的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为Service。定义接口时,应使用模型语言,并确保操作名称是Ubiquitous Language中的术语。

引入领域层服务,有助于在应用层和领域层之间,保持一条明确的界限。

注意:在建模期间,要避免将新的业务操作,全都定义为领域服务。应该尽可能思考,是否应该属于某个实体,避免导致实体的贫血设计。

良好的领域服务有以下特征:

  • 与领域概念相关的操作不属于EntityValue Object的一个自然的部分
  • 接口是根据领域模型的其它元素定义的
  • 操作是无状态的

备注:此处的Service,属于领域层,亦称为领域服务(Domain Service),是为了和应用服务加以区分。

模块

对于当前限界上下文,进行二次分类的划分,是可选的。如果当前的限界上下文所涵盖的聚合根比较多,并且可以从业务语义上能清晰得进行划分,那么这个时候就需要引入模块的概念了。

具体开发语言上的实现,Java的称之为Package,C#的称之为Namespace。

聚合(Aggregate)

将实体和值对象划分为聚合并围绕着聚合定义边界。选择一个实体作为每个聚合的根,并仅允许外部对象持有对聚合根的引用。作为一个整体来定义聚合的属性和不变量,并把其执行责任赋予聚合根或指定的框架机制。

由1-N个实体和0-N个值对象聚合而成的,存在一致性边界的特殊的 实体,从业务操作角度考虑,往往是一个提供操作入口的实体。

举个例子:修改员工的教育背景信息。 操作入口是“员工信息”,通过“员工信息”修改“教育背景”。那么这个“员工信息”就是一个聚合根。

规则

  • 一致性边界

  • 设计最小边界

  • 通过标识引用外部聚合根

  • 边界外使用最终一致性

打破规则的理由

  • 处于用户界面便利

  • 缺乏技术

  • 全局事务

  • 查询性能

领域事件(Domain Event)

工厂(Factory)

仓储(Repository)

应用服务(Application Service)

实战演练