DDD 领域模型的几种划分

举报
aoho 发表于 2021/06/02 15:00:58 2021/06/02
【摘要】 在前面的文章讲解了领域驱动设计的几种架构风格,下面我们具体结合简单的实例来看其中的领域模型划分,初步分为4大类:失血模型贫血模型充血模型胀血模型我们看看这些领域模型的具体内容,以及他们的优缺点。 失血模型失血模型简单来说,就是domain object只有属性的getter/setter方法的纯数据类,所有的业务逻辑完全由business object来完成(又称TransactionScr...

在前面的文章讲解了领域驱动设计的几种架构风格,下面我们具体结合简单的实例来看其中的领域模型划分,初步分为4大类:

  1. 失血模型
  2. 贫血模型
  3. 充血模型
  4. 胀血模型

我们看看这些领域模型的具体内容,以及他们的优缺点。

失血模型

失血模型简单来说,就是domain object只有属性的getter/setter方法的纯数据类,所有的业务逻辑完全由business object来完成(又称TransactionScript),这种模型下的domain object被Martin Fowler称之为“贫血的domain object”。如下:

  • 一个实体类叫做Item

    public class Item implements Serializable {   
     private Long id = null;   
     private int version;   
     private String name;   
     private User seller;   
     // ...  
     //   getter/setter方法省略不写,避免篇幅太长   
    

}
```

  • 一个DAO接口类叫做ItemDao

    public interface ItemDao {   
     public Item getItemById(Long id);   
     public Collection findAll();   
     public void updateItem(Item item);   
    

}
```

  • 一个DAO接口实现类叫做ItemDaoHibernateImpl

    public class ItemDaoImpl implements ItemDao extends DaoSupport {   
     public Item getItemById(Long id) {   
         return (Item) getHibernateTemplate().load(Item.class, id);   
     }   
     public Collection findAll() {   
         return (List) getHibernateTemplate().find("from Item");   
     }   
     public void updateItem(Item item) {   
         getHibernateTemplate().update(item);   
     }   
    

}
```

  • 一个业务逻辑类叫做ItemManager(或者叫做ItemService)

public class ItemManager {
private ItemDao itemDao;
public void setItemDao(ItemDao itemDao) { this.itemDao = itemDao;}
public Bid loadItemById(Long id) {
itemDao.loadItemById(id);
}
public Collection listAllItems() {
return itemDao.findAll();
}
public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount,
Bid currentMaxBid, Bid currentMinBid) throws BusinessException {
if (currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0) {
throw new BusinessException(“Bid too low.”);
}

  // ...  

}
```

以上是一个完整的失血模型的示例代码。在这个示例中,loadItemById、findAll 等等业务逻辑统统放在 ItemManager 中实现,而 Item 只有 getter/setter 方法。

贫血模型

简单来说,就是 domain ojbect 包含了不依赖于持久化的领域逻辑,而那些依赖持久化的领域逻辑被分离到 Service 层。

Service(业务逻辑,事务封装) --> DAO —> domain object

这也就是 Martin Fowler 指的 rich domain object:

  • 一个带有业务逻辑的实体类,即domain object是Item
  • 一个DAO接口ItemDao
  • 一个DAO实现ItemDaoHibernateImpl
  • 一个业务逻辑对象ItemManager

这种模型的优点:

  1. 各层单向依赖,结构清楚,易于实现和维护
  2. 设计简单易行,底层模型非常稳定

缺点为:

  1. domain object的部分比较紧密依赖的持久化 domain logic 被分离到Service层,显得不够 OO
  2. Service 层过于厚重

具体代码较为简单,不再展示。

充血模型

充血模型和第二种模型差不多,所不同的就是如何划分业务逻辑,即认为,绝大多业务逻辑都应该被放在domain object里面(包括持久化逻辑),而Service层应该是很薄的一层,仅仅封装事务和少量逻辑,不和DAO层打交道。

Service(事务封装) —> domain object <—> DAO

这种模型就是把第二种模型的 domain object 和 business object 合二为一了。所以 ItemManager 就不需要了,在这种模型下面,只有三个类,他们分别是:

  • Item:包含了实体类信息,也包含了所有的业务逻辑
  • ItemDao:持久化DAO接口类
  • ItemDaoHibernateImpl:DAO接口的实现类

在这种模型中,所有的业务逻辑全部都在Item中,事务管理也在Item中实现。
这种模型的优点:

  1. 更加符合OO的原则
  2. Service层很薄,只充当Facade的角色,不和DAO打交道。

这种模型的缺点:

  1. DAO和domain object形成了双向依赖,复杂的双向依赖会导致很多潜在的问题。
  2. 如何划分Service层逻辑和domain层逻辑是非常含混的,在实际项目中,由于设计和开发人员的水平差异,可能导致整个结构的混乱无序。
  3. 考虑到Service层的事务封装特性,Service层必须对所有的domain object的逻辑提供相应的事务封装方法,其结果就是Service完全重定义一遍所有的domain logic,非常烦琐,而且 Service 的事务化封装其意义就等于把 OO 的domain logic 转换为过程的 Service TransactionScript。

胀血模型

基于充血模型的第三个缺点,有同学提出,干脆取消Service层,只剩下domain object和DAO两层,在domain object的domain logic上面封装事务。

domain object(事务封装,业务逻辑) <—> DAO

似乎ruby on rails就是这种模型,他甚至把 domain object 和 DAO 都合并了。

该模型优点:

  1. 简化了分层
  2. 也算符合OO

该模型缺点:

  1. 很多不是domain logic的 service 逻辑也被强行放入 domain object,引起了domain ojbect模型的不稳定
  2. domain object 暴露给web层过多的信息,可能引起意想不到的副作用。

小结

在这四种模型当中,失血模型和胀血模型应该是不被提倡的。而贫血模型和充血模型从技术上来说,都已经是可行的了。贫血模型和充血模型哪个更加好一些?人们针对这个问题进行了旷日持久的争论,最后仍然没有什么结果。双方争论的焦点主要在我上面加粗的两句话上,就是领域模型是否要依赖持久层,因为依赖持久层就意味着单元测试的展开要更加困难(无法脱离框架进行测试,原文的讨论中这里专指Hibernate),领域层就更难独立,将来也更难从应用程序中剥离出来,当然好处是业务逻辑不必混放在不同的层中,使得单一职责性体现的更好。而支持者(充血模型)认为,只要将持久层抽象出来,即可减少测试的困难性,同时适用充血模型毕竟带来了不少开发上的便利性,除了依赖持久层这一点,拥有更多好处的充血模型仍然值得选择。最后,谁也没能说服谁,关于贫血模型和充血模型的选择,更多的要靠具体的业务场景来决定,并不能说哪一种更比哪一种好。设计模式这种东西不是向来都没有什么定论么。

我个人则倾向使用充血模型,因为充血模型更加像一个设计完善的系统架构,好在计算机世界里有很多的 IOC 和 DI 框架,唯一的缺陷依赖持久层可以通过各种变通的方法绕过,随着技术的进步,一些缺陷也会被慢慢解决。我的思路是这样的:先将持久层抽象为接口,然后通过服务层将持久层注入到领域模型中,这样领域模型仅仅会依赖于持久层的接口。而这个接口,可以利用现有框架的技术进行抽象。

订阅最新文章,欢迎关注我的公众号:aoho 求索

参考

  1. 【DDD】业务建模实践 —— 删除帖子
  2. 贫血,充血模型的解释以及一些经验
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。