在当前的开发者社区,广泛流行一种被Martin Fowler称为贫血领域模型的构架模式。该模式由于大师的批判而饱受指责。这个模式有个致命的缺陷:在处理复杂领域时常常表现不佳。很多迹象表明,当我们面对复杂应用时,最好还是转向一个基于丰富领域模型的构架。
尽管丰富领域模型有着显而易见的好处,但也给实践带来了挑战,这既有构建技术上的原因,也有设计方法上的原因。对于构建技术,如Annotation、Aspect和DI等复杂技术的使用,最终能够清晰的被掌握,但在设计方法上,往往由于实践的不同而难以取得共识。
本文的目的仅仅是在技术上给出一种由贫血领域模型向丰富领域模型转换的方案,供有相同需要的同行参考。
1.前提和限制<o:p></o:p>
无论使用哪种领域模型,通常都需要借助一些工具的支持,这些工具包括Ioc容器和O/R映射工具。因作者经验所限,当提及这些工具时,只意味着Java世界里的Spring和Hibernate。
有关贫血领域模型的论述请参考:
http://www.martinfowler.com/bliki/AnemicDomainModel.html
贫血领域模型的最佳实践请参考:
丰富领域模型的最权威指南请访问:
http://www.domaindrivendesign.org
由于语境的不同,不同的分层模式的术语具有不同的含义,但它们所要实现的任务,是可以分离出来的,而这些任务,目前还没有造成广泛的语义混乱。为了更好的比较本文中提及的两种构架模式,将它们所要完成的任务定义如下:
任务 |
描述 |
表现逻辑 |
接收来自系统外部的请求,将请求代理到其他模块,并将返回的处理结果,通过某种方式呈现给请求者。 |
应用逻辑 |
是用例的外观的实现,协调用例的真正实现者完成一次应用程序相关的功能。 |
领域逻辑 |
对问题领域最本质内容进行建模,实现体现用户核心价值的功能。 |
持久化逻辑 |
与数据的外部存储交互。 |
基础服务 |
更加的以技术为中心,为软件系统的各模块提供基础支持。 |
2.贫血领域模型构架<o:p></o:p>
2.1.分层模式<o:p></o:p>
即使同样打着贫血模型的标签,它们也会有不同的风格。下面是比较典型的一种:
层 |
任务 |
对象 |
描述 |
表现层 |
表现逻辑 |
模型对象 |
Model。领域层中的实体/值对象,也可以用独立的对象。 |
视图 |
View |
||
控制器 |
Controller。通过某种机制而获得对用户请求的响应。 |
||
领域层 |
在领域逻辑中混合了应用逻辑 |
服务 |
Service。同时处理领域逻辑和应用逻辑 |
实体 |
Entity。领域模型的静态视图 |
||
数据源层 |
持久化逻辑 |
DAO对象 |
有很多实现方式,如JDBC、Hibernate、iBATIS、JPA、EJB CMP |
基础设施层 |
基础服务 |
<o:p> </o:p> |
<o:p> </o:p> |
可以参考下面的示意图:[见附件1]
2.2.示例代码<o:p></o:p>
现在假设有一个在线购物网站,我们要浏览产品列表,然后选中了一个感兴趣的产品,此时我们需要查看该产品的详细信息。
1)页面发出请求
下面是一个可能的JSF代码片断:
<h:commandlink value="#{product.name}" action="#{product.edit}"></h:commandlink> <f:param value="#{product.id}" name="productid"></f:param>
|
2)分派到控制器
假设请求被调度到控制器yourpackage.action.ProductAction:
package yourpackage.action;<o:p></o:p> ......<o:p></o:p> public class ProductAction extends BaseAction {<o:p></o:p> //通过依赖注入的服务<o:p></o:p> private ProductService productService;<o:p></o:p> //作为模型对象的领域对象<o:p></o:p> private Product product;<o:p></o:p> private Integer id;<o:p></o:p> //响应查看产品信息的事件<o:p></o:p> public String edit() {<o:p></o:p> product = productService.getProduct(id);<o:p></o:p> return “product”; //JSF将其解析为product.xhtml视图<o:p></o:p> }<o:p></o:p> } |
3)领域层的服务
一般会有一个服务接口ProductService,然后是该接口的实现ProductServiceImpl:
package yourpackage.service; ...... public class ProductServiceImpl extends BaseService implements ProductService { //通过依赖注入的DAO对象 private ProductDao productDao; //获取产品信息的领域逻辑方法 public Product getProduct(Integer id) { return productDao.getProduct(id); } } |
4)领域层的实体
下面是JPA风格的实体,该实体仅用于承载数据,而没有领域行为(贫血之说由此而来)。
package yourpackage.model; ...... @Entity public class Product { @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="Id") public Long getId() { return id; } <o:p> </o:p> @Column(name="Name", length=30, nullable=false) public String getName() { return name; } } |
5)DAO对象
这里同样需要一个接口,其实现类如果用JPA的话:
package yourpackage.dao; ...... public class ProductDaoJpa extends BaseDaoJpa implements ProductDao { public Product getProduct(Integer id) { return (Product) getJpaTemplate().find(Product.class, id);; } ...... } |
2.3.分析<o:p></o:p>
优点:
* 获得了分层的最基本的好处。
* 易于理解,快速掌握:没有采用更复杂的技术,并且有丰富的示例资源。
* 有广泛的工具支持,非常容易的从Spring/Hibernate类型的框架获益。
* 适合于模型简单,以CRUD操作为主的领域。
缺点:
* 模型的领域表达能力欠缺。
* 代码职责分配不合理。
3. 丰富领域模型<o:p></o:p>
3.1分层模式<o:p></o:p>
典型的DDD风格的分层模式如下:
层 |
任务 |
对象 |
描述 |
表现层 |
表现逻辑 |
模型对象 |
领域层中的实体/值对象,也可以用独立的对象。 |
视图 |
<o:p> </o:p> |
||
控制器 |
<o:p> </o:p> |
||
应用层 |
应用逻辑 |
服务 |
对用例建模 |
领域层 |
领域逻辑 |
服务 |
对领域操作建模 |
实体 |
对领域概念建模、并可被持久化 |
||
值对象 |
Value Object。对领域概念建模 |
||
存储库 |
Repository。隔离持久化 |
||
基础设施 |
持久化逻辑和基础服务 |
数据映射 |
通常以DAO模式封装持久化操作 |
基础服务 |
|
http://api.blogs.com/the_catch_blog/2005/05/protecting_the_.html
这些做法在一定程度上弥补了直接传递领域对象带来的负面影响。
3.2有什么不同?<o:p></o:p>
从上面的表格我们可以发现,要做的事还是那些事,只不过部分职责被重新分配了而已。相对于贫血领域模型,在新的分层模式中:
* 不变的是表现层;
* 被合并的是数据源层,现在成了基础设施的一部分;
* 增加的是应用层;
* 被重新组织的是领域层,其职责被分配到相应的对象中:实体、值对象、服务和存储库。
这里最大的变化有三个:
1)应用逻辑被从领域逻辑中独立出来,形成了新的应用层。该层的接口按照用例进行设计,因此粒度较大;该层反映的是用系统所实现的任务,如果需要,也能反映工作流程。
2)领域层只反映领域逻辑中最核心部分,因此也是最复杂的(如果领域复杂程度超过技术复杂程度的话)。其中的实体不但持有数据,还具备丰富的行为;服务是一些领域相关的操作(这不同于应用层的服务,更不同于基础设施层的服务);通过存储库隔离了与数据技术的联系。
3)领域层可以独立地访问其他层的服务和资源。这是一个有争议的话题,同时很有技术上的挑战性,下文会通过实例代码进行说明。