领域驱动设计与 Spring Boot
约 2006 字大约 7 分钟
布欧-Lewyon
2026-05-16
首页 › Spring Boot › 架构师进阶 › 领域驱动设计与 Spring Boot
建议先阅读模块化与系统边界,理解限界上下文后再深入 DDD。
领域驱动设计(DDD)是由 Eric Evans 提出的软件设计方法论,核心思想是让代码模型与业务领域模型对齐。Spring Boot 不强制 DDD,但它的 IoC、AOP、Repository 抽象、事件机制天然适合 DDD 落地。
核心战术模式
DDD 的战术模式定义了领域模型的基本构建块:
实体(Entity)
有唯一标识、生命周期中状态可变的领域对象:
// 实体——有 @Id 和业务标识
@Entity
@Table(name = "orders")
public class Order {
@Id
private Long id; // 技术主键
@Column(unique = true)
private String orderNo; // 业务标识
@Enumerated(EnumType.STRING)
private OrderStatus status;
private LocalDateTime createdAt;
// 行为封装在实体内部——不要暴露 setter
public void confirm() {
if (this.status != OrderStatus.PENDING) {
throw new IllegalStateException("只有待支付订单可以确认");
}
this.status = OrderStatus.CONFIRMED;
}
public void cancel() {
if (this.status == OrderStatus.SHIPPED) {
throw new IllegalStateException("已发货订单不能取消");
}
this.status = OrderStatus.CANCELLED;
}
}生产意识:实体中的行为方法应抛出语义明确的业务异常(
OrderAlreadyShippedException),而不是返回布尔值或错误码。调用方可以通过异常类型决定 HTTP 状态码。
值对象(Value Object)
无标识、通过属性值判断相等、不可变的领域概念:
// 值对象——用 record(不可变)
public record Money(BigDecimal amount, String currency) {
public Money {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("金额不能为负");
}
if (currency == null || currency.isBlank()) {
throw new IllegalArgumentException("币种不能为空");
}
}
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("币种不一致");
}
return new Money(this.amount.add(other.amount), this.currency);
}
}聚合与聚合根(Aggregate / Aggregate Root)
聚合是一组一致性边界——边界内的实体和值对象在同一个事务中保证一致性。聚合根是外部访问聚合的唯一入口:
// 聚合根:Order
@Entity
@Table(name = "orders")
public class Order {
@Id
private Long id;
@OneToMany(cascade = ALL, orphanRemoval = true)
@JoinColumn(name = "order_id")
private List<OrderItem> items = new ArrayList<>(); // 子实体
private Money total; // 快照值对象
// 外部只能通过 Order 操作 items——不能直接操作 OrderItem
public void addItem(OrderItem item) {
this.items.add(item);
recalculateTotal();
}
public void removeItem(Long itemId) {
this.items.removeIf(i -> i.getId().equals(itemId));
recalculateTotal();
}
private void recalculateTotal() {
// 聚合内部计算
}
}
// 子实体——属于聚合内部
@Entity
@Table(name = "order_items")
public class OrderItem {
@Id
private Long id;
private String productName;
private int quantity;
private Money price;
}项目结构(六边形架构)
DDD 经典的分层结构结合 Spring Boot 的模块化能力:
com.example.order
├── domain/ # 领域层
│ ├── model/ # 实体 + 值对象 + 聚合
│ │ ├── Order.java
│ │ ├── OrderItem.java
│ │ ├── OrderStatus.java
│ │ └── Money.java
│ ├── service/ # 领域服务
│ │ └── OrderDomainService.java
│ ├── repository/ # Repository 接口(领域层定义)
│ │ └── OrderRepository.java
│ └── event/ # 领域事件
│ ├── OrderConfirmedEvent.java
│ └── OrderEventPublisher.java
│
├── application/ # 应用层
│ └── service/
│ └── OrderApplicationService.java
│
├── infrastructure/ # 基础设施层
│ ├── persistence/ # JPA/MyBatis 实现
│ │ └── JpaOrderRepository.java
│ ├── messaging/ # 消息实现
│ │ └── RabbitOrderEventPublisher.java
│ └── config/
│
└── web/ # 接口层(Controller)
└── controller/
└── OrderController.java分层依赖规则
web → application → domain ← infrastructure(实现 domain 接口)
↑
infrastructure(实现 Repository)domain/零外部依赖——不依赖 Spring、不依赖 JPA 注解(除@Entity外)。application/依赖domain/,编排领域对象完成用例。infrastructure/实现domain/定义的接口。web/依赖application/,不直接访问domain/。
DDD 在 Spring Boot 中的落地示例
领域层:纯 POJO
// domain/model/Order.java
public class Order {
private OrderId id;
private OrderStatus status;
private List<OrderItem> items;
private Money total;
public Order(OrderId id) {
this.id = id;
this.status = OrderStatus.PENDING;
this.items = new ArrayList<>();
this.total = Money.ZERO;
}
public OrderId getId() { return id; }
public OrderStatus getStatus() { return status; }
public void confirm() {
if (status != OrderStatus.PENDING) {
throw new OrderAlreadyConfirmedException(id);
}
this.status = OrderStatus.CONFIRMED;
// 触发领域事件——由基础设施层处理
DomainEventPublisher.instance().publish(new OrderConfirmedEvent(id));
}
// 只有 getter——没有 setter
}应用层:编排服务
// application/service/OrderApplicationService.java
@Service
@Transactional
public class OrderApplicationService {
private final OrderRepository orderRepository;
private final OrderDomainService orderDomainService;
public OrderApplicationService(OrderRepository orderRepository,
OrderDomainService orderDomainService) {
this.orderRepository = orderRepository;
this.orderDomainService = orderDomainService;
}
public void confirmOrder(Long orderId) {
// ① 从 Repository 获取聚合
Order order = orderRepository.findById(new OrderId(orderId))
.orElseThrow(() -> new OrderNotFoundException(orderId));
// ② 领域服务校验
orderDomainService.validateBeforeConfirm(order);
// ③ 聚合内执行业务逻辑
order.confirm();
// ④ Repository 保存(ORM 自动 Flush)
orderRepository.save(order);
}
}基础设施层:Repository 实现
// infrastructure/persistence/JpaOrderRepository.java
@Repository
public class JpaOrderRepository implements OrderRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public Optional<Order> findById(OrderId orderId) {
return Optional.ofNullable(
entityManager.find(Order.class, orderId.getValue()));
}
@Override
public void save(Order order) {
entityManager.persist(order);
}
}领域事件
领域事件是 DDD 中解耦聚合的关键手段。Spring Boot 的 ApplicationEventPublisher 天然适合作为事件总线:
// domain/event/OrderConfirmedEvent.java
public record OrderConfirmedEvent(OrderId orderId, LocalDateTime confirmedAt) {}// 在应用层或领域层发布事件
@Service
public class OrderApplicationService {
private final ApplicationEventPublisher eventPublisher;
public void confirmOrder(Long orderId) {
Order order = orderRepository.findById(new OrderId(orderId)).orElseThrow();
order.confirm();
orderRepository.save(order);
// 发布领域事件
eventPublisher.publishEvent(new OrderConfirmedEvent(order.getId(), LocalDateTime.now()));
}
}// 在基础设施层监听事件(发送消息、更新写模型等)
@Component
public class OrderEventListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderConfirmed(OrderConfirmedEvent event) {
// 事务已提交——可以安全发送消息
rabbitTemplate.convertAndSend("order.confirmed", event);
}
}什么时候用 DDD
DDD 并非适用于所有项目,需根据实际情况权衡:
| 场景 | 推荐模式 | 原因 |
|---|---|---|
| CRUD 管理系统、报表 | 贫血模型 + 三层架构 | DDD 增加复杂度,收益微薄 |
| 交易系统、风控、定价引擎 | DDD 聚合 + 领域服务 | 业务规则复杂,需要封装与测试 |
| 多团队协作的大型项目 | DDD 限界上下文 + 六边形架构 | 上下文边界清晰,利于并行开发 |
| 微服务架构 | DDD 限界上下文对应微服务 | 每个服务一个聚合,事件驱动通信 |
贫血模型 vs 充血模型
// ❌ 贫血模型(Anti-Pattern)——实体只是数据容器,逻辑在 Service 中
@Entity
public class Order {
private String status; // 直接暴露 String
// getter / setter
}
@Service
public class OrderService {
public void confirm(Long id) {
Order order = repo.findById(id);
if (!"PENDING".equals(order.getStatus())) {
throw new RuntimeException("...");
}
order.setStatus("CONFIRMED"); // setter 暴露给任何调用方
}
}
// ✅ 充血模型——实体有自己的行为
@Entity
public class Order {
private OrderStatus status; // 枚举类型
public void confirm() {
if (status != OrderStatus.PENDING) {
throw new OrderAlreadyConfirmedException();
}
this.status = OrderStatus.CONFIRMED;
}
}贫血模型的问题:业务逻辑分散在 Service 层,当需求变更时需要修改多个 Service 方法。充血模型将逻辑封装在实体中,变更影响范围缩小到实体内部。Spring Data JPA 对充血模型友好——只改实体的状态字段,
save()时自动 flush。
小结
- DDD 核心战术模式:Entity(有标识)、Value Object(无标识不可变)、Aggregate(一致性边界)、Domain Service(跨聚合逻辑)、Domain Event(解耦)。
- 六边形架构:
domain/零依赖 →application/编排 →infrastructure/实现 →web/入口。 - 聚合根是外部访问的唯一入口,子实体只能通过聚合根操作。
- 领域事件通过 Spring 的
ApplicationEventPublisher+@TransactionalEventListener实现事务边界内的事件发布。 - 贫血模型 vs 充血模型:CRUD 选贫血,复杂业务选充血。
- 易错:不要将 JPA 的
@Entity等同于 DDD 的 Entity——JPA Entity 只是持久化映射,DDD Entity 是业务概念。过度使用 DDD 会导致不必要的抽象("为每个值对象建一个类"),先对最核心的业务聚合采用 DDD,外围 CRUD 保持简单。Spring Boot 本身不强制 DDD——它是工具,不是框架。 - 思考任务:从项目中选一个核心聚合(如订单、合同),用六边形架构重构——将业务逻辑从 Service 层移到 Entity 中,通过
ApplicationEventPublisher解耦跨聚合逻辑。
上一节:平台与工程治理
下一节:可观测性
