什么是 DDD (Domain-Driven Design)?
DDD 并不是一种框架(像 Spring Boot),也不是一种具体的技术(像 MySQL、ES),它是一种软件架构设计思想 和复杂业务系统的破局方法论。
它的核心理念是:"软件设计的核心在于深刻理解和抽象真实的业务领域,让代码结构与现实业务逻辑保持高度一致。"
简单来说:以前我们写代码,是"面向数据库编程"(先建表,再写增删改查);而在 DDD 中,我们是"面向业务模型编程"(先理清业务概念,再考虑怎么存)。
为什么需要 DDD?(痛点与破局)
痛点:传统的"面条代码"与"大泥球"
在传统的 MVC 架构(Controller -> Service -> Dao -> 数据库表)中,当业务变得极其复杂时(比如全房通这种涉及租赁、财务、审批、维修的庞大系统):
-
Service 层变得无限臃肿 :一个
HousingService.java可能有几千行代码,里面塞满了业务校验、计算账单、发消息、调其他接口的逻辑。 -
贫血模型 (Anemic Domain Model) :我们写的
Housing实体类(Entity)只有几十个get和set方法,它本身没有任何业务逻辑。所有的逻辑都在 Service 里,实体成了一个单纯的"数据袋"。 -
牵一发而动全身:改了一个财务计算的 Bug,结果导致收房功能挂了。因为代码高度耦合。
DDD 的破局之道
DDD 将庞大的系统拆分成多个独立的**"界限上下文 (Bounded Context)"**,也就是微服务的雏形。比如:
-
租赁域(管收房、出房、合同)
-
财务域(管账单、流水、催收)
-
基础域(管店面、员工、字典)
在每个域内,构建自己独立的充血模型(有状态、有行为的实体),各管各的事。
DDD 的四大核心概念 (以全房通系统为例)
1. 实体 (Entity) 与 值对象 (Value Object)
-
实体 (Entity):有唯一标识,且状态会随时间变化的业务对象。
- 例子 :
qft_housing(房源)。一套房子从"未审核"变成"已租",它的状态在变,但它的房源编号(如 ZB0288)永远不变,它是实体。
- 例子 :
-
值对象 (Value Object):没有唯一标识,只用来描述事物特征的对象。
- 例子:房子的"面积 (98㎡)"、"户型 (3室1厅)"。如果把 3室1厅 改成 2室1厅,它其实变成了一个全新的户型描述,这类属性通常作为值对象依附在实体上。
2. 聚合 (Aggregate) 与 聚合根 (Aggregate Root)
这是 DDD 最重要的概念。
-
聚合:一组生命周期强绑定、需要保证数据一致性的实体和值对象的集合。
-
聚合根:这个集合的"带头大哥"。外界想操作集合里的任何东西,都必须通过聚合根。
-
例子 :
qft_housing(房源)是聚合根 。qft_housing_cover_other_expenses(代扣附加费)是集合内的子实体。 -
规则 :你不能跳过房源直接去给附加费表插一条数据;你必须调用房源聚合根的
addExpense()方法,由房源自己去校验(比如:房源是不是被冻结了?)并最终落盘。
-
3. 领域服务 (Domain Service)
当一个业务逻辑跨越了多个聚合根,不适合放在任何一个单一的实体里时,就用领域服务。
- 例子 :转租业务。涉及把 A 房源的租客退掉,并在 B 房源重新生成合同。这不属于 A 房源,也不属于 B 房源,这是一个跨聚合的
TransferHousingDomainService。
4. 领域事件 (Domain Event)
当聚合根的状态发生重大改变时,向外发出的广播(通常借助 RabbitMQ / Kafka)。
-
例子 :房源登记成功后,发出一个
HousingRegisteredEvent事件。 -
效果:财务域监听到这个事件,去生成账单;搜索域监听到,去更新 ES 索引。这就实现了模块间的彻底解耦。
DDD 典型的四层架构代码结构
如果全房通的后端严格按照 DDD 重构,它的代码目录大概长这样:
com.qft.lease (租赁域)
├── api (用户接口层)
│ └── controller (接收 HTTP 请求,转交给应用层)
│
├── application (应用层)
│ └── service (负责编排调度,不写核心业务逻辑。如:开启事务 -> 调用领域模型 -> 发送 MQ 消息)
│
├── domain (领域层 - 核心!)
│ ├── model
│ │ ├── Housing (房源聚合根实体,里面写满了校验和状态流转的业务逻辑)
│ │ └── Expense (附加费实体)
│ ├── repository (只定义接口:HousingRepository,不写实现)
│ └── event (领域事件定义)
│
└── infrastructure (基础设施层)
├── persistence (这里才写 MyBatis/JPA,实现领域层的 Repository 接口,存入 MySQL)
├── mq (RabbitMQ 的发送实现)
└── search (ES 的查询实现)
总结:DDD 带来的改变
-
代码即业务 :非技术人员看 Domain 层的代码(如
housing.checkOut()),就能看懂业务逻辑,不再是一堆干瘪的setStatus(1)。 -
保护核心资产 :无论外围的数据库从 MySQL 换成 Oracle,还是消息队列从 RabbitMQ 换成 Kafka(基础设施层变化),最核心的领域层(Domain)代码一行都不用改。
-
完美契合微服务:按 DDD 的界限上下文拆分微服务,边界最清晰,不会出现服务间数据库乱连、逻辑乱调的"分布式大泥球"。