DDD 架构 —— 领域驱动设计
DDD(Domain-Driven Design,领域驱动设计)不是某个框架,而是一套代码组织方法论:以业务领域为中心建模和组织代码,而不是按技术职能分层。它和微服务是绝配——微服务回答"拆几个服务",DDD 回答"边界怎么划"。
一、为什么会有 DDD:三层架构的痛点
传统三层架构(Controller → Service → Dao)按技术职能分层,小项目很清晰,但业务复杂后会变异:
java
// 一个几千行的 OrderService,塞满所有和订单有关的逻辑
public class OrderServiceImpl {
// 下单、取消、退款、改地址、查询、分页、导出……
// 全是过程式代码:先查库 → 一堆 if/else → 再 update
}痛点:
- 贫血模型:实体类(Entity/PO)只有 getter/setter,不含任何业务逻辑,逻辑全堆在 Service,业务规则散落难找
- 面向数据库建模:先设计表结构,代码围着表转,业务语义被稀释成一条条 SQL
- 改一个业务要横跨所有层:Controller、Service、Dao、DTO 全摸一遍
- 沟通鸿沟:产品说"库存扣减",开发脑中是
update stock set num = num - 1,同一个业务两套语言
DDD 的主张:业务逻辑归还给业务对象(充血模型),代码结构跟着业务边界走。
二、DDD 核心概念速览
战略设计(宏观:怎么划边界)
| 概念 | 含义 |
|---|---|
| 领域 / 子域 | 整个业务系统;按价值拆成核心域(赚钱的,如订单)、支撑域(必须但不核心,如库存)、通用域(谁都要,如登录鉴权) |
| 限界上下文 | 一个模型成立的边界——同一个"商品"在售前上下文叫 SKU,在物流上下文叫包裹。微服务拆分的最佳单位就是限界上下文 |
| 通用语言 | 业务和开发说同一种话:代码里的类名、方法名直接用业务术语(Order.cancel() 而不是 updateStatus(3)) |
战术设计(微观:领域层怎么写)
| 概念 | 含义 | 例子 |
|---|---|---|
| 实体 Entity | 有唯一标识、有生命周期的对象 | 订单(靠 orderId 识别) |
| 值对象 Value Object | 无标识、不可变,靠属性判等 | 地址、金额 |
| 聚合 Aggregate | 一致性边界,一个事务只改一个聚合 | 订单+订单明细 = 订单聚合 |
| 聚合根 | 聚合的入口,外界只能通过它操作聚合内部 | Order 是根,不能绕过它直接改 OrderItem |
| 仓储 Repository | 持久化的抽象,只给聚合根建仓储 | orderRepository.save(order) |
| 领域事件 | 领域内发生的事,用于解耦 | OrderCancelledEvent |
三、代码对比:贫血 vs 充血(DDD 最直观的差异)
传统三层(贫血)——逻辑在 Service:
java
public void cancelOrder(Long orderId) {
Order order = orderMapper.selectById(orderId);
if (order.getStatus() != 2) { // 魔法数字,规则散落各处
throw new BizException("已支付订单才能取消");
}
order.setStatus(3);
orderMapper.updateById(order);
}DDD(充血)——规则内聚在领域对象:
java
public class Order { // 领域对象自己管自己的规则
public void cancel() {
if (this.status != Status.PAID) {
throw new BizException("已支付订单才能取消");
}
this.status = Status.CANCELLED;
this.events.add(new OrderCancelledEvent(this.id)); // 领域事件
}
}
// Service 退化为"用例编排":取聚合 → 调领域方法 → 存仓储 → 发事件
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId);
order.cancel(); // 规则在领域对象里
orderRepository.save(order);
}好处:业务规则只有一处,且用业务语言写的,产品经理几乎能读懂。
四、DDD 分层架构(对比三层)
传统三层(依赖方向朝下,最终围着数据库转):
Controller → Service → Dao → 数据库
DDD 四层(领域层是核心,依赖倒置,基础设施依赖领域):
┌─────────────────────────────┐
│ 用户接口层 interfaces │ Controller、对外 DTO
│ 应用层 application │ 用例编排、事务,不含业务规则
│ 领域层 domain ⭐ │ 实体、聚合、领域事件、领域服务(核心)
│ 基础设施层 infrastructure │ Repository 实现、MQ、缓存
└─────────────────────────────┘关键区别:
- 三层的核心是 Dao/数据库,DDD 的核心是领域层
- 依赖倒置:
domain定义OrderRepository接口,infrastructure提供实现(MyBatis/JPA)——领域层不依赖任何技术细节,换数据库领域层一行不改
五、横向对比:DDD vs 其他架构
先分清两个正交的维度,很多混淆来自把两者混着比:
- 部署架构(单体 / 微服务):回答"拆几个独立部署的服务"
- 设计方法论(三层 / DDD / 整洁架构):回答"服务内部代码怎么组织"
| 架构 | 类型 | 一句话 | 和 DDD 的关系 |
|---|---|---|---|
| MVC/三层 | 设计方法论 | 按技术职能分层 | DDD 的对照面;CRUD 项目用三层更好 |
| 微服务 | 部署架构 | 按业务拆独立部署的服务 | 正交互补:用 DDD 划限界上下文 → 一个上下文落地成一个微服务 |
| 整洁/六边形架构 | 设计方法论 | 依赖指向内,业务核心不依赖框架/DB | 和 DDD 一脉相承,DDD 的分层通常用整洁/六边形实现 |
| 事件驱动 EDA | 通信架构 | 服务/模块间通过事件异步协作 | 领域事件天然适配 EDA,跨上下文解耦常用 MQ 事件 |
常见搭配:单体 + DDD(不拆服务但代码按上下文分包)、微服务 + DDD(最主流的大厂组合)。反过来"微服务但每个服务里还是三层贫血"也大量存在——拆了服务但没拆明白。
六、什么时候用 DDD
适合:
- 业务规则复杂、多变的系统(金融、电商交易、ERP、SaaS 核心域)
- 系统要长期演进,生命周期以年计
- 正要做微服务,需要划分服务边界的方法论
- 团队和产品沟通成本高,需要通用语言对齐认知
不适合:
- CRUD 管理系统、外包项目、小工具——三层 + SQL 三天搞定,上 DDD 纯属自虐
- 团队没人懂 DDD 就强行上——概念多、上手陡,容易写出"四不像"(分层齐全但依然贫血)
⚠️ DDD 的成本是真实存在的:概念量、代码量、抽象层级都上升。正确姿势是只给核心域上 DDD,支撑域和通用域继续用简单三层。
七、落地与学习建议
- 先从充血模型 + 一个聚合练手:拿自己项目里最复杂的一个业务(如订单),把规则从 Service 挪进领域对象,体会内聚
- 再练限界上下文:画业务流程图,找"同一个词在不同环节含义不同"的地方,那就是上下文边界
- 微服务落地:一个限界上下文 = 一个服务 = 一套独立数据库
- 书:入门看《解构领域驱动设计》(张逸)或《实现领域驱动设计》(红皮书),原典《领域驱动设计》(蓝皮书)偏理论,别第一本就读它
