一、Entiry(实体)和Value Object(值对象)
1、实体
实体的定义在原书《领域驱动设计》中的描述如下:
一些对象主要不是由它们的属性定义的。它们实际上表示了一条“标识线”,这条线跨越时间,而且常常经历多种不同的表示。 领域驱动设计
听起来比较晦涩,可以概括几点:
1)、在同一类模型实中需要区别开来,一个实体是唯一的东西;
2)、每个实体有唯一标识来区别彼此;
3)、实体有生命周期,我们可以对它多次修改,但它仍然还是同一个实体。
2、值对象
当我们只关心一个模型元素的属性时,或者说对于同一类模型实例我们不用区别每一个时,只关心其属性时,这些对象就可以归为值对象。
相对于实体,值对象有几个特征:
1)不可变
即值对象是不能修改的,如果要修改则应该产生一个新对象,这样就不用管理其整个生命周期。
同样对于一个修改方法,以下是实体的伪代码:
Public Class Order{
pulic void setStatus(Status status){
this.status = status;
}
}
如果是值对象,则伪代码应该是这样的:
Public Class Order{
pulic Order setStatus(Status status){
Order order = new Order(status);
return order;
}
}
2)、它没有标识,两个值对象是否相等只要比较属性即可。
说了这么多,我们可以举一些例子:
订单
这个模型在电商系统中比较常见,假如我昨天创建了一个订单,今天也创建了一个订单,在系统中这两个订单是不同的,两个订单通过订单号来区别彼此,并且订单需要管理其整个生命周期,包括从创建到支付再到发货,因此订单是实体。
发货地址
如果我要将一个订单发到深圳XX村XX街道,则我不用关心这个地址何时创建,我只需这个地址就行了,因此这个地址可以在昨天的订单使用,也可以在今天的订单使用,也没必要创建相同的2个地址。因为判断2个地址是否相同,是通过对比省、市、村、街道等属性,而不像上面的订单中,通过订单号来区别,因此地址是值对象。
上面这些举例是基于电商的场景来说的,如果一些场景发生变化,实际的模型可能有变化,比如说对于快递公司来说同一个目的地地址可能是一个宿舍,则这个地址需要表示为实体了,因为同一个地址可能对应多个目的地。
二、聚合
聚合是一组相关对象的集合,称之为修改单元。
每个聚合都有一个根和一个边界,根一般是一个实体,外部对象只能引用根,内部对象之间则可以相互引用。
为什么需要聚合呢,原书给的原因如下:
1)、保证对象更改后的一致性;
2)、保持固定规则;
这里还是以上面的订单为例,在电商系统中,一个完整的订单除了订单模型,还有地址、支付、物流等模型。
假如我们要修改发货地址,如果我们不通过订单去修改发货地址,则一些规则无法保证,如防止订单已经打包发货了的情况下是不允许修改发货地址的,如果先不从订单得到地址,而是从数据库中取出来直接修改地址,则这个规则可能被破坏了。
另外就有就是一个订单还在,但是地址已经删除了也是灾难,因此所有对地址的修改都应该从订单出去,先找到订单,然后通过订单去修改地址。
三、领域、子域、核心子域、支撑子域
领域是一个组织所做的事情以及其中包含的一切。
子域是领域一其中一个部分或者说某一个方面。
领域的概念太宽泛了,可以表示整个业务系统,而子域则是表示其中的一部分,之所以要这么分,因为分解是我们面对复杂系统的一个常用办法,只要将系统拆分的足够我们可以理解的范围才容易掌握,这里不用太纠结概念。
像公司做电商的,则电商就是你的领域,里面可以分解为商品子域、订单子域、物流子域等。
核心子域
领域中最核心的子域,即是有价值,最核心的业务子域。
支撑子域
领域中比较通用的子域,起支撑的子域。
如电商系统中订单应该是最核心的子域,短信、邮件发送可以作为通知子域,后者主要起支撑使用,也是比较通用的,在其它系统中也是可以用的。
之所以要区分两者 ,原书作者认为应该将主要精力放在核心子域,将技术最好的人投入到这些领域上,而支撑子域则用能力一般的人就可以,分清主、次。
实际上大部分公司是反过来的,认为通用子域对人的要求更高,这会导致最核心的业务域没有设计好。