`

从贫血领域模型到丰富领域模型

阅读更多

参考: http://www.uml.org.cn/mxdx/200907132.asp

 

 

在当前的开发者社区,广泛流行一种被Martin Fowler称为贫血领域模型的构架模式。该模式由于大师的批判而饱受指责。这个模式有个致命的缺陷:在处理复杂领域时常常表现不佳。很多迹象表明,当我们面对复杂应用时,最好还是转向一个基于丰富领域模型的构架。

尽管丰富领域模型有着显而易见的好处,但也给实践带来了挑战,这既有构建技术上的原因,也有设计方法上的原因。对于构建技术,如Annotation、Aspect和DI等复杂技术的使用,最终能够清晰的被掌握,但在设计方法上,往往由于实践的不同而难以取得共识。

本文的目的仅仅是在技术上给出一种由贫血领域模型向丰富领域模型转换的方案,供有相同需要的同行参考。

1.前提和限制

无论使用哪种领域模型,通常都需要借助一些工具的支持,这些工具包括Ioc容器和O/R映射工具。因作者经验所限,当提及这些工具时,只意味着Java世界里的Spring和Hibernate。

有关贫血领域模型的论述请参考:

http://www.martinfowler.com/bliki/AnemicDomainModel.html

贫血领域模型的最佳实践请参考:

https://appfuse.dev.java.net

丰富领域模型的最权威指南请访问:

http://www.domaindrivendesign.org

由于语境的不同,不同的分层模式的术语具有不同的含义,但它们所要实现的任务,是可以分离出来的,而这些任务,目前还没有造成广泛的语义混乱。为了更好的比较本文中提及的两种构架模式,将它们所要完成的任务定义如下:

任务

描述

表现逻辑

接收来自系统外部的请求,将请求代理到其他模块,并将返回的处理结果,通过某种方式呈现给请求者。

应用逻辑

是用例的外观的实现,协调用例的真正实现者完成一次应用程序相关的功能。

领域逻辑

对问题领域最本质内容进行建模,实现体现用户核心价值的功能。

持久化逻辑

与数据的外部存储交互。

基础服务

更加的以技术为中心,为软件系统的各模块提供基础支持。

2.贫血领域模型构架

2.1.分层模式

即使同样打着贫血模型的标签,它们也会有不同的风格。下面是比较典型的一种:

任务

对象

描述

表现层

表现逻辑

模型对象

Model。领域层中的实体/值对象,也可以用独立的对象。

视图

View

控制器

Controller。通过某种机制而获得对用户请求的响应。

领域层

在领域逻辑中混合了应用逻辑

服务

Service。同时处理领域逻辑和应用逻辑

实体

Entity。领域模型的静态视图

数据源层

持久化逻辑

DAO对象

有很多实现方式,如JDBC、Hibernate、iBATIS、JPA、EJB CMP

基础设施层

基础服务

 

 

2.2.示例代码

现在假设有一个在线购物网站,我们要浏览产品列表,然后选中了一个感兴趣的产品,此时我们需要查看该产品的详细信息。

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;

......

public class ProductAction extends BaseAction {

    //通过依赖注入的服务

private ProductService productService;

//作为模型对象的领域对象

private Product product;

private Integer id;

//响应查看产品信息的事件

public String edit() {

        product = productService.getProduct(id);

return “product”;  //JSF将其解析为product.xhtml视图

}

}

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;

       }

 

       @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.分析

优点:

* 获得了分层的最基本的好处。

* 易于理解,快速掌握:没有采用更复杂的技术,并且有丰富的示例资源。

* 有广泛的工具支持,非常容易的从Spring/Hibernate类型的框架获益。

* 适合于模型简单,以CRUD操作为主的领域。

缺点:

* 模型的领域表达能力欠缺。

* 代码职责分配不合理。

3. 丰富领域模型

3.1分层模式

典型的DDD风格的分层模式如下:

任务

对象

描述

表现层

表现逻辑

模型对象

领域层中的实体/值对象,也可以用独立的对象。

视图

 

控制器

 

应用层

应用逻辑

服务

对用例建模

领域层

领域逻辑

服务

对领域操作建模

实体

对领域概念建模、并可被持久化

值对象

Value Object。对领域概念建模

存储库

Repository。隔离持久化

基础设施

持久化逻辑和基础服务

数据映射

通常以DAO模式封装持久化操作

基础服务

 

在实践中,我个人更倾向于将数据映射从基础设施层分离出来,这样会有一个更清晰的层次结构。

无论哪种领域模型,在是否将领域对象传递到表现层作为模型数据的问题上一直存在争议。要做出恰当的设计决定,需要在灵活性和严谨性之间做出平衡。有一些技术上的方法可以使领域对象在作为DTO对象进行传递时得到保护,可以参考下面的文章《Protecting the Domain Model》:

http://api.blogs.com/the_catch_blog/2005/05/protecting_the_.html

这些做法在一定程度上弥补了直接传递领域对象带来的负面影响。

3.2有什么不同?

从上面的表格我们可以发现,要做的事还是那些事,只不过部分职责被重新分配了而已。相对于贫血领域模型,在新的分层模式中:

* 不变的是表现层;

* 被合并的是数据源层,现在成了基础设施的一部分;

* 增加的是应用层;

* 被重新组织的是领域层,其职责被分配到相应的对象中:实体、值对象、服务和存储库。

这里最大的变化有三个:

1)应用逻辑被从领域逻辑中独立出来,形成了新的应用层。该层的接口按照用例进行设计,因此粒度较大;该层反映的是用系统所实现的任务,如果需要,也能反映工作流程。

2)领域层只反映领域逻辑中最核心部分,因此也是最复杂的(如果领域复杂程度超过技术复杂程度的话)。其中的实体不但持有数据,还具备丰富的行为;服务是一些领域相关的操作(这不同于应用层的服务,更不同于基础设施层的服务);通过存储库隔离了与数据技术的联系。

3)领域层可以独立地访问其他层的服务和资源。这是一个有争议的话题,同时很有技术上的挑战性,下文会通过实例代码进行说明。

4.重构到丰富领域模型构架

4.1.技术方案

为了简便起见,下文中使用A和B这两个别名分别代表“贫血领域模型”和“丰富领域模型”,使用名称空间表示模型中的层,如B::应用层表示丰富领域模型的应用层。

通过前面的比较,可以很容易的得到下面转换的方案:

1)保持表现层不变

2)分离出来应用层。从A::领域层移出应用逻辑形成B::应用层。

3)重构一个纯粹的领域层。将A::领域层的领域逻辑部分进行分解:

    # 将概念性的逻辑重构到B::领域层.实体和B::领域层.值对象;
    # 将操作性逻辑重构到B::领域层.服务;
    # 将数据源访问抽象为存储库接口,存储库的实现由Ioc容器注入。

4)重构数据访问对象,使其实现领域层的存储库接口。

5)基础设施层保持不变。

不多做解释,下面通过代码进行说明。

4.2. 示例代码

还是前面的在线购物网站,但这次使用了一个稍微能体现领域逻辑的用例:列出与当前产品相关的其他产品。

1)页面发出请求

下面是一个可能的JSF代码片断:

  1. <h:commandLink action="#{product.related}" value="#{相关产品}">  
  2.     <f:param name="productid" value="#{product.id}" />  
  3. weaver>  
  4. aspectj>  

然后,需要在JVM加载参数中指定下面的内容:

-javaagent: <path-to-ajlibs>/aspectjweaver.jar</path-to-ajlibs>

现在可以说大功告成了。

5)DAO对象

现在DAO需要实现的接口变成了领域层的ProductRepository:

package yourpackage.dao;

import yourpackage.domain;

......

public class ProductDaoJpa extends BaseDaoJpa implements ProductRepository {

       public Product getProduct(Integer id) {

              return (Product) getJpaTemplate().find(Product.class, id);;

       }

       public Product getRelatedProduct(Integer id) {

              //这里是具体的实现代码

       }

}

5.结论

要构建一个纯粹的领域模型,往往需要在领域对象中直接使用外部服务,如数据库访问,外部资源,或其他必要的服务。在没有强大的Annotation、AOP及DI技术的支持下,这些外部服务或资源很难被注入到领域对象中,由此形成了贫血模型的设计风格。

特别地,对于Spring用户来说,在2.0版发布之后,问题的解决变得更容易一些。利用Spring集成的AspectJ实现向领域对象进行依赖注入,可以使领域对象可以表达更丰富的逻辑,从而过渡到丰富领域模型。在这个关键问题解决之后,剩下的问题也就迎刃而解了。

 

分享到:
评论

相关推荐

    对贫血和充血模型的理解

    贫血模型和充血模型是两种在软件开发,尤其是面向对象编程中常见的设计策略,主要应用于领域驱动设计(Domain-Driven Design, DDD)中。这两种模型主要关注于业务逻辑和数据之间的关系,以及如何在软件架构中有效地...

    基于GO的六边形架构框架,可支撑充血的领域模型范式代码实现.rar

    本资料主要关注的是使用Go语言实现的一个六边形架构框架,它特别适用于支持充血的领域模型范式。让我们深入探讨这些概念以及它们如何相互关联。 首先,我们来了解什么是六边形架构,也被称为端口和适配器架构。这种...

    领域驱动设计和开发实战.pdf

    DDD提出的富领域模型与贫血领域模型形成鲜明对比。贫血领域模型中实体只是数据载体,没有业务逻辑;而富领域模型则包含业务逻辑,是模块化、可扩展的,易于维护。 领域模型的典型特征包括: 1. 领域模型与业务中的...

    DDD领域驱动设计day02.pdf

    领域驱动设计中的领域模型包括充血模型和贫血模型两种不同的建模方式。贫血模型主要存在于传统分层架构中,其特点是实体类中几乎没有业务逻辑,主要通过getter和setter方法来访问属性。这种模式下,业务逻辑分散在...

    充血模型设想实现(2010/07/30更新)

    在软件开发中,领域模型是对业务领域的抽象和建模,它包含业务规则、逻辑和状态。充血模型强调对象应该拥有自己的行为和状态,而不是简单地作为数据容器。这个模型与贫血模型相对,后者通常由无行为的POJO(Plain ...

    领域驱动设计中的实现方式

    领域驱动设计(Domain-Driven Design,简称 DDD)是一种软件开发方法,强调通过深入理解和...然而,在复杂的业务环境中,考虑使用更丰富的领域模型,如富领域对象或聚合根,可以更好地捕捉业务逻辑并增强模型的表达力。

    浅谈Asp.net中使用“充血模型”1

    在Asp.net开发中,"充血模型"是一种提倡领域对象拥有丰富行为和业务逻辑的设计模式,相对应于传统的"贫血模型"。"贫血模型"通常将数据模型、业务逻辑和数据访问分离,使得领域对象仅包含属性,而业务逻辑和数据操作...

    Adaptive Multi-Task Transfer Learning

    Zhu、Shaodian Zhang来自上海交通大学,他们提出了一种方法,该方法能够利用资源丰富的领域到资源贫乏领域的领域不变知识。经过广泛的实验验证,他们的模型在性能上一致地超越了单一任务的CWS和其它迁移学习基线...

    全血细胞计数 (CBC).zip

    背景描述 该数据集是全血细胞计数 (CBC) 图像的综合集合,经过精心组织以支持机器学习和深度学习项目,尤其是在医学图像...该数据集提供了丰富的 CBC 图像来源,可用于训练机器学习模型,以自动分析和解释这些测试。

    血细胞检测数据集yolo格式

    这些标注文件是模型训练的关键,确保模型能学习到细胞的特征并正确区分不同的细胞类型。 训练过程中,数据通常会被划分为训练集、验证集和测试集。训练集用于训练模型,验证集用于调整模型参数,而测试集则用来评估...

    JavaEye论坛热点月报 总第7期

    8. **贫血模型与领域模型** - 讨论了一个简单的示例,比较了贫血模型(Anemic Domain Model)和领域模型(Domain-Driven Design)在Java应用中的应用和优缺点。 9. **Spring中的DAO与Service** - 在Spring框架中,...

    支付场景微服务实战

    - **限界上下文**: 是 DDD 中的一个概念,用来明确地界定不同领域模型的作用范围,有助于解决模型冲突和复杂度问题。 - **实体与值对象**: 实体拥有唯一标识符,其状态会随时间变化;而值对象则没有唯一标识符,它们...

    BCCD_Dataset-master.zip

    在医学图像分析领域,这类数据集对于自动化血液检查和疾病诊断具有重大意义,尤其是对于那些需要快速、精确检测的病症,如贫血、白血病等。BCCD数据集因其小巧且结构明确,成为研究者和开发者入门目标检测算法的首选...

    基于SpringMVC实现的秒杀系统.zip

    - **Model**: 秒杀商品的信息存储在数据库中,可以通过贫血模型或富模型来表示。贫血模型只包含基本属性,而富模型还包含了业务逻辑。 - **View**: 用户界面通常由JSP、HTML或者Thymeleaf等技术渲染,展示商品信息...

    hobigon-golang-api-server:我的Hobby API服务器(Golang + DDD +分层体系结构)

    Hobigon服务器 爱好+ = Hobigon Hobigon是使我的生活变得丰富多彩的服务器。 这只是一种爱好。 我重做了用Ruby(Rails)制作的。 -&gt; (对不起,这是私人) Hobigon具有三个接口: Web...改善贫血领域模型 松弛 任务

    BCCD血细胞图像数据集

    这个数据集包含了大量的血细胞图像,为机器学习和深度学习算法提供了丰富的训练和测试素材,旨在推动医学成像技术的发展,特别是在血液检测和疾病诊断方面的应用。 **数据集内容** BCCD数据集通常由多个子文件夹...

    hibernate培训笔记.docx

    Hibernate不仅仅是一个ORM框架,它还包含了丰富的持久层设计模式,如单位-of-work、数据访问对象(DAO)、贫血模型和富模型等。这些模式帮助开发者更好地组织和管理数据库操作。 ### 持久层设计 持久层是应用程序...

    企业应用程序架构模式:基于Martin Fowler的企业应用程序架构模式的解释

    6. **事务脚本与领域模型**:对于简单业务逻辑,事务脚本模式直接在服务层处理;复杂的业务逻辑则更适合使用领域模型,通过模拟业务领域的概念和规则。 7. **事件驱动架构**:在大型分布式系统中,事件驱动架构允许...

    java仓库管理系统期末课程设计

    他们还将接触到软件工程中的设计模式,如贫血模型和富模型,以及如何使用版本控制工具(如Git)进行协作开发。最后,通过测试用例的编写和执行,学生可以学习到单元测试和集成测试的重要性,确保代码的质量和系统的...

Global site tag (gtag.js) - Google Analytics