DDD战术3 领域设计模型 I
2019-09-27 本文已影响0人
莫小归
GitChat课程《领域驱动设计--战术篇》笔记,课程作者张逸
一.表达领域设计模型
1.企业应用架构中表达领域逻辑的常见模式:事务脚本、表模块、领域模型
1.1事务脚本
- 事务脚本将整个领域场景按顺序分解为多个子任务,然后组合为一个完整过程。多数情况一个事务脚本会对应一个数据库事务,该模式仍然从数据库的角度进行思考和设计
- 面向对象语言可以由一个类实现事务脚本,通常有以下两种实现方式
1)类的每个public方法等同于一个事务脚本,完成一个完整的领域场景。在public方法内部可以通过组合方法模式(即通过其他方法的调用来组合新的方法)来改进代码的可读性,避免重复逻辑。
2)引入命令模式,每个事务脚本对应到一个类中,然后根据其通用特征抽象为公共的命令接口或抽象类,每个封装了事务脚本的类都实现该命令接口。
如下推荐朋友服务的代码采用的就是上述第一种事务脚本模式
public class FriendInvitationService {
public void inviteUserAsFriend(String ownerId, String friendId) {
try {
bool isFriend = friendshipDao.isExisted(ownerId, friendId);
if (isFriend) {
throw new FriendshipException(String.format("Friendship with user id %s is existed.", friendId));
}
bool beInvited = invitationDao.isExisted(ownerId, friendId);
if (beInvited) {
throw new FriendshipException(String.format("User with id %s had been invited.", friendId));
}
FriendInvitation invitation = new FriendInvitation();
invitation.setInviterId(ownerId);
invitation.setFriendId(friendId);
invitation.setInviteTime(DateTime.now());
User friend = userDao.findBy(friendId);
sendInvitation(invitation, friend.getEmail());
invitationDao.create(invitation);
} catch (SQLException ex) {
throw new ApplicationException(ex);
}
}
}
- 事务脚本直接而简单,在业务逻辑相对简单时,事务脚本在处理性能和代码可读性都有明显的优势。但这种过程式的设计可能设计出一个庞大的服务类,又由于缺乏明晰的领域概念,随需求的变化与增加,代码容易膨胀,整个软件系统较难维护
1.2表模板
- 表模块模式持有整个表的数据(而不是数据表的一行),数据库中的一个表对应一个表模块对象,是数据模型驱动设计的直观体现。该对象封装领域逻辑,并包含对表数据进行操作(持久化)的方法
- 表模块更像是为了解决关系表数据结构的存取问题而定制的模式,能够让领域逻辑与关系数据库的衔接更加简单。表模块对象拥有的数据基本上是自给自足的,可以避免贫血模型的出现,但违背单一职责原则,变得过于臃肿
1.3领域模型
- 领域模型合并了行为和数据,复杂领域模型按照领域逻辑设计对象,广泛运用继承、策略和其他设计模式,通过数据映射器实现对象与数据的映射
- 软件复杂度来自重复和变化,领域驱动设计遵循高内聚低耦合的“正交设计”,从角色、职责和协作三个方面考虑职责分配以减少重复,从间接、抽象与分离三个方面考虑抽象接口以应对变化
2.DDD现实对象模型中的问题与解决
现实的对象模型需要考虑外部资源、依赖、性能和数据一致性,与理想模型相比,有诸多设计约束,DDD引入实体、值对象、聚合、资源库、工厂、领域事件等设计要素
2.1领域模型对象如何与数据库协作?
- 引入资源库(Repository)隔离领域逻辑与数据库实现,并将领域模型对象当做资源,将存储领域对象的介质抽象为仓库
2.2领域模型对象的加载以及对象间的关系如何处理?
- 引入聚合(Aggregate)划分对象之间的边界,在边界内保证所有对象的一致性,并在对象协作与独立之间取得平衡
2.3领域模型对象在身份上是否存在差别?
- 区分实体(Entity)与值对象(Value Object),避免不必要的身份跟踪与并发控制
2.4领域模型对象彼此之间如何能弱依赖地完成状态的变更通知?
- 引入领域事件(Domain Event),通过发布者/订阅者来发布与订阅领域事件
3.领域设计要素间关系图
- Eric Evans 规定只能是实体(Entity)、值对象(Value Object)、领域服务(Domain Service)与领域事件(Domain Event)表示模型。这样的约束就可以避免将太多的领域逻辑泄漏到领域层外的其他地方,例如应用层或基础设施层
- 其次,图中明确提及使用聚合(Aggregate)来封装实体和值对象,并维持边界内所有对象的完整性。若要访问聚合,需要通过资源库(Repository)来访问,这就隐式地划定了边界和入口,若能做到这一点,对聚合内所有类型的领域对象都可以得到有效的控制。当然,若牵涉到复杂或可变的创建逻辑,还可以利用工厂(Factory)来创建聚合内的领域对象
- 最后,若牵涉到实体的状态变更(注意,值对象是不牵涉到状态变更的),则通过领域事件来推动
4.领域模型对象的哲学依据
- 亚里士多德范畴学术将范畴分为十类:实体、数量、性质、关系、地点、时间、形态、状况、活动(主动)、遭遇(被动)
- 对应DDD中的四种领域模型对象
1)实体:实体范畴,包含了其他范畴,包括引起属性变化和状态迁移的活动
2)值对象:为主体对象的属性,通常代表数量、性质、关系、地点、时间或形态
3)领域事件:封装了主体的状况,代表了因为主动活动导致的状态变迁产生的被动遭遇
4)领域服务:范畴必须“内居”于一主体,若活动与遭遇代表的业务行为无法找到一个主体对象来“内居”,就以领域服务作为特殊的主体来封装