DDD(Domain-Driven Design,领域驱动设计)是复杂业务建模的方法论。传统三层架构(Controller/Service/Dao)在业务简单时够用,一旦业务复杂——规则多、状态多、对象关系复杂——就会露出"贫血模型"的问题。这篇把 DDD 的核心概念(实体/值对象/聚合/仓储)讲清楚,以及它和传统分层的本质区别。
一、解决什么问题:贫血模型的痛点
传统三层里,实体常常是"贫血"的——只有 getter/setter,没有业务逻辑,逻辑全堆在 Service:
// 贫血模型:实体是"哑巴",只有数据没有行为
public class Order {
private Long id;
private BigDecimal amount;
private String status;
// 只有 getter/setter,没有任何业务逻辑
}
// 业务逻辑散落在 Service 里
public class OrderService {
public void pay(Order order) {
if ("PENDING".equals(order.getStatus())) { // 业务规则在 Service
order.setStatus("PAID");
}
}
}问题:业务规则散落在各个 Service 里,实体自己"管不住"自己的状态。订单能不能支付、支付后状态怎么变,这些规则本该属于"订单"自己,却写在了 Service 里。业务一复杂,Service 里全是 if/else,规则东一块西一块,改一处漏一处。
DDD 的思路:把业务逻辑放回实体,让实体"充血"、自己管自己。
二、贫血 vs 充血
// 充血模型:实体包含业务逻辑,自己管自己的状态
public class Order {
private Long id;
private BigDecimal amount;
private OrderStatus status; // 用枚举而不是 String
public void pay() { // 业务逻辑在实体里
if (status != OrderStatus.PENDING) {
throw new IllegalStateException("订单不是待支付状态");
}
this.status = OrderStatus.PAID;
// 还可以发领域事件、更新相关值对象...
}
}现在"订单怎么支付"的规则内聚在 Order 里,谁想支付订单都调 order.pay(),规则只有一处,改也只改一处。这就是 DDD 的核心精神:让领域对象承载业务规则,而不是让 Service 去 if/else。
三、核心概念(战术设计)
DDD 有一套概念,理解它们的"身份"就不会混:
| 概念 | 是什么 | 例子 |
|---|---|---|
| 实体(Entity) | 有唯一标识、可变的对象 | 订单(有 orderId)、用户 |
| 值对象(Value Object) | 无标识、不可变、靠属性区分 | 金额、地址、电话号码 |
| 聚合(Aggregate) | 一组强相关的实体/值对象,有明确边界 | 订单 + 订单项 |
| 聚合根(Aggregate Root) | 聚合的入口,外部只能通过它访问聚合内部 | 订单(Order) |
| 仓储(Repository) | 聚合的持久化接口(存/取聚合) | OrderRepository |
| 领域服务(Domain Service) | 跨聚合的业务逻辑 | 转账(涉及两个账户) |
| 领域事件(Domain Event) | 领域内发生的重要事件 | 订单已支付 |
最容易混的两对:
实体 vs 值对象:实体有"身份证"(唯一 ID,比如订单号),值对象没有("100 元"和另一个"100 元"没区别,属性相同就是同一个)。值对象要不可变,改了就是新对象。
聚合根:聚合是一个"整体",订单和它的订单项是一个聚合,订单是聚合根——外部要改订单项,必须通过订单这个入口(
order.addItem()),不能直接拿订单项改。这是为了保证聚合内部的一致性。
// 聚合根:Order 是入口,外部只能通过它操作内部
public class Order {
private Long id;
private List<OrderItem> items = new ArrayList<>(); // 聚合内部
public void addItem(Product product, int qty) {
// 业务规则:加商品时校验、算价,都封装在聚合根里
items.add(new OrderItem(product, qty));
recalcAmount();
}
// items 不对外暴露 setter,外部改不了内部,只能走 addItem()
}四、DDD 分层
DDD 通常搭配一种分层(和传统三层不同,更强调"领域"在中心):
接口层(Controller)→ 应用层(Application Service)→ 领域层(Domain:实体/聚合/领域服务)→ 基础设施层(Repository 实现/DB)
↑ 领域层是核心,不依赖任何框架和数据库关键:领域层不依赖 Spring、不依赖数据库——它是纯 Java 对象,业务规则就在这。数据库、框架都是外圈,通过接口(Repository)接入。这个思想往下走就是六边形/洋葱/整洁架构(分层架构演进那条线)。
五、战略设计(一句话了解)
DDD 还有"战略设计"层面,核心是限界上下文(Bounded Context)——把大系统按业务边界拆成多个"上下文",每个上下文有自己的模型(同一个"订单",在订单上下文和物流上下文里含义不同)。它解决的是"大系统怎么拆"的问题,是微服务拆分的思想来源之一。
六、什么时候用 DDD
DDD 不是银弹,别硬套:
- 适合:业务复杂、规则多、状态流转多、需要长期演进的系统(电商、金融、交易)
- 不适合:简单 CRUD、纯数据展示、业务规则少——硬套 DDD 只会徒增复杂度
一句话:业务简单用三层就够,业务复杂到"if/else 塞满 Service"时才上 DDD。
小结
- DDD 解决"贫血模型业务规则散落"的问题,让实体充血、自己管自己
- 核心概念:实体(有 ID)/值对象(无 ID 不可变)/聚合根(聚合入口)/仓储(持久化接口)/领域事件
- 分层:领域层在中心,不依赖框架和数据库
- 战略设计:限界上下文,按业务边界拆系统
- 复杂业务才用,简单 CRUD 别硬套
想了解 DDD 分层再往下走是什么,看《分层架构》和《Clean Architecture 整洁架构》——它们是"领域核心 + 依赖指向中心"这条线的延续。
