在上一篇文章中,经过Spring Web应用的瑕疵引出改善的措施,咱们讲解了领域驱动开发的相关概念和设计策略。本文主要讲解领域模型的几种类型和DDD的简单实践案例。html
在《实现领域驱动设计》一书中提到了几种架构风格:六边形架构、REST架构、CQRS 和事件驱动等。在实际使用中,落地的架构并不是是纯粹其中的一种,而颇有可能户将上述几种架构风格结合起来实现。java
分层架构的一个重要原则是每层只能与位于其下方的层发生耦合。分层架构能够简单分为两种,即严格分层架构和松散分层架构。在严格分层架构中,某层只能与位于其直接下方的层发生耦合,而在松散分层架构中,则容许某层与它的任意下方层发生耦合。DDD分层架构中比较经典的三种模式:四层架构、五层架构和六边形架构。程序员
Eric Evans在《领域驱动设计-软件核心复杂性应对之道》这本书中提出了传统的四层架构模式:web
传统的四层架构都是限定型松散分层架构,即Infrastructure层的任意上层均可以访问该层(“L”型),而其它层遵照严格分层架构。算法
五层架构是根据《DCI架构:面向对象编程的新构想》中说起的DCI架构模式总结而成。DCI架构(Data、Context和Interactive三层架构):编程
DCI目前普遍被看做是对DDD的一种发展和补充,用在基于面向对象的领域建模上。五层架构的具体定义以下:设计模式
六边形架构(Hexagonal Architecture),又称为端口和适配器风格,最先由 Alistair Cockburn 提出。在 DDD 社区获得了发展和推广,之因此是六变形是为了突显这是个扁平的架构,每一个边界的权重是相等的。ruby
咱们知道,经典分层架构分为三层(展示层、应用层、数据访问层),而对于六边形架构,能够分红另外的三层:网络
这样作的好处是将使业务边界更加清晰,从而得到更好的扩展性,除此以外,业务复杂度和技术复杂度分离,是 DDD 的重要基础,核心的领域层能够专一在业务逻辑而不用理会技术依赖,外部接口在被消费者调用的时候也不用去关心业务内部是如何实现。多线程
RESTful风格的架构将 资源
放在第一位,每一个 资源
都有一个 URI 与之对应,能够将 资源
看着是 DDD 中的实体;RESTful 采用具备自描述功能的消息实现无状态通讯,提升系统的可用性;至于 资源
的哪些属性能够公开出去,针对 资源
的操做,RESTful使用HTTP协议的已有方法来实现:GET、PUT、POST和DELETE。
在 DDD 的实现中,咱们能够将对外的服务设计为 RESTful 风格的服务,将实体/值对象/领域服务做为资源
对外提供增删改查服务。可是并不建议直接将实体暴露在外,一来实体的某些隐私属性并不能对外暴露,二来某些资源获取场景并非一个实体就能知足。所以咱们在实际实践过程当中,在领域模型上增长了 DTO 这样一个角色,DTO 能够组合多个实体/值对象的资源对外暴露。
CQRS 就是日常你们在讲的读写分离,一般读写分离的目的是为了提升查询性能,同时达到读/写的解耦。让 DDD 和 CQRS 结合,咱们能够分别对读和写建模,查询模型一般是一种非规范化数据模型,它并不反映领域行为,只是用于数据显示;命令模型执行领域行为,且在领域行为执行完成后,想办法通知到查询模型。
那么命令模型如何通知到查询模型呢? 若是查询模型和领域模型共享数据源,则能够省却这一步;若是没有共用数据源,则能够借助于 消息模式
(Messaging Patterns)通知到查询模型,从而达到最终一致性(Eventual Consistency)。
Martin 在 blog 中指出:CQRS 适用于极少数复杂的业务领域,若是不是很适合反而会增长复杂度;另外一个适用场景为获取高性能的服务。
在上面小节讲解了领域驱动设计的几种架构风格,下面咱们具体结合简单的实例来看其中的领域模型划分,初步分为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
这种模型的优势:
缺点为:
具体代码较为简单,再也不展现。
充血模型和第二种模型差很少,所不一样的就是如何划分业务逻辑,即认为,绝大多业务逻辑都应该被放在domain object里面(包括持久化逻辑),而Service层应该是很薄的一层,仅仅封装事务和少许逻辑,不和DAO层打交道。
Service(事务封装) ---> domain object <---> DAO
这种模型就是把第二种模型的 domain object 和 business object 合二为一了。因此 ItemManager 就不须要了,在这种模型下面,只有三个类,他们分别是:
在这种模型中,全部的业务逻辑所有都在Item中,事务管理也在Item中实现。 这种模型的优势:
这种模型的缺点:
基于充血模型的第三个缺点,有同窗提出,干脆取消Service层,只剩下domain object和DAO两层,在domain object的domain logic上面封装事务。
domain object(事务封装,业务逻辑) <---> DAO
彷佛ruby on rails就是这种模型,他甚至把 domain object 和 DAO 都合并了。
该模型优势:
该模型缺点:
在这四种模型当中,失血模型和胀血模型应该是不被提倡的。而贫血模型和充血模型从技术上来讲,都已是可行的了。贫血模型和充血模型哪一个更加好一些?人们针对这个问题进行了旷日持久的争论,最后仍然没有什么结果。双方争论的焦点主要在我上面加粗的两句话上,就是领域模型是否要依赖持久层,由于依赖持久层就意味着单元测试的展开要更加困难(没法脱离框架进行测试,原文的讨论中这里专指Hibernate),领域层就更难独立,未来也更难从应用程序中剥离出来,固然好处是业务逻辑没必要混放在不一样的层中,使得单一职责性体现的更好。而支持者(充血模型)认为,只要将持久层抽象出来,便可减小测试的困难性,同时适用充血模型毕竟带来了很多开发上的便利性,除了依赖持久层这一点,拥有更多好处的充血模型仍然值得选择。最后,谁也没能说服谁,关于贫血模型和充血模型的选择,更多的要靠具体的业务场景来决定,并不能说哪种更比哪种好。设计模式这种东西不是向来都没有什么定论么。
我我的则倾向使用充血模型,由于充血模型更加像一个设计完善的系统架构,好在计算机世界里有不少的 IOC 和 DI 框架,惟一的缺陷依赖持久层能够经过各类变通的方法绕过,随着技术的进步,一些缺陷也会被慢慢解决。个人思路是这样的:先将持久层抽象为接口,而后经过服务层将持久层注入到领域模型中,这样领域模型仅仅会依赖于持久层的接口。而这个接口,能够利用现有框架的技术进行抽象。