贫血(失血)的事务性脚本,会丢失业务知识。 一开始使用贫血(失血)的事务性脚本还好,但随着业务逻辑日趋复杂,代码量在飞速增长,业务知识在不断丢失,最终很难通过阅读代码理解业务逻辑,项目的可构建性、可理解性飞速下降。 这个时候就轮到领域驱动设计登场了。领域驱动设计可以极大的减少、避免业务知识的丢失,从而更好的应对日趋复杂的业务逻辑。
以业务为中心,而不是以技术为中心。 可以考虑事件风暴来理清业务,确定通用语言。
分类
- 核心域(subdomain)
- 支撑子域(supporting subdomain)
- 通用子域(generic subdomain)
应对复杂性 划分出核心域、支撑子域、通用子域,集中资源于核心域。
上下文映射图表示了各个上下文间的集成关系与方式。
上下文间的关系
- 合作(partnership)
- 共享内核(sharedKernel)两个包组件是有可能共享同一个模型的
- 客户供应商(customer-supplier)
- 跟随者(conformist)
- 防腐层(anticorruptionLayer)
- 开放主机服务(OpenHostService)
- 已发布语言(PublishedLanguage)
- 各行其道(separateWay)
- 大泥球(BigBallofMud)
上下文间集成方式
- rpc
- rest
- 消息(聚合发布领域事件)
通过唯一标志进行区分。
是描述性的信息,通过描述信息进行区分,而不是唯一标志。 通常用于描述实体与聚合。
聚合由聚合根(实体),和其它实体、值对象组成。
为何会有聚合 我认为聚合是为了简化及时修改多个实体、值对象,而自然产生的概念。 所以要考虑聚合是否简化了业务逻辑的实现,而不是为了使用聚合而聚合。
为何聚合作为事务一致性边界 聚合作为事务一致性边界有两层含义。
- 聚合内的修改在一个独立事务中完成;
- 聚合间的协作通过领域事件的发布与订阅完成;
我认为会有这点要求,有两点原因:
- 从业务角度考虑,大部分的多个聚合修改完全不需要及时完成,有延迟对业务也没有影响;
- 技术习惯使然。试想当一个团队能够轻车熟路的在多个服务间通过领域事件完成最终一致性后,很自然的就会让同一个进程内聚合也采用这种方式,因为此时这样反而简单;
所以是否采用领域事件,异步的完成最终一致性,一要看业务的及时性要求,二要结合自己团队的技术储备。
聚合设计的基本规则
- 在聚合边界内保护业务不变性
虽然简化业务操作,但是因为eager load有可能导致过多的内存消耗。
- 聚合要设计的小巧
聚合设计的过大,虽然可能方便,但有可能占用太多内存
- 只能通过标识符引用其它聚合
避免聚合间直接通过指针等方式引用,导致内存占用过高、编写复杂
- 使用最终一致性更新其它聚合
为了简单,无论各个聚合是否在同一个进程内容,统一都用外部的消息中间件完成
有些操作不适合放在聚合、实体、值对象上,这时就可以用无状态的领域服务完成。
由聚合发布,完成最终一致性。