领域驱动设计-读书笔记-第七章-一个扩展的示例

本章的内容十分硬核,使用实际案例-货物运输系统进行说明,如何使用DDD一步步落地。根据本章的内容,这里总结下作者落地DDD的流程。

  1. 提出业务需求,业务需求展现形式不局限,可能是prd,wiki,或者几句话。
  2. 消化需求,同PD或者领域专家讨论,提炼需求中的关键知识点。
  3. 经过多次讨论后,同领域专家建立通用领域语言。一般表现形式为:标准统一的领域单词,或者宽泛的UML类图。
  4. 模型驱动设计,建立起核心模型。核心模型表现形式一般也是采用UML类图。建立核心模型分为以下几步,这里需要进一步拆分。1)划分entity,value object。2)确定模型类之间的关联关系。3)划分边界,确定aggregate。4)设计repository。5)确定数据库模型。6)根据需求,进行场景走查,逐步迭代精简模型。 
  5. 明确应用层职责和能力,确定最终存储层的设计。在很大程度上这一步已经体现出架构设计。
  6. 组织编码,按照领域知识拆分包,核心领域层代码编写(entity,value object,aggregate,factory,service)。应用层以及存储层完成具体的编码实现。

第一部分:业务需求

假设我们为货运公司开发软件,最初的需求包含以下三项基本功能:

(1) 跟踪客户货物的主要处理;
(2) 事先预约货物;
(3) 当货物到达其处理过程中的某个位臵时,自动向客户寄送发票。

第二部分:消化需求,提炼关键知识点

2.1 初步分析

(1) 跟踪客户货物的主要处理;

  1. 跟踪。货物的历史记录,从何处运到何处,通过何种方式运送,在每个阶段是如何处理?
  2. 货物的主要处理,货物分为哪些处理方式,Cargo Hading可以抽象为不同的处理方式
  3. 客户。客户是否唯一,客户承担哪些角色,客户与货物的关系?

(2)事先预约货物。

  1. 提供单独的预约功能,指定预约,从何处送到何处,通过何种方式。

(3) 当货物到达其处理过程中的某个位臵时,自动向客户寄送发票。

  1. 满足Cargo Hading的某个阶段,以及到达某个指定位置,向客户发送发票。

2.2 提炼出的关键知识点

模型层面

客户模型,Customer,承担多角色,如:托运人,收货人,付款人。一个Cargo只能由一个Customer来承担,限定多对一关系。

作者在这部分内容,提炼出了一个非常核心的模型。Delivery Specification (运送规格),这里的运送规格有点抽象,结合书中内容,具体是指:包括了运送的目的地、到达日期等。

Delivery Specification (运送规格) 对象作用:

  1. 减轻Cargo对象职责。
  2. 与运送相关的所有细节,统一由Delivery Specification承担。

Carrier Movement,表示由某个Carrier(车或者船),执行从一个Location到另一个Location的旅程。

Delivery History,运送历史,反映了Cargo实际上发生了什么事情。

应用层功能提炼

  1. 查询货物跟踪功能。Tracking Query
  2. 预定货物功能。Booking Application
  3. 自动发送发票功能。Send Application

第三部分:确定通用领域语言

第四部分:建立核心领域模型

1. 划分entity,value object

entity

  1. Customer,客户,customer使用纳税号作为唯一标识。
  2. Cargo,货物,这里可以采用货物码作为唯一标识。
  3. Carrier Movent,运输活动事件,是真实世界中发生的事件,这里可以采用调度表的唯一ID作为唯一标识。
  4. Location,位置,可以采用自增主键作为唯一标识。
  5. Delivery History,运送历史,这是一个比较复杂的对象,与Cargo一对一。唯一标识可以采用自增ID。
  6. Hading Event,处理事件,唯一标识:Cargo Id + 完成时间 + 类型

value object

  1. Delivery Specification,假如有两个Cargo去往同一个地点,可以采用同一个Delivery Specification。

2. 确定模型类之间的关联关系

 

 

划分边界,确定aggregate

aggregate

  1. Customer有自己的唯一标识,即自己单独的聚合根。
  2. Carrier Movent,也是自己的聚合根。
  3. Location,也是自己的聚合根。
  4. Cargo这个稍微有点复杂,Cargo的聚合根即货物的唯一ID,但是聚合的边界,包括Delivery History、Delivery Specification。1)Delivery  History,因为没人会在不知道Cargo的情况下直接去查询Delivery History。因为Delivery History不需要直接的全局访问,而且它的标识实际上只是由Cargo 派生出的,因此很适合将Delivery History放在Cargo的边界之内,并且它也无需是一个AGGREGATE根。2)Delivery Specification是一个VALUE OBJECT,因此将它包含在Cargo AGGREGATE中也不复杂。
  5. Hading Event,自己的聚合根。

设计repository

后续的优化,Handing Event作为单独的聚合根,需要有repository。

 

第五部分:确定分包逻辑

按照,Customer,Shipping,Bill三个包进行划分。这样领域知识会比较清晰。

总结

至此整个实例的模型驱动设计过程,已经展现的比较清楚,此笔记省略了后面的系统交互部分,因为这部分感觉放到六边形架构去说比较合适。

这里再提炼下,根据此篇文章提炼到领域驱动建模的方法论:

  1. 业务需求消化,业务需求一定要详尽,其中涉及到的领域知识一定要能完整清晰的体现出来。这里对PD的要求比较高。拒绝一句话需求,例如需求:客户可以预定货物。这个需求就太粗狂了,需要对这种需求逐步细化,可以加N多形容词,进行表达疑问。例如:什么样的客户,具有什么角色、权限的客户,通过什么方式去预定,预定货物有什么要求,是全部货物还是指定货物,预定后,是否要通知客户,预定成功,货物的各个流转阶段,是否也要通知客户。预定的这些货物,在后台谁能看到?什么角色的人去维护这些预定的货物。可以看到,根据一句话需求,能衍生出N多问题。
  2. 解决掉需求中的各种疑问,提炼关键知识点,建立起核心模型的轮廓。
  3. 与PD或者领域专家,反复交流沟通,基于关键知识点,建立起通用领域语言,这里的通用领域语言,已经是比较宽泛的UML类图了,可能还不是很全面,但是离核心模型已经越来越清晰了。
  4. 模型驱动设计,建立核心领域模型。区分entity,value object,aggregate,service,factory。划分聚合的边界。采用场景走查方式,反复确认核心模型是否可以满足需求。
  5. 数据存储设计。基于核心领域模型的entity,去建立底层存储模型。
  6. 应用层职责明确。围绕PRD,确定应用层接口设计。
  7. 编码阶段:module划分 + 设计模式。