news 2026/9/3 4:13:27

Spring+DDD实战:从贫血模型到领域驱动的架构演进

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring+DDD实战:从贫血模型到领域驱动的架构演进

Spring+DDD实战:从贫血模型到领域驱动的架构演进

【免费下载链接】spring-reading涵盖了 Spring 框架的核心概念和关键功能,包括控制反转(IOC)容器的使用,面向切面编程(AOP)的原理与实践,事务管理的方式与实现,Spring MVC 的流程与控制器工作机制,以及 Spring 中数据访问、安全、Boot 自动配置等方面的深入研究。此外,它还包含了 Spring 事件机制的应用、高级主题如缓存抽象和响应式编程,以及对 Spring 源码的编程风格与设计模式的深入探讨。项目地址: https://gitcode.com/GitHub_Trending/sp/spring-reading

引言:一个典型的架构困境

"我们的代码越来越难改了!"——这是很多开发团队在业务系统演进到一定阶段后的共同感慨。随着功能不断叠加,原本清晰的MVC架构逐渐演变成"大泥球"(Big Ball of Mud),业务逻辑分散在Controller、Service和DAO中,修改一个简单的业务规则需要改动多个文件。

让我们从一个真实案例开始:用户积分系统。在传统架构下,代码可能是这样的:

// 贫血模型示例 - 业务逻辑分散在各处 @Service public class PointService { public void transferPoints(Long fromUserId, Long toUserId, int amount) { // 在Service中验证业务规则 if (amount <= 0) { throw new IllegalArgumentException("积分数量必须大于0"); } Point fromPoints = pointDao.findByUserId(fromUserId); Point toPoints = pointDao.findByUserId(toUserId); // 在Service中执行核心业务逻辑 if (fromPoints.getBalance() < amount) { throw new InsufficientPointsException("积分不足"); } fromPoints.setBalance(fromPoints.getBalance() - amount); toPoints.setBalance(toPoints.getBalance() + amount); pointDao.update(fromPoints); pointDao.update(toPoints); } }

这种贫血模型的问题在于:业务逻辑分散在Service层,领域对象只是数据的载体,缺乏真正的行为封装。

架构演进:从分层到领域驱动

第一步:识别核心领域概念

在积分系统中,我们需要识别出真正的领域对象

  • User(用户):系统的主体
  • Point(积分):用户的积分资产
  • PointTransaction(积分交易):积分转移的记录

第二步:重构为富领域模型

// 富领域模型 - 积分实体 public class Point { private Long userId; private int balance; // 构造函数 public Point(Long userId, int balance) { this.userId = userId; this.balance = balance; } // 领域行为:增加积分 public void add(int amount) { if (amount <= 0) { throw new IllegalArgumentException("增加积分必须大于0"); } this.balance += amount; } // 领域行为:减少积分 public void subtract(int amount) { if (amount <= 0) { throw new IllegalArgumentException("减少积分必须大于0"); } if (this.balance < amount) { throw new InsufficientPointsException("积分不足"); } this.balance -= amount; } // 查询方法 public boolean canSubtract(int amount) { return this.balance >= amount; } }

第三步:设计领域服务

当业务逻辑涉及多个实体时,使用领域服务来封装:

// 积分领域服务 @Service public class PointDomainService { private final PointRepository pointRepository; public PointDomainService(PointRepository pointRepository) { this.pointRepository = pointRepository; } @Transactional public PointTransaction transfer(TransferCommand command) { Point fromPoint = pointRepository.findByUserId(command.getFromUserId()); Point toPoint = pointRepository.findByUserId(command.getToUserId()); // 执行领域逻辑 fromPoint.subtract(command.getAmount()); toPoint.add(command.getAmount()); // 保存变更 pointRepository.save(fromPoint); pointRepository.save(toPoint); // 记录交易 return PointTransaction.recordTransfer(command); } }

Spring框架的DDD实现技巧

依赖注入:解耦领域与基础设施

Spring的构造函数注入是实现DDD分层的关键:

// 应用服务 - 协调领域服务和外部交互 @Service public class PointApplicationService { private final PointDomainService pointDomainService; private final EventPublisher eventPublisher; // 构造函数注入,依赖关系明确 public PointApplicationService(PointDomainService pointDomainService, EventPublisher eventPublisher) { this.pointDomainService = pointDomainService; this.eventPublisher = eventPublisher; } public void handleTransfer(TransferRequest request) { // 1. 参数验证 validateRequest(request); // 2. 执行核心领域逻辑 PointTransaction transaction = pointDomainService.transfer(request.toCommand()); // 3. 发布领域事件 eventPublisher.publish(new PointsTransferredEvent(transaction)); } }

事务管理:保证领域操作的原子性

使用Spring的声明式事务确保领域操作的完整性:

// 在配置类中启用事务管理 @Configuration @EnableTransactionManagement public class TransactionConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }

仓储模式:隔离数据访问细节

仓储接口定义在领域层,实现放在基础设施层:

// 领域层 - 仓储接口 public interface PointRepository { Point findByUserId(Long userId); void save(Point point); // 其他查询方法... } // 基础设施层 - 仓储实现 @Repository public class JdbcPointRepository implements PointRepository { private final JdbcTemplate jdbcTemplate; public JdbcPointRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public Point findByUserId(Long userId) { // 数据访问实现 String sql = "SELECT * FROM points WHERE user_id = ?"; return jdbcTemplate.queryForObject(sql, new PointRowMapper(), userId); } }

实战演练:构建完整的DDD应用

项目结构设计

src/main/java/com/example/pointsystem/ ├── application/ # 应用层 │ ├── command/ # 命令对象 │ ├── service/ # 应用服务 │ └── event/ # 应用事件 ├── domain/ # 领域层 │ ├── model/ # 领域模型 │ ├── service/ # 领域服务 │ ├── repository/ # 仓储接口 │ └── event/ # 领域事件 ├── infrastructure/ # 基础设施层 │ ├── persistence/ # 持久化实现 │ ├── external/ # 外部服务调用 │ └── config/ # 配置类 └── interfaces/ # 接口层 ├── rest/ # REST API └── dto/ # 数据传输对象

核心领域模型实现

// 聚合根 - 用户 public class User { private Long id; private String username; private UserStatus status; private Point point; // 聚合内的实体引用 // 工厂方法 public static User create(String username) { User user = new User(); user.id = IdGenerator.nextId(); user.username = username; user.status = UserStatus.ACTIVE; user.point = new Point(user.id, 0); return user; } // 领域行为 public void activate() { if (this.status == UserStatus.DISABLED) { this.status = UserStatus.ACTIVE; } } // 业务方法 public void transferPointsTo(User targetUser, int amount) { // 验证业务规则 if (!this.isActive() || !targetUser.isActive()) { throw new BusinessException("用户状态异常"); } // 执行积分转移 this.point.subtract(amount); targetUser.point.add(amount); // 记录领域事件 DomainEventPublisher.publish(new PointsTransferredEvent(this.id, targetUser.id, amount)); } }

避坑指南:DDD实践中的常见问题

问题1:过度设计

症状:为每个简单的业务概念都创建聚合、实体、值对象。

解决方案:从简单开始,只有当业务复杂性确实需要时才引入DDD概念。

问题2:技术实现污染领域模型

症状:在领域对象中引入JPA注解、JSON序列化等基础设施关注点。

解决方案:使用防腐层隔离技术细节:

// 领域模型 - 纯粹的POJO public class Point { private Long userId; private int balance; // 纯粹的领域行为,无技术依赖 public void add(int amount) { this.balance += amount; } } // 基础设施层 - 映射配置 @Entity @Table(name = "points") public class PointJpaEntity { @Id private Long userId; private int balance; // 转换为领域模型 public Point toDomainModel() { return new Point(this.userId, this.balance); } }

问题3:事务边界设计不当

症状:一个事务中包含过多的业务操作,导致锁竞争和性能问题。

解决方案每个事务对应一个用例,避免跨聚合的长事务。

架构收益:从混乱到有序

采用DDD架构后,我们的系统获得了显著的改进:

  1. 业务逻辑集中:积分相关的所有规则都在Point实体中
  2. 代码可读性提升:通过领域对象的方法名就能理解业务意图
  3. 测试更容易:可以独立测试领域对象的行为
  4. 演进更安全:修改业务规则时影响范围明确

总结:DDD不是银弹,而是工具箱

Spring框架为DDD实践提供了强大的基础设施支持,但成功的关键在于:

  • 渐进式重构:不要试图一次性重构整个系统
  • 团队共识:确保团队成员理解并认同DDD的价值
  • 持续改进:在实践中不断调整和优化架构设计

记住:好的架构是演进而来的,不是设计出来的。从识别当前架构的问题开始,逐步引入DDD概念,让架构服务于业务,而不是业务服务于架构。

【免费下载链接】spring-reading涵盖了 Spring 框架的核心概念和关键功能,包括控制反转(IOC)容器的使用,面向切面编程(AOP)的原理与实践,事务管理的方式与实现,Spring MVC 的流程与控制器工作机制,以及 Spring 中数据访问、安全、Boot 自动配置等方面的深入研究。此外,它还包含了 Spring 事件机制的应用、高级主题如缓存抽象和响应式编程,以及对 Spring 源码的编程风格与设计模式的深入探讨。项目地址: https://gitcode.com/GitHub_Trending/sp/spring-reading

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 23:53:05

鲁棒性测试集构建:涵盖各种极端情况检验DDColor稳定性

鲁棒性测试集构建&#xff1a;涵盖各种极端情况检验DDColor稳定性 在数字遗产保护日益受到重视的今天&#xff0c;老照片修复已不再只是技术爱好者的个人项目&#xff0c;而是逐渐成为档案馆、博物馆乃至家庭影像数字化中的刚需任务。然而&#xff0c;现实中的黑白老照片往往存…

作者头像 李华
网站建设 2026/8/25 19:34:19

WebToEpub终极指南:5分钟实现网页内容永久保存

WebToEpub终极指南&#xff1a;5分钟实现网页内容永久保存 【免费下载链接】WebToEpub A simple Chrome (and Firefox) Extension that converts Web Novels (and other web pages) into an EPUB. 项目地址: https://gitcode.com/gh_mirrors/we/WebToEpub 还在为喜欢的网…

作者头像 李华
网站建设 2026/9/3 1:01:38

如何高效使用IDR:Delphi反编译工具的完整操作指南

如何高效使用IDR&#xff1a;Delphi反编译工具的完整操作指南 【免费下载链接】IDR Interactive Delphi Reconstructor 项目地址: https://gitcode.com/gh_mirrors/id/IDR IDR&#xff08;Interactive Delphi Reconstructor&#xff09;是一款专为Windows环境设计的强大…

作者头像 李华
网站建设 2026/9/2 22:09:53

格力空调智能控制终极指南:如何在Home Assistant实现完美集成

还在为空调控制而烦恼吗&#xff1f;想要摆脱手机APP的束缚&#xff0c;实现真正智能化的温度管理吗&#xff1f;今天我要为你介绍一款革命性的格力空调集成方案&#xff0c;让你在Home Assistant中轻松掌控所有空调设备&#xff0c;享受智能家居带来的便利生活&#xff01; 【…

作者头像 李华
网站建设 2026/9/2 23:03:40

如何确保Allegro导出Gerber文件符合生产标准

如何确保Allegro导出Gerber文件一次过厂&#xff1a;从设计到生产的实战避坑指南在高速、高密度PCB设计中&#xff0c;哪怕电路原理完美无缺&#xff0c;布线拓扑滴水不漏&#xff0c;只要Gerber输出这一步没踩准节拍&#xff0c;整块板子就可能“胎死腹中”。更令人头疼的是&a…

作者头像 李华
网站建设 2026/9/2 22:09:52

虚拟偶像联动:让数字人‘穿越’进修复后的老照片合影

虚拟偶像联动&#xff1a;让数字人“穿越”进修复后的老照片合影 在一张泛黄的黑白全家福前&#xff0c;祖母年轻时的笑容依稀可见&#xff0c;但色彩早已褪去。如果能让今天的虚拟偶像穿上旗袍&#xff0c;站在她身旁合影——不是简单的图像拼贴&#xff0c;而是一次真正意义上…

作者头像 李华