实用指南:基于Springboot的DDD实战(不依赖框架)

news/2025/10/26 22:50:57/文章来源:https://www.cnblogs.com/wzzkaifa/p/19167557

基于Springboot的DDD实战(不依赖框架)

领域驱动设计(DDD)是一把锋利的双刃剑。它既是斩断复杂业务“一团乱麻”的神兵利器,也可能在经验不足的团队手中,成为过度设计、拖累项目的沉重枷锁。

今天,我们一起结合经典之作《实现领域驱动设计》(红皮书),软件设计的基本原则,通过一个复杂的业务场景,探讨如何在Spring生态中,真正地、务实地落地DDD,并融入一些好的的工程实践。


1. 理念的基石:DDD不是银弹,而是战略罗盘

在开始之前,我们必须达成一个共识:DDD的核心价值在于战略设计,而非战术上的炫技。


2. 我们的竞技场:一个复杂的业务场景——“Nova Coffee”新零售平台

为了让讨论不流于空泛,我们设定一个足够复杂的业务背景。

业务背景: “Nova Coffee”是一家精品连锁咖啡品牌,希望打造一个线上下单、线下履约、会员一体化的新零售平台。

  • 核心流程:

    1. 用户端: 消费者通过App浏览商品、下单、支付、选择自提或外送。

    2. 门店端: 咖啡师在门店工作台(POS/平板)接收订单,制作饮品,完成订单(叫号自提或交由骑手)。

    3. 会员系统: 用户通过消费累积积分(星星),兑换优惠券,升级会员等级。

    4. 营销活动: 运营人员可以配置各种营销活动,如“第二杯半价”、“满30减5优惠券”等。

    5. 履约配送: 如果是外送单,系统需要与第三方运力平台对接,进行叫单、状态同步。

这个场景的复杂性在于,它涉及多个相互关联但又职责分明的业务领域。


3. 战略设计:绘制架构蓝图,而非一头扎进代码

这是落地DDD最关键,也最容易被忽视的一步。先别急着创建Spring Boot项目!

过程:

  1. 识别领域与子域:

  2. 建立通用语言 (Ubiquitous Language):

  3. 定义限界上下文 (Bounded Context):

    基于子域和通用语言的差异,我们定义限界上下文:

  4. 绘制上下文地图 (Context Map):

    这是战略设计的核心产出,它定义了上下文之间的关系。我们将使用Spring Cloud来实现这些关系。


4. 战术设计:在限界上下文中精雕细琢

现在,我们可以选择一个限界上下文,比如核心的Ordering Context,来深入战术设计和项目搭建。

4.1 项目结构 (多模块Maven/Gradle)

这是避免DDD被滥用的第一道防线:强制性的分层隔离

nova-coffee/
├── pom.xml
└── ordering-context/├── pom.xml├── domain/                    # 领域层 (纯粹的领域模型,无任何框架依赖)│   ├── pom.xml│   └── src/main/java/│       └── com/novacoffee/ordering/domain/│           ├── model/│           │   ├── order/│           │   │   ├── Order.java            (聚合根)│           │   │   ├── OrderItem.java        (实体)│           │   │   ├── OrderStatus.java      (枚举)│           │   │   └── Money.java            (值对象)│           │   └── ...│           ├── event/│           │   └── OrderCreatedEvent.java    (领域事件)│           ├── repository/│           │   └── OrderRepository.java      (仓储接口)│           └── service/│               └── PricingService.java       (领域服务)├── application/               # 应用层 (编排领域层,处理用例)│   ├── pom.xml│   └── src/main/java/│       └── com/novacoffee/ordering/application/│           ├── OrderingApplicationService.java (应用服务)│           └── dto/│               ├── CreateOrderCommand.java   (命令)│               └── OrderDTO.java             (数据传输对象)├── infrastructure/            # 基础设施层 (实现领域接口,与外界交互)│   ├── pom.xml│   └── src/main/java/│       └── com/novacoffee/ordering/infrastructure/│           ├── persistence/│           │   └── JpaOrderRepository.java   (JPA实现仓储)│           ├── acl/│           │   └── MembershipACL.java        (防腐层实现)│           └── messaging/│               └── KafkaEventPublisher.java  (事件发布实现)└── interfaces/                # 接口层 (暴露API,处理外部请求)├── pom.xml└── src/main/java/└── com/novacoffee/ordering/interfaces/└── web/└── OrderController.java      (Spring MVC Controller)
  • 依赖关系interfaces -> application -> domaininfrastructure -> domain关键:**domain**层不依赖任何其他层,它是项目的核心和灵魂。
4.2 样例代码 (以Order聚合为例)

Domain层:**Order.java**(聚合根)

// package com.novacoffee.ordering.domain.model.order;
// 无Spring、JPA注解,纯粹的Java对象
public class Order {
private Long id;
private OrderStatus status;
private List<OrderItem> items;private Money totalPrice;private Long consumerId;// 构造函数负责创建时业务规则校验public Order(Long consumerId, List<OrderItem> items, PricingService pricingService) {if (consumerId == null) throw new IllegalArgumentException("Consumer ID cannot be null.");if (items == null || items.isEmpty()) throw new IllegalArgumentException("Order must have at least one item.");this.consumerId = consumerId;this.items = new ArrayList<>(items);this.status = OrderStatus.PENDING_PAYMENT;// 委托领域服务计算价格this.totalPrice = pricingService.calculateTotalPrice(this.items);}// 业务方法,封装行为和状态变更public void pay() {if (this.status != OrderStatus.PENDING_PAYMENT) {throw new IllegalStateException("Order is not pending payment.");}this.status = OrderStatus.PAID;// 此处可以发布领域事件: DomainEventPublisher.publish(new OrderPaidEvent(this.id));}// getters... (注意:仅暴露必要信息,保护内部状态)}

Domain层:**OrderRepository.java**(仓储接口)

// package com.novacoffee.ordering.domain.repository;
public interface OrderRepository {
Optional<Order> findById(Long id);void save(Order order); // 保存操作涵盖了新增和更新}

Application层:**OrderingApplicationService.java**

// package com.novacoffee.ordering.application;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
// ... imports
@Service
public class OrderingApplicationService {
private final OrderRepository orderRepository;
private final PricingService pricingService; // 领域服务
private final MembershipACL membershipACL; // 防腐层
// 构造函数注入
public OrderingApplicationService(...) { ... }
@Transactional
public Long createOrder(CreateOrderCommand command) {
// 1. 通过ACL获取外部信息
if (!membershipACL.isMemberActive(command.getConsumerId())) {
throw new BusinessException("Member is not active.");
}
// 2. 将DTO转换为领域对象
List<OrderItem> items = command.getItems().stream().map(...).collect(Collectors.toList());// 3. 创建聚合根,执行领域逻辑Order newOrder = new Order(command.getConsumerId(), items, pricingService);// 4. 使用仓储持久化orderRepository.save(newOrder);// 5. (可选)发布领域事件// eventPublisher.publish(new OrderCreatedEvent(newOrder.getId()));return newOrder.getId();}}

5. 架构师的红线:如何避免错误的DDD实践(实践反例)

  1. 贫血模型 + 上帝Service (最常见的错误)

    • 错误做法Order类只有一堆getter/setter。OrderingApplicationService里有上千行代码,包含了各种if/else来处理状态流转和价格计算。

    • 为何错误: 这完全违背了DDD的封装原则,业务逻辑泄露,Order沦为数据载体。最终导致代码难以测试和维护。

    • 正确做法: 如上例所示,将业务逻辑和规则(如pay()方法)内聚到Order聚合根中。

  2. 领域层被框架污染

    • 错误做法: 在Order.java领域模型上添加@Entity@Table@Column等JPA注解。

    • 为何错误: 这让你的核心领域模型与持久化技术紧紧绑定。如果有一天你想从JPA切换到MyBatis,或者甚至换成NoSQL数据库,将是一场灾难。

    • 正确做法: 在infrastructure层创建单独的JPA实体OrderJpaEntity,并在仓储实现中完成领域对象Order和持久化对象OrderJpaEntity之间的转换(可以使用MapStruct等工具)。

  3. 无视限界上下文,跨服务直接查库

    • 错误做法Ordering Context的服务为了获取会员等级,直接配置Membership Context的数据库连接池,跨库JOIN查询。

    • 为何错误: 这是微服务架构的头号杀手。它破坏了服务的封装性和自主性,两个团队被紧紧耦合在一起,一方的数据库变更可能导致另一方服务崩溃。

    • 正确做法: 严格遵守上下文地图。Ordering只能通过Membership发布的API(OHS)或订阅其事件来获取数据。

  4. 万物皆聚合

    • 错误做法: 把所有有关联的实体都塞进一个巨大的聚合里,比如Consumer聚合里包含了List<Order>List<Address>List<Coupon>

    • 为何错误: 聚合的设计原则是尽可能小,并保证事务的一致性。巨大的聚合会导致严重的性能问题(加载整张对象图)和并发冲突。

    • 正确做法: 聚合之间通过ID引用。Order聚合中只包含consumerId,而不是整个Consumer对象。如果需要Consumer的信息,通过应用服务去查询。

DDD是一场回归软件本质的修行

将DDD与Spring生态结合是一件有趣的事。它要求我们不仅是代码的编写者,更是业务的思考者和模型的塑造者。

请记住,DDD的成功不在于你使用了多少时髦的模式,而在于:

这充满挑战,但回报也是巨大的——一个清晰、健壮、可演化的,能够真正支撑业务长期发展的软件系统。

这,正是一个资深架构师的价值所在。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/947125.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我是如何通过开发微信小游戏赚得人生第一桶金的

我是如何通过开发微信小游戏赚得人生第一桶金的我是如何通过开发微信小游戏赚得人生第一桶金的一、初识 2023-2024年开始接触游戏开发的,那时候公司的业务有些小游戏的玩法,玩法很简单,但是每场都挺火爆的,日活有1…

20232418 2025-2026-1 《网络与系统攻防技术》实验三实验报告

20232418 2025-2026-1 《网络与系统攻防技术》实验三实验报告 1.实验内容 本次实验主要围绕免杀原理与实践展开,通过多种技术手段尝试实现恶意代码的免杀效果。首先,使用msfvenom生成不同格式的恶意文件(如exe、jar…

ADB命令手册 - Android Debug Bridge命令参考

ADB命令手册 - Android Debug Bridge命令参考 本手册包含ADB的常用命令、示例和拓展知识,帮助开发者和测试人员更好地使用ADB工具进行Android设备的调试和管理。 第一部分:ADB基础命令 adb version描述:显示ADB版本…

回忆录:梦开始的往事

回忆录:梦开始的往事 目录初入初入OI之门、 冲向算法 小奥之战 荣班 丘班失利 懵懂初一上(上)密码说明 由于文章内容涉及学校机密,不便直接公开发布,请希望阅读者洛谷私信hsr_ray或者添加微信hsrray2011,经作者审核…

大学生为啥一定要认真听讲

在我的大学生活中,上课分神似乎已经成为一种常态,我能清楚的认识到大学与高中学习态度的区别。在高中,我会因为老师讲课的进度快慢或者因为听课效果而选择去独立学习,从最后的结果来看,效果是有的。但是在大学,除…

以听筑基,以行践知:解锁学习新范式的思考

读罢三篇关于学习方法的文章,我对“如何高效学习”有了颠覆性认知。从前总觉得上课听不听无所谓,课后靠刷题就能补,直到看完Scalers关于“大学生上课必须认真听讲”的论述,才惊觉自己浪费了最宝贵的学习资源。老师…

Day4表单-imput标签

表单的作用是为了在网页上收集用户信息,一般在登录页面,注册页面和搜索区域应用 inpute标签的基本使用<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8">…

学好专业,养好体魄——我的学习感悟

不知不觉,已经来到了大二,读完老师分享的三篇文章,我最深的体悟是:专业学习没有捷径,唯有扎实掌握课堂上的基础知识,才能为后续的学习与实践筑牢根基。 数据科学与大数据技术专业知识体系繁杂,大二正是夯实基础…

ti2

JDK、JRE、JVM,以及三者的关系 1)JDK 指的是 java 开发工具包,它包括编译器、JAVA核心类库、JVM、开发辅助工具(jps、jinfo、jmap、jconsole、jvisualvm) 2)JRE 指的是 JAVA 程序运行环境,主要包括 JAVA 核心类…

单像素demo初探

入门仿真(零硬件)single_pixel_demo(Matlab + Python) 用 Hadamard 模式做采样,含加噪声与重建的完整最小示例;最适合第一天跑通。 GitHubsingle-pixel-imaging(教育向示例) 讲清单像素成像基本原理与流程,代…

昨天 今天 明天

祝你生日快乐今天的你超乎往常的坦率嘛 虽然今天的也是歌名一般这种乐色小文章开头是要介绍一下我的近况的 但是我觉得不介绍也没关系 我还是不能理解大学 就像我从来没有真正理解过高中 不管它处于进行时还是完成时 都…

刻意练习的重要性

「做中学(Learning By Doing)」之乒乓球刻意训练一年总结 https://www.cnblogs.com/rocedu/p/5826362.html 读这篇文章和《刻意练习》有感,刻意练习对我们学习和提升能力非常重要,这篇文章中说,自己打了二十多年的…

深入解析:解构IDP未来前景:去中心化金融的“阳谋”与以人为本的生态蓝图(解读)

深入解析:解构IDP未来前景:去中心化金融的“阳谋”与以人为本的生态蓝图(解读)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; fon…

加密算法相关

RSA 加密算法的流程1️⃣ 密钥生成 你的步骤是标准的 RSA:选大质数 (p, q)通常 1024 位以上(现代安全要求 2048 位以上) 确保无法被高效分解计算模数 $ (n = p \times q)$用于公钥和私钥 明文和密文都需小于 (n)计算…

利用 kubeadm 快速部署 kubernetes(k8s) 集群

kubernetes(k8s)集群部署环境:ubuntu22.04,kubeadm v1.28.15节点准备工作(全部) 1. 关闭防火墙 # rootsystemctl stop firewalld systemctl disable firewalldsystemctl stop ufw systemctl disable ufw2. 关闭安全…

第七周物理实验:分光仪调节及三棱镜折射率测量

分光仪调节及三棱镜折射率测量 时间: 实验目的 掌握分光仪调节和使用 测量三棱镜顶角 测量最小偏向角&玻璃折射率 实验仪器 JJY型分光仪、双平面反射镜、玻璃三棱镜、汞灯 实验原理 分光仪主要组成部分:自由准直…

联发科技 Genio 物联网高效的平台,引领 IoT 智能新时代

联发科技 Genio 物联网高效的平台,引领 IoT 智能新时代pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&…

20232324 2025-2026-1 《网络与系统攻防技术》实验三实验报告

20232324 2025-2026-1 《网络与系统攻防技术》实验三实验报告1.实验内容 1.1实验目标理解恶意代码免杀原理,掌握 msf 编码器、Veil-Evasion 工具、C+Shellcode 编程等免杀技术,生成能规避杀毒软件的恶意程序,并通过…

密码学学习

对称加密: 加密和解密使用的是同一把钥匙 对称加密:分成流加密和块加密 流加密:例如123456,先加密1,再加密2 块加密:相当于分组加密 1234 5678 主要是DES和AES(高级加密)数字签名:用发送方的私钥对该哈希值加密,…

电脑文件系统整理概要

文件夹分类原则: 57原则:同层最多7个文件夹、最多5层 应用处理原则: 应用安装时按照优先安装D盘(如果磁盘未分区则忽略),无法设置时再安装C盘 应用以创建快捷方式后进行管理 应用应分为以下几类:常用,主选,备…