跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

DDD 领域驱动设计:失血、贫血、充血与胀血模型详解及代码示例

失血、贫血、充血与胀血模型是领域对象设计的四种形态。失血模型仅含属性,业务逻辑全在服务层;贫血模型增加行为但不依赖持久层;充血模型包含大部分业务及持久化逻辑;胀血模型将所有逻辑放入实体。文章通过 Java 代码对比了各模型实现,分析了 DDD 分层架构,并指出贫血模型因隔离持久化技术更利于维护而值得推荐。

松间照月发布于 2025/2/23更新于 2026/6/1321 浏览
DDD 领域驱动设计:失血、贫血、充血与胀血模型详解及代码示例

领域模型分为 4 大类:失血模型、贫血模型、充血模型、胀血模型。这类理论都是由软件设计领域的大牛(如 Martin Fowler)提出来的,有其背景和原因。

想要理解这几个分类,先要知道**'血'指的是 Domain Object 的 Domain 层内容**。

失血模型

Domain Object(领域对象)模型仅仅包含对象属性的定义和操作对象属性的getter/setter 方法,**所有的业务逻辑完全由 Business Logic 层 (业务逻辑层) 中的服务类来完成。**这种类在 java 中叫 POJO,在.NET 中叫 POCO。

优点

领域对象结构简单

缺点

肿胀的业务服务代码逻辑,难于理解和维护

无法良好的应对复杂业务逻辑和场景

贫血模型

Domain Object(领域对象)模型包含对象属性的定义和操作对象属性的getter/setter 方法并包含了对象的行为(例如:就像一个完整的人,具有一些属性如姓名、性别、年龄等,还具有一些能力,如走路、吃饭、恋爱等,这样才是一个完整的对象), 但不包含依赖 Dao 层 (持久层) 的业务逻辑。这部分依赖于 Dao 层的业务逻辑将会放到Business Logic 层(业务逻辑层)中的服务类来实现,组合逻辑也由服务类负责。可以看出,贫血模型中的领域对象是不依赖于持久层的。代码架构层次结构是: Client-> Business Facade Service -> Business Logic Service(Business Logic Service 是依赖 Domain Object 的行为) -> Data Access Service

优点

层次结构清楚,各层之间单向依赖

对于只有少量业务逻辑的应用来说,使用起来非常自然

开发迅速,易于理解

缺点

  • 无法良好的应对非常复杂逻辑和场景

充血模型

Domain Object(领域对象)模型包含对象属性的定义和操作对象属性的getter/setter 方法并包含了大多数相关的业务逻辑,也包含了依赖于持久层的业务逻辑,Business Logic 层是很薄的一层,仅仅简单封装少量业务逻辑以及控制事务、权限逻辑等,不和 DAO 层打交道。所以**,使用充血模型的领域对象是依赖于持久层的。代码架构层次结构是**: Client-> Business Facade Service -> Business Logic Service -> Domain Object -> Data Access Service

优点

  • 更加符合 OO 的原则

Business Logic 层很薄,**符合单一职责,不像在贫血模型里面那样包含所有的业务逻辑太过沉重,**只充当 Facade 的角色,不和 DAO 打交道。

缺点

  • 什么样的逻辑应该放在 Domain Object 中,什么样的业务逻辑应该放在 Business Logic 中,这是很含糊的。即使划分好了业务逻辑,由于分散在 Business Logic 和 Domain Object 层中,不能更好的分模块开发。熟悉业务逻辑的开发人员需要渗透到 Domain Logic 中去,而在 Domian Logic 又包含了持久化,对于开发者来说这十分混乱。
  • 其次,因为 Business Logic 要控制事务并且为上层提供一个统一的服务调用入口点,它就必须把在 Domain Logic 里实现的业务逻辑全部重新包装一遍,完全属于重复劳动。

胀血模型

Domain Object(领域对象)模型包含对象属性的定义和操作对象属性的getter/setter 方法并包含了所有相关的的业务逻辑,也包含了不想关的其它应用逻辑(如授权、事务等)。胀血模型取消了 Business Logic 层 (业务逻辑层),只剩下 Domain Object 和 DAO 两层,在 Domain Object 的 Domain Logic 上面封装事务,授权逻辑等。

优点

  • 简化了代码分层结构
  • 也算符合面向对象设计

缺点

  • 取消了 Business Logic 层 (业务逻辑层),在 Domain Object 的 Domain Logic 上面封装事务,授权等很多本不应该属于领域对象的逻辑,使业务逻辑再次进行到混论的状态,引起了 Domain Object 模型的不稳定
  • 代码理解和维护性差

失血模型代码样例

下面用举一个具体的代码来说明:

  • 一个实体类叫做 Item,指的是一个拍卖项目
  • 一个 DAO 接口类叫做 ItemDao
  • 一个 DAO 接口实现类叫做 ItemDaoHibernateImpl
  • 一个业务逻辑类叫做 ItemManager(或者叫做 ItemService)

Java 代码:

public class Item implements Serializable { 
     private Long id = null; 
     private int version; 
     private String name; 
     private User seller; 
     private String description; 
     private MonetaryAmount initialPrice; 
     private MonetaryAmount reservePrice; 
     private Date startDate; 
     private Date endDate; 
     private Set categorizedItems = new HashSet(); 
     private Collection bids = new ArrayList(); 
     private Bid successfulBid; 
     private ItemState state; 
     private User approvedBy; 
     private Date approvalDatetime; 
     private Date created = new Date(); 
     //getter/setter 方法省略不写,避免篇幅太长 
}

Java 代码:

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

ItemDao 定义持久化操作的接口,用于隔离持久化代码。

Java 代码:

public class ItemDaoHibernateImpl implements ItemDao extends HibernateDaoSupport { 
     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); 
     } 
}

ItemDaoHibernateImpl 完成具体的持久化工作,请注意,数据库资源的获取和释放是在 ItemDaoHibernateImpl 里面处理的,每个 DAO 方法调用之前打开 Session,DAO 方法调用之后,关闭 Session。(Session 放在 ThreadLocal 中,保证一次调用只打开关闭一次)

Java 代码:

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."); 
     } 
    
     // Auction is active 
     if ( !state.equals(ItemState.ACTIVE) ) 
             throw new BusinessException("Auction is not active yet."); 
    
     // Auction still valid 
     if ( item.getEndDate().before( new Date() ) ) 
             throw new BusinessException("Can't place new bid, auction already ended."); 
    
     // Create new Bid 
     Bid newBid = new Bid(bidAmount, item, bidder); 
    
     // Place bid for this Item 
     item.getBids().add(newBid); 
     itemDao.update(item);      //   调用 DAO 完成持久化操作 
     return newBid; 
     } 
}

事务的管理是在 ItemManger 这一层完成的,ItemManager 实现具体的业务逻辑。除了常见的和 CRUD 有关的简单逻辑之外,这里还有一个 placeBid 的逻辑,即项目的竞标。

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

贫血模型代码样例

下面用举一个具体的代码来说明:

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

Java 代码:

public class Item implements Serializable { 
     //   所有的属性和 getter/setter 方法同上,省略 
     public Bid placeBid(User bidder, MonetaryAmount bidAmount, 
                         Bid currentMaxBid, Bid currentMinBid) 
             throws BusinessException { 
    
             // Check highest bid (can also be a different Strategy (pattern)) 
             if (currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0) { 
                     throw new BusinessException("Bid too low."); 
             } 
    
             // Auction is active 
             if ( !state.equals(ItemState.ACTIVE) ) 
                     throw new BusinessException("Auction is not active yet."); 
    
             // Auction still valid 
             if ( this.getEndDate().before( new Date() ) ) 
                     throw new BusinessException("Can't place new bid, auction already ended."); 
    
             // Create new Bid 
             Bid newBid = new Bid(bidAmount, this, bidder); 
    
             // Place bid for this Item 
             this.getBids.add(newBid);   // 请注意这一句,透明的进行了持久化,但是不能在这里调用 ItemDao,Item 不能对 ItemDao 产生依赖!return newBid; 
     } 
}

**竞标这个业务逻辑被放入到 Item 中来。**请注意 this.getBids.add(newBid); 如果没有 Hibernate 或者 JDO 这种 O/R Mapping 的支持,我们是无法实现这种透明的持久化行为的。但是请注意,Item 里面不能去调用 ItemDAO,对 ItemDAO 产生依赖!

Java 代码:

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

ItemDao 定义持久化操作的接口,用于隔离持久化代码。

Java 代码:

public class ItemDaoHibernateImpl implements ItemDao extends HibernateDaoSupport { 
     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); 
     } 
}

ItemDaoHibernateImpl 完成具体的持久化工作

Java 代码:

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 { 
         item.placeBid(bidder, bidAmount, currentMaxBid, currentMinBid); 
         itemDao.update(item);     // 必须显式的调用 DAO,保持持久化 
     } 
}

充血模型代码样例

下面用举一个具体的代码来说明:

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

Java 代码:

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

ItemDao 定义持久化操作的接口,用于隔离持久化代码。

Java 代码:

public class ItemDaoHibernateImpl implements ItemDao extends HibernateDaoSupport { 
     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); 
     } 
}

ItemDaoHibernateImpl 完成具体的持久化工作

Java 代码:

public class Item implements Serializable { 
    //所有的属性和 getter/setter 方法都省略 
    private static ItemDao itemDao; 
     public void setItemDao(ItemDao itemDao) {this.itemDao = itemDao;} 
    
     public static Item loadItemById(Long id) { 
         return (Item) itemDao.loadItemById(id); 
     } 
     public static Collection findAll() { 
         return (List) itemDao.findAll(); 
     } 
 
     public Bid placeBid(User bidder, MonetaryAmount bidAmount, 
                     Bid currentMaxBid, Bid currentMinBid) 
     throws BusinessException { 
    
         // Check highest bid (can also be a different Strategy (pattern)) 
         if (currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0) { 
                 throw new BusinessException("Bid too low."); 
         } 
        
         // Auction is active 
         if ( !state.equals(ItemState.ACTIVE) ) 
                 throw new BusinessException("Auction is not active yet."); 
        
         // Auction still valid 
         if ( this.getEndDate().before( new Date() ) ) 
                 throw new BusinessException("Can't place new bid, auction already ended."); 
        
         // Create new Bid 
         Bid newBid = new Bid(bidAmount, this, bidder); 
        
         // Place bid for this Item 
         this.addBid(newBid); 
         itemDao.update(this);       //   调用 DAO 进行显式持久化 
         return newBid; 
     } 
}

DDD 分层架构模型及基于贫血模型的微服务代码模型

DDD 分层架构模型

它包括用户接口层、应用层、领域层和基础层,分层架构各层的职责边界非常清晰,又能有条不紊地分层协作。

  • 用户接口层:面向前端提供服务适配,面向资源层提供资源适配。这一层聚集了接口适配相关的功能。
  • 应用层职责:实现服务组合和编排,适应业务流程快速变化的需求。这一层聚集了应用服务和事件相关的功能。
  • 领域层:实现领域的核心业务逻辑。这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务和事件等领域对象,以及它们组合所形成的业务能力。
  • 基础层:贯穿所有层,为各层提供基础资源服务。这一层聚集了各种底层资源相关的服务和能力。

分层架构的一个重要原则是每层只能与位于其下方的层发生耦合。

分层架构可以简单分为两种,即严格分层架构和松散分层架构。在严格分层架构中,某层只能与位于其直接下方的层发生耦合,而在松散分层架构中,则允许某层与它的任意下方层发生耦合。

分层架构的优点,Martin Fowler 在《Patterns of Enterprise Application Architecture》一书中给出了答案:

  1. 开发人员可以只关注整个结构中的某一层
  2. 可以很容易的用新的实现来替换原有层次的实现
  3. 可以降低层与层之间的依赖
  4. 有利于标准化
  5. 有利于各层逻辑的复用

**适当的分层体系结构将开发层面进行隔离,这些层不受其他层的更改的影响,从而使重构更加容易。**划分任务并定义单独的层是架构师面临的挑战。当需求很好地适应了模式时,这些层将易于解耦或分层开发。

使用场景:

  • 需要快速构建的新应用程序
  • 需要严格的可维护性和可测试性标准的应用
基于贫血模型的微服务代码模型

现在,我们来看一下,按照 DDD 分层架构模型设计出来的微服务代码模型到底长什么样子呢?

其实,DDD 并没有给出标准的代码模型,不同的人可能会有不同理解。下面要说的这个微服务代码模型是我经过思考和实践后建立起来的,主要考虑的是微服务的边界、分层以及架构演进。

微服务一级目录结构

**微服务一级目录是按照 DDD 分层架构的分层职责来定义的。**从下面这张图中,我们可以看到,在代码模型里分别为用户接口层、应用层、领域层和基础层,建立了 interfaces、application、domain 和 infrastructure 四个一级代码目录。

这些目录的职能是这样的。

  • Interfaces(用户接口层):它主要存放用户接口层与前端交互、展现数据相关的代码。前端应用通过这一层的接口,向应用服务获取展现所需的数据。这一层主要用来处理用户发送的 Restful 请求,解析用户输入的配置文件,并将数据传递给 Application 层。数据的组装、数据传输格式以及 Facade 接口等代码都会放在这一层目录里。
  • Application(应用层):它主要存放应用层服务组合和编排相关的代码。应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合,向上为用户接口层提供各种应用数据展现支持服务。应用服务和事件等代码会放在这一层目录里。
  • Domain(领域层):它主要存放领域层核心业务逻辑相关的代码。领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里
  • Infrastructure(基础层):它主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。

下面具体说明一下领域层的代码目录结构

**Domain 是由一个或多个聚合包构成,共同实现领域模型的核心业务逻辑。**聚合内的代码模型是标准和统一的,包括:entity、event、repository 和 service 四个子目录。

  • **Aggregate(聚合):**它是聚合软件包的根目录,可以根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。以聚合为单位的代码放在一个包里的主要目的是为了业务内聚,而更大的目的是为了以后微服务之间聚合的重组。聚合之间清晰的代码边界,可以让你轻松地实现以聚合为单位的微服务重组,在微服务架构演进中有着很重要的作用。
  • **Entity(实体):**它存放聚合根、实体、值对象以及工厂模式(Factory)相关代码。实体类采用贫血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。
  • **Event(事件):**它存放事件实体以及与事件活动相关的业务逻辑代码。
  • **Service(领域服务):**它存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服务设计为一个类。如果领域服务内的业务逻辑相对复杂,我建议你将一个领域服务设计为一个领域服务类,避免由于所有领域服务代码都放在一个领域服务类中,而出现代码臃肿的问题。领域服务封装多个实体或方法后向上层提供应用服务调用。
  • Repository(仓储):它存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,我们设定了一个原则:一个聚合对应一个仓储。

特别说明:按照 DDD 分层架构,仓储实现本应该属于基础层代码,但为了在微服务架构演进时,保证代码拆分和重组的便利性,把聚合仓储实现的代码放到了聚合包内。这样,如果需求或者设计发生变化导致聚合需要拆分或重组时,我们就可以将包括核心业务逻辑和仓储代码的聚合包整体迁移,轻松实现微服务架构演进。

综合评价

在这四种模型当中,失血模型和胀血模型应该是不被提倡的。而贫血模型和充血模型从技术上来说,都是可行的。但是我个人仍然主张使用贫血模型。其理由:

  • 虽然贫血模型的 Domain Object 确实不够 rich,但 Domain Object 只包含属于它本身的领域逻辑,不包含持久化逻辑,将有效的隔离和屏蔽了持久化技术,进而可以对持久化技术进行灵活的替换。
  • 贫血模型中提出的按照是否依赖持久化进行划分,这种标准是非常确定的,不会引起开发团队设计上的争议。

目录

  1. 失血模型
  2. 贫血模型
  3. 充血模型
  4. 胀血模型
  5. 失血模型代码样例
  6. 贫血模型代码样例
  7. 充血模型代码样例
  8. DDD 分层架构模型及基于贫血模型的微服务代码模型
  9. DDD 分层架构模型
  10. 基于贫血模型的微服务代码模型
  11. 综合评价
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • DCU BW1000 环境下 llama.cpp 推理 Qwen3-Coder 模型失败分析
  • AI 基础核心概念:Prompt、Agent、Function Calling 及 RAG 解析
  • 学得会、做得出、能展示!12493+基于Web的校园二手商品交易系统设计与实现 全套资料打包送,学习更高效!
  • 无人机 Remote ID Beacon 帧字段详解
  • 2026 年 3 月全球大模型全景:国产登顶与智能体爆发
  • 2025年9月GESP C++八级真题及题解:最短距离
  • 超越 CRUD:用 JeecgBoot 低代码模式一天搭建请假审批系统
  • 2026 年 3 月全球 AI 前沿动态与技术突破
  • Jetpack Compose 核心特性与实战指南
  • Ubuntu 20.04 和 22.04 安装 Python 3 实战指南
  • 网络安全行业现状与学习路径规划
  • Telegram 中文搜索机器人 @letstgbot 技术解析与开发实战
  • Web 自动化测试入门:核心概念与百度搜索实战
  • AI 鸿蒙 App 开发:从页面到能力系统的架构变革
  • Python 性能测试框架 Locust 实战教程
  • 医疗连续体机器人模块化控制界面设计与 Python 库应用
  • 云开发 Copilot:AI 辅助低代码开发实战
  • 二分查找实战:山峰数组峰顶索引与寻找峰值
  • Spring 事务管理与传播机制详解
  • Java JDK 21 安装与环境配置指南(Windows + macOS)

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online