从问题空间到实现空间:领域模型和涉及质量
写好代码的普适性原则:易于理解,易于演进,低开发成本
降低业务域和方案域的表示差距,易于演进,代码和模型的变化是统一且同步进化的
从模型到代码:DDD的4个构造块模式
实体 Entity
具有唯一标识来跟踪的具有重要业务意义的对象
这些对象会随着业务进展产生状态和属性的变更,但他们代表的业务对象不变
例如出行计划、支付单
值对象 Value Object
值对象仅仅用来描述特征,我们只关心对象的属性,不需要id
区分实体对象和值对象可以降低系统的复杂性
例如出行信息和费用信息是用来描述出行计划的特征的,相同的出行信息可以互换,不需要唯一标识
(领域)服务 Service
一些业务逻辑并不是直接与领域对象相关,它本身代表了一种商业策略或者业务处理过程。
服务自身是无状态的。即服务本身是不持有数据的。
例如费用计算、上车点计算等业务处理流程。
领域事件 Domain Event
领域事件本身是业务结构的反映,通过事件风暴等方式可以清晰地发现业务中的关键事件。
领域事件可以降低系统设计的复杂程度,例如事件溯源这种架构。
如果一个事件(例如系统宕机)并不是业务活动的结果,那么它不能称为领域事件。
领域事件是一种特殊的“值对象”,即一旦产生就不会被更改了。
领域事件如果在远程则必须要有id,但如果仅在本地就不一定。
注意事项
尽量使用值对象,大多数时候并不需要完整的实体信息
对于 “乘车人” 、 “发布人” 这类值对象也需要保存一个ID来支持必要时获取更多信息或者信息同步
对于生命周期不同的对象,慎用关联(例如 “上车点” 和 “预定义上车点” )
转换数据库视角为领域视角
慎用数据库外键,不要靠数据库来保证一致性
在领域层保障业务完整性:DDD的3个生命周期模式
聚合 Aggregates
聚合提高了对象系统的粒度,保证了业务逻辑的完整性,减少了错误产生的概率
- 将实体和值对象划分为聚合并且围绕聚合来定义边界。
- 作为一个整体来定义聚合的属性和不变量,并把其执行责任赋予聚合根(串联其它聚合对象的核心对象)或者指定的框架机制。贫血模型和充血模型
- 一定要选择一个实体作为聚合的根,并仅允许外部对象持有对聚合根的引用,不可以持有对聚合根的局部对象的引用。聚合的核心作用就是封装业务的完整性,进而获得了知识丰富的行为。
为什么一个聚合不该包含其它聚合的聚合根?
生命周期一致性原则:同一个聚合里的生命周期是同步的,即如果聚合根消失,聚合内其他元素也都应该消失。
小聚合原则:在不破坏业务完整性的基础上,小聚合带来更大的灵活性
具体在代码实现中,一个聚合根引用另一个聚合根时,引用的时另一个聚合根的id,即将另一个聚合根视作当前聚合根下的一个值对象来处理。
工厂 Factories
因为聚合需要保证业务完整性,而对象并不是一次性构建出来的,我们应当使用工厂来解决聚合构造的完整性问题。
在设计模式中,工厂是用于分离构造和使用的。而在DDD中,工厂是用来保证聚合的构造的。
不一定使用的时工厂模式,也可以是 builder 或者静态方法,重要的是聚合根一次性被完整的构造出来。
资源库 Repositories
资源库是聚合的仓储机制,外部世界通过资源库,并且只能通过资源库来完成对聚合的访问。例如只有资源库可以提供数据库接口。
资源库以聚合的整体来管理对象,一个聚合只能有一个以聚合根命名的资源库。
资源库模式并不等价于持久化,更不是数据库访问层。
在产品代码中增加领域层:聚焦核心业务逻辑
- 接口层
接口层数据校验,必要的数据补全,负责处理边界性事务
- 应用层
容易变的业务逻辑
- 领域层
稳定的业务逻辑
按照聚合来组织代码结构,用一个package来描述一个聚合,每个包里都有一个聚合根。
在领域层代码中体现业务逻辑和业务完整性。
- 数据层
数据持久化,外部数据访问,消息收发
依赖倒置:接口是属于使用者来决定的。稳定性的领域层放在中间,提供对外接口。