$$ DDD 就是领域驱动设计,Domain,Driven,Design $$
⚖️ 对比 MVC ,DDD
✨️ 架构易拆分
- 传统的 MVC 架构不灵活:在项目推进的过程中,拆分功能会拆不动【A 功能,B 功能底层的调用会很混乱,会调到一起】
- 由于 DDD 架构是业务优先,所以拆分项目会很容易
✨️ 业务优先 :MVC 架构的项目结构,一看是不知道这一块是干嘛的,DDD 架构是按业务划分的
💥 类爆炸
💛 方法一
看完 DDD 架构的设计原则,会发现本来 MVC 用一个 Controller,一个 Service 能解决的问题,DDD 要用 interfaces,application,充血模型,仓库,工厂……很多类来解决,所以我们引入了 聚合
[!quote] 聚合 聚合 中包括聚合根【聚合内的一个对象,也是唯一对外开放的接口】,各种值对象,各种实体
- 减少类之间的依赖关系
有个大商场【电商系统】:
- 购物车【订单聚合】:购物车里可以有很多商品【订单项】,也可以包含支付方式……
- 购物车的把手【订单实体,即聚合根】:顾客通过购物车的把手来推动购物车,而不是直接拿着商品走,购物车的把手是操作整个购物车的唯一方式【无论你是要添加商品、移除商品,还是查看支付信息】
- 商品和支付信息【订单项,支付方式值对象】:它们属于购物车,但你不会直接拿着商品走向收银台支付,而是通过推动整个购物车来进行结算
💛 方法二
只有引起实体类属性值变化的业务【新增,删除……】才去按照 DDD 架构的条条框框去做,如果这个实体类只有查询,排序……,这些不引起实体类属性值变化的业务,那我们可以不要充血模型,repository ……,避免引入更多的类
📝 设计原则
- 单一职责原则:一个类只做一件事【比如 A 类负责转账业务,那么只有转账业务流程发生变化了,才改动这个类,其他不管怎么变,A 类雷打不动】
- 开放封闭原则:对扩展开放,对修改封闭【未来增加功能时,做到增加类,而不修改类】
- 依赖反转原则:程序之间只依赖于抽象接口,而不依赖于具体的实现
💛 充血模型
[!quote] 贫血模型 贫血模型就是只有属性,和
get(),set()方法的 POJOjavapublic class Account { private int id; private int balance; // 余额 ……对应的 get ,set 方法 }当我们的业务多起来之后,这个 pojo 承载了哪些业务看不出来了,我需要到一个个
Service中去找
[!quote] 充血模型 充血模型 就是把 pojo 的属性,以及引起属性的值发生变化的方法,写到 pojo 中。例如下面这个 pojo ,一看就知道有两个业务:
javapublic class Account { private int id; private int balance; // 余额 ……对应的 get ,set 方法 public void TransferIn(int money){ balance = balance + money; } public void TransferOut(int money){ if(balance < money){ throws new InsufficientMonryException(); } balance = balance - money; } }
- 只有引起 pojo 属性值变化的方法,才写到 pojo 里
一个充血模型只负责一个业务,如果有多个业务需要到同一个充血模型,那就每个业务创建一个该业务需要用到的属性的充血模型。但是多个相似的充血模型还是只会创建一个数据库表
public class AccountOne {
属性1;
属性2;
}
public class AccountTwo {
属性3;
属性4;
}
// 数据库表
字段:属性1 属性2 属性3 属性4💛 聚合
[!quote] 聚合 聚合 是一组具有内聚性的相关对象的集合。当你对数据库的操作需要使用到多个实体时,可以创建聚合
- 聚合内事务一致性 :在订单聚合内部,所有的操作【添加订单项、修改收货地址 ……】都由聚合根对象 Order 进行管理,并在同一个事务中完成
- 聚合外最终一致性 :在不同聚合之间的操作可能涉及多个事务,系统通过异步消息、事件驱动 ……来确保最终一致性,即使中间状态可能暂时数据不一致,但最终会达到一致
[!quote] 聚合根 聚合根 是聚合中的一个比较特殊的实体对象,它管理着聚合内的其他实体对象,以确保其数据的一致性和完整性
- 负责创建、修改、删除聚合内的其他实体,并且只能通过聚合根来修改聚合内的实体对象
// 聚合 = 聚合根 + 其他实体
// 聚合根
public class Order {
private String orderId;
private User user;
private List<OrderItem> orderItems;
private ShippingAddress shippingAddress;
// 构造方法 ……
// 其他业务逻辑方法 ……
}
// 用户实体
public class User {
private String userId;
private String name;
// 构造方法 ……
// 其他方法 ……
}
// 订单明细实体 ……
// 收货地址值对象 ……🧱 项目结构

💛 触发器层 trigger
我们可以根据触发动作进行分类:
- 接口调用(HTTP / RPC)
- HTTP 接口:通过 RESTful API ,外部系统可以直接调用领域服务。适合需要同步响应的场景
- RPC 调用:通过 gRPC、Thrift 等远程过程调用框架,实现服务之间的高效通信。适合服务之间的高性能通信需求
- 消息监听 :监听消息队列中的消息,触发相应的领域逻辑处理
- 定时任务 :通过定时任务框架,定时触发某些领域逻辑
[!hint] 如果这个应用程序只有接口调用,那可以把
trigger换成interfaces
💛 接口层 api
- 提供统一的外部接口 :API 层负责将系统的内部功能通过一组清晰的接口暴露给外部(如前端、移动端、第三方系统等),而屏蔽内部实现细节,仅暴露出客户端需要的功能和数据,保证了系统的安全性和可维护性
- 让领域层专注于业务逻辑,而不用处理低层的通信协议问题
- 支持多种客户端的需求:系统可能需要支持多种客户端(如 Web 前端、移动端、第三方合作伙伴等),而不同的客户端可能通信协议,数据格式不同,API 层可以协调
💛 应用层 application
application 层不能被引入,并且要直接或间接地引入所有模块
application 应用层 :
- 在没有 trigger 层时:用来组合领域层之间的业务,形成完整的业务【比如有一个领域是知识星球领域,另一个领域是 ChatGPT 领域,我要进行两个领域的对接,就在应用层实现】
- 有 trigger 层时:则由 trigger 负责组合领域层之间的业务,application 层负责协调一些全局的配置,配置 resource 资源目录,以及编写测试用例
💛 领域层 domain
domain 层不能引入任何模块
domain领域层 ==Service==model领域模型,定义了领域对象、聚合和值对象bo业务对象vo值对象- ……
repository仓库,里面定义了仓库接口,访问 Infrastructure 层的 repository 实现类service领域服务,包含业务逻辑,只关注业务功能实现,不与外部任何接口和服务直连,而是通过仓储 Repository,端口 Port,或者适配器 Adapter 实现调用
[!hint] Model
POJO【plain old java object】普通 Java 对象,可以作为其他数据模型的基础
VO【value object】 :值对象是不可变的对象,没有唯一 ID 标识符,用于表示一组值【意味着如果两个值对象的属性相同,那么它们就是相等的】
- 通常不会提供 setter 方法,而是提供构造函数或者 Builder 方法来实例化对象
- VO 中通常重写 equals 和 hashCode 方法,以便基于值进行比较
PO / 实体【Entity】 :实体必须要有唯一 ID 标识符,意味着如果两个 Entity 的属性值相同,它们也不相等;PO 是与数据库表相映射的 Java 对象【所以 PO 和实体可以看作是相同的概念】DTO【data transfer object】:DTO 用于在不同层之间传输数据
- 不包含任何业务逻辑
充血模型:充血模型 = PO + 业务逻辑,如果某个 PO 中有业务逻辑,那它就是充血模型
BO【Business object】:业务对象,可以是多个 PO 的组合,并且包含业务逻辑
[!hint] 在领域与领域之间,如果需要某个充血模型,要把 充血模型 使用工厂组装成 贫血模型 进行传输
💛 基础层 infrastructure
infrastructure基础层,包含了数据库,缓存,网关,第三方工具…… ==Mapper==MapperPOredisrepository使用@Repository标记
infrastructure 与 domain 的关系
infrastructure 与 domain 之间通过仓储 Repository 来解耦
domain- 【repository】
- IRaffleRepository 是一个业务优先的 Repository
- 【repository】
infrastructure- 【JPARepository】
- IStrategyRepository 这个是实体优先的 Repository
- 【repository】
- RaffleRepository:RaffleRepository 实现了 IRaffleRepository 接口;然后再调用多个实体优先的 Repository 来整合形成自己的业务
- 【JPARepository】
💛 类型层 types
types 层用来定义一系列自定义的,用于所有层的公共对象(异常类,全局参数类,全局配置类 ……)
🧩 扩展
[!hint] 除非你的系统过于复杂,无法作为单体进行管理,否则不要考虑微服务
微服务创始人说过:
- 单体架构优先:单体架构能用,不要用微服务
- 项目不要一开始就构建微服务:所有成功的微服务案例,都是从单体架构演变的【因为你只有做好了单体架构,你才熟悉业务流程,知道哪块臃肿了,要拆出去,如果一上来就微服务,一不下心拆错了,后期就难改了】

