DDD领域驱动设计深度解析:从理论到代码实践
标签:DDD 领域驱动设计 架构设计 微服务 充血模型 聚合根
摘要:本文从战略设计到战术设计,深入剖析DDD核心概念,包含聚合根设计、值对象实现、领域事件机制、仓储模式落地,以及贫血模型与充血模型的本质区别与选型建议,附完整代码示例与生产级实践指南。
一、DDD核心概念与架构分层
1.1 DDD四层架构
┌─────────────────────────────────────────────────────────┐
│ User Interface │
│ 用户接口层(适配器) │
│ Controller / DTO / Assembler / VO转换 │
│ 职责:协议转换、参数校验、权限检查 │
├─────────────────────────────────────────────────────────┤
│ Application │
│ 应用层(编排器) │
│ Service / EventSubscriber / 流程编排 │
│ 职责:用例编排、事务控制、领域事件发布 │
│ 原则:无业务逻辑,只负责协调领域层 │
├─────────────────────────────────────────────────────────┤
│ Domain │
│ 领域层(核心) │
│ Aggregate / Entity / ValueObject / DomainService │
│ DomainEvent / Repository Interface / Specification │
│ 职责:业务规则、领域模型、不变量维护 │
│ 原则:纯内存操作,不依赖基础设施 │
├─────────────────────────────────────────────────────────┤
│ Infrastructure │
│ 基础设施层 │
│ RepositoryImpl / MQProducer / Cache / RPC Client │
│ 职责:技术实现、数据持久化、外部服务调用 │
│ 原则:实现领域层定义的接口,依赖倒置 │
└─────────────────────────────────────────────────────────┘
依赖关系:上层 → 下层(Domain不依赖任何层,通过DIP依赖反转)
1.2 战术设计核心概念映射
| DDD概念 | 对应代码元素 | 核心特征 | 示例 |
|---|---|---|---|
| 聚合根 | Order实体 |
边界内一致性维护者,全局唯一标识 | 订单聚合根包含订单项、地址 |
| 实体 | OrderItem |
有唯一标识(局部或全局),可变状态 | 订单项(聚合内唯一ID) |
| 值对象 | Address / Money |
无标识,不可变,通过属性判等 | 地址、金额、日期范围 |
| 领域事件 | OrderCreatedEvent |
领域内发生的业务事实,用于解耦 | 订单已创建、库存已扣减 |
| 仓储 | OrderRepository |
聚合根的持久化抽象,领域层定义接口 | 保存、查询、删除聚合根 |
| 领域服务 | OrderDomainService |
跨聚合的业务逻辑,无状态 | 订单价格计算、库存检查 |
| 工厂 | OrderFactory |
复杂聚合根的创建逻辑封装 | 创建订单时初始化默认状态 |
二、聚合根(Aggregate Root)深度剖析
2.1 聚合根设计原则
┌─────────────────────────────────────────────────────────┐
│ Order(聚合根) │
│ 唯一标识:OrderId │
├─────────────────────────────────────────────────────────┤
│ 属性: │
│ - OrderId id(全局唯一标识) │
│ - OrderStatus status(状态:CREATED/PAID/SHIPPED) │
│ - Money totalAmount(总金额,值对象) │
│ - Address shippingAddress(收货地址,值对象) │
│ - List<OrderItem> items(订单项,实体集合) │
│ - DateTime createTime(创建时间) │
├─────────────────────────────────────────────────────────┤
│ 业务方法(充血模型): │
│ + pay(Money payment) → 支付逻辑,状态流转 │
│ + ship(TrackingNumber trackingNo) → 发货逻辑 │
│ + cancel(String reason) → 取消逻辑,业务规则校验 │
│ + addItem(Product product, int quantity) → 添加商品 │
│ + removeItem(OrderItemId itemId) → 移除商品 │
│ + calculateTotal() → 重新计算总价 │
├─────────────────────────────────────────────────────────┤
│ 不变量(Invariants): │
│ - 订单必须包含至少一个订单项 │
│ - 已支付订单不能修改订单项 │
│ - 已发货订单不能取消 │
│ - 总金额 = sum(订单项小计) │
└─────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌───────────┴───┐ ┌───────┴───────┐ ┌───┴───────────┐
│ OrderItem │ │ Address │ │ Money │
│ (实体) │ │ (值对象) │ │ (值对象) │
│ │ │ │ │ │
│ - OrderItemId │ │ - Province │ │ - amount │
│ - ProductId │ │ - City │ │ - currency │
│ - ProductName │ │ - District │ │ │
│ - Money price │ │ - Detail │ │ + add() │
│ - int quantity│ │ │ │ + subtract() │
│ │ │ + validate() │ │ + multiply() │
│ + subtotal() │ │ + format() │ │ │
└───────────────┘ └───────────────┘ └───────────────┘
2.2 聚合根源码实现
java
/**
* 聚合根抽象基类
*/
public abstract class AggregateRoot<ID extends Identifier> extends Entity<ID> {
private final List<DomainEvent> domainEvents = new ArrayList<>();
/**
* 注册领域事件
*/
protected void registerEvent(DomainEvent event) {
domainEvents.add(event);
}
/**
* 清除领域事件(发布后调用)
*/
public void clearDomainEvents() {
domainEvents.clear();
}
public List<DomainEvent> getDomainEvents() {
return Collections.unmodifiableList(domainEvents);
}
}
/**
* 订单聚合根(充血模型)
*/
public class Order extends AggregateRoot<OrderId> {
// ==================== 属性(私有,封装) ====================
private OrderStatus status;
private Money totalAmount;
private Address shippingAddress;
private final List<OrderItem> items = new ArrayList<>();
private LocalDateTime createTime;
private LocalDateTime payTime;
private LocalDateTime shipTime;
// ==================== 构造器(工厂方法控制创建) ====================
/**
* 私有构造器,强制通过工厂方法创建
*/
private Order() {}
/**
* 工厂方法:创建订单(维护创建时不变量)
*/
public static Order create(OrderId id, Address address, List<OrderItem> items) {
// 校验不变量
if (items == null || items.isEmpty()) {
throw new IllegalArgumentException("Order must contain at least one item");
}
if (address == null || !address.isValid()) {
throw new IllegalArgumentException("Invalid shipping address");
}
Order order = new Order();
order.setId(id);
order.shippingAddress = address;
order.status = OrderStatus.CREATED;
order.createTime = LocalDateTime.now();
// 添加订单项(内部方法,确保一致性)
items.forEach(order::addItemInternal);
// 计算总价
order.calculateTotal();
// 注册领域事件
order.registerEvent(new OrderCreatedEvent(id, order.totalAmount, LocalDateTime.now()));
return order;
}
// ==================== 业务方法(充血模型核心) ====================
/**
* 支付订单(业务规则封装)
*/
public void pay(Money payment) {
// 状态校验
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("Only created order can be paid, current: " + status);
}
// 金额校验
if (!payment.equals(this.totalAmount)) {
throw new IllegalArgumentException("Payment amount mismatch");
}
// 状态流转
this.status = OrderStatus.PAID;
this.payTime = LocalDateTime.now();
// 注册领域事件
registerEvent(new OrderPaidEvent(getId(), payment, LocalDateTime.now()));
}
/**
* 发货(业务规则封装)
*/
public void ship(TrackingNumber trackingNo) {
if (status != OrderStatus.PAID) {
throw new IllegalStateException("Only paid order can be shipped");
}
if (trackingNo == null || !trackingNo.isValid()) {
throw new IllegalArgumentException("Invalid tracking number");
}
this.status = OrderStatus.SHIPPED;
this.shipTime = LocalDateTime.now();
registerEvent(new OrderShippedEvent(getId(), trackingNo, LocalDateTime.now()));
}
/**
* 取消订单(业务规则封装)
*/
public void cancel(String reason) {
if (status == OrderStatus.SHIPPED || status == OrderStatus.COMPLETED) {
throw new IllegalStateException("Shipped or completed order cannot be cancelled");
}
this.status = OrderStatus.CANCELLED;
registerEvent(new OrderCancelledEvent(getId(), reason, LocalDateTime.now()));
}
/**
* 添加订单项(维护聚合边界内一致性)
*/
public void addItem(Product product, int quantity) {
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("Can only modify items when order is created");
}
// 检查是否已存在相同商品,合并数量
items.stream()
.filter(item -> item.getProductId().equals(product.getId()))
.findFirst()
.ifPresentOrElse(
existing -> existing.increaseQuantity(quantity),
() -> addItemInternal(new OrderItem(product, quantity))
);
calculateTotal();
}
/**
* 移除订单项
*/
public void removeItem(OrderItemId itemId) {
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("Can only modify items when order is created");
}
items.removeIf(item -> item.getId().equals(itemId));
// 维护不变量:订单至少有一个订单项
if (items.isEmpty()) {
throw new IllegalStateException("Order must contain at least one item");
}
calculateTotal();
}
/**
* 计算总价(内部方法)
*/
private void calculateTotal() {
this.totalAmount = items.stream()
.map(OrderItem::subtotal)
.reduce(Money.ZERO, Money::add);
}
private void addItemInternal(OrderItem item) {
items.add(item);
}
// ==================== Getter(只读访问) ====================
public OrderStatus getStatus() { return status; }
public Money getTotalAmount() { return totalAmount; }
public Address getShippingAddress() { return shippingAddress; }
public List<OrderItem> getItems() { return Collections.unmodifiableList(items); }
}
三、值对象(Value Object)深度剖析
3.1 值对象核心特征
| 特征 | 说明 | 代码体现 |
|---|---|---|
| 无标识 | 不通过ID区分,通过属性值判等 | 无id字段,equals()比较所有属性 |
| 不可变性 | 创建后状态不可变,线程安全 | 所有字段final,无setter,修改返回新实例 |
| 可替换性 | 属性变化时直接替换整个对象 | address = new Address(...) |
| 值相等性 | 相同属性值即相等 | 重写equals()和hashCode() |
| 无副作用 | 方法不修改状态,返回新值对象 | money.add(other)返回新Money |
3.2 值对象源码实现
java
/**
* 金额值对象(不可变,线程安全)
*/
public final class Money implements ValueObject<Money> {
public static final Money ZERO = new Money(BigDecimal.ZERO, Currency.CNY);
private final BigDecimal amount;
private final Currency currency;
/**
* 私有构造器,强制使用工厂方法
*/
private Money(BigDecimal amount, Currency currency) {
if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Invalid amount");
}
if (currency == null) {
throw new IllegalArgumentException("Currency cannot be null");
}
this.amount = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP);
this.currency = currency;
}
// ==================== 工厂方法 ====================
public static Money of(BigDecimal amount, Currency currency) {
return new Money(amount, currency);
}
public static Money of(double amount, Currency currency) {
return new Money(BigDecimal.valueOf(amount), currency);
}
public static Money yuan(double amount) {
return new Money(BigDecimal.valueOf(amount), Currency.CNY);
}
// ==================== 业务方法(无副作用,返回新实例) ====================
/**
* 加法
*/
public Money add(Money other) {
checkCurrency(other);
return new Money(this.amount.add(other.amount), this.currency);
}
/**
* 减法
*/
public Money subtract(Money other) {
checkCurrency(other);
BigDecimal result = this.amount.subtract(other.amount);
if (result.compareTo(BigDecimal.ZERO) < 0) {
throw new InsufficientBalanceException("Result cannot be negative");
}
return new Money(result, this.currency);
}
/**
* 乘法(折扣计算)
*/
public Money multiply(double factor) {
return new Money(this.amount.multiply(BigDecimal.valueOf(factor)), this.currency);
}
/**
* 比较
*/
public boolean isGreaterThan(Money other) {
checkCurrency(other);
return this.amount.compareTo(other.amount) > 0;
}
public boolean isZero() {
return this.amount.compareTo(BigDecimal.ZERO) == 0;
}
// ==================== 值对象相等性 ====================
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return amount.compareTo(money.amount) == 0 && currency == money.currency;
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
@Override
public String toString() {
return currency.getSymbol() + amount.toPlainString();
}
// ==================== Getter(只读) ====================
public BigDecimal getAmount() { return amount; }
public Currency getCurrency() { return currency; }
private void checkCurrency(Money other) {
if (this.currency != other.currency) {
throw new CurrencyMismatchException("Cannot operate on different currencies");
}
}
}
/**
* 地址值对象(复合值对象)
*/
public final class Address implements ValueObject<Address> {
private final String province;
private final String city;
private final String district;
private final String detail;
private final String zipCode;
private final String phone;
private Address(Builder builder) {
this.province = Objects.requireNonNull(builder.province);
this.city = Objects.requireNonNull(builder.city);
this.district = Objects.requireNonNull(builder.district);
this.detail = Objects.requireNonNull(builder.detail);
this.zipCode = builder.zipCode;
this.phone = builder.phone;
validate();
}
/**
* 业务方法:验证地址有效性
*/
public boolean isValid() {
return StringUtils.isNotBlank(province)
&& StringUtils.isNotBlank(city)
&& StringUtils.isNotBlank(detail)
&& (zipCode == null || zipCode.matches("\\d{6}"));
}
/**
* 业务方法:格式化地址
*/
public String format() {
return String.format("%s%s%s%s", province, city, district, detail);
}
/**
* 修改地址(返回新实例)
*/
public Address withDetail(String newDetail) {
return new Builder()
.province(this.province)
.city(this.city)
.district(this.district)
.detail(newDetail)
.zipCode(this.zipCode)
.phone(this.phone)
.build();
}
// ==================== Builder模式创建 ====================
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String province;
private String city;
private String district;
private String detail;
private String zipCode;
private String phone;
public Builder province(String province) { this.province = province; return this; }
public Builder city(String city) { this.city = city; return this; }
public Builder district(String district) { this.district = district; return this; }
public Builder detail(String detail) { this.detail = detail; return this; }
public Builder zipCode(String zipCode) { this.zipCode = zipCode; return this; }
public Builder phone(String phone) { this.phone = phone; return this; }
public Address build() {
return new Address(this);
}
}
// equals/hashCode/toString...
}
四、领域事件(Domain Event)机制
4.1 领域事件架构
┌─────────────────────────────────────────────────────────┐
│ Domain Event(领域事件) │
│ OrderCreatedEvent / OrderPaidEvent │
│ 包含:事件ID、发生时间、业务数据 │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ AggregateRoot.registerEvent() │
│ 聚合根业务方法中注册事件 │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ApplicationService(应用层) │
│ 事务提交前:收集所有领域事件 │
│ 事务提交后:发布事件到EventBus │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ EventBus / ApplicationEventPublisher │
│ Spring事件机制或Axon/MQ实现 │
└─────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌───────────┴───┐ ┌───────┴───────┐ ┌───┴───────────┐
│ 同步监听器 │ │ 异步监听器 │ │ Saga流程 │
│ 库存扣减 │ │ 消息通知 │ │ 订单履约 │
│ 积分计算 │ │ 日志记录 │ │ 跨聚合事务 │
└───────────────┘ └───────────────┘ └───────────────┘
4.2 领域事件实现
java
/**
* 领域事件标记接口
*/
public interface DomainEvent {
EventId getEventId();
LocalDateTime getOccurredOn();
}
/**
* 订单创建事件
*/
public class OrderCreatedEvent implements DomainEvent {
private final EventId eventId;
private final LocalDateTime occurredOn;
private final OrderId orderId;
private final Money totalAmount;
private final List<OrderItemDTO> items;
public OrderCreatedEvent(OrderId orderId, Money totalAmount, List<OrderItem> items) {
this.eventId = EventId.generate();
this.occurredOn = LocalDateTime.now();
this.orderId = orderId;
this.totalAmount = totalAmount;
this.items = items.stream()
.map(item -> new OrderItemDTO(
item.getProductId().getValue(),
item.getProductName(),
item.getQuantity()
))
.collect(Collectors.toList());
}
// Getter...
}
/**
* 领域事件监听器(应用层)
*/
@Component
public class OrderEventListener {
@Autowired
private InventoryService inventoryService;
@Autowired
private PointsService pointsService;
/**
* 同步监听:库存扣减(必须在同一事务)
*/
@EventListener
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleOrderCreatedSync(OrderCreatedEvent event) {
// 同一事务内执行,失败则整体回滚
inventoryService.reserve(event.getOrderId(), event.getItems());
}
/**
* 异步监听:积分计算(事务提交后执行)
*/
@EventListener
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleOrderCreatedAsync(OrderCreatedEvent event) {
// 事务已提交,失败不影响主流程
pointsService.calculatePoints(event.getOrderId(), event.getTotalAmount());
}
/**
* 异步监听:发送通知
*/
@EventListener
@Async
public void sendNotification(OrderPaidEvent event) {
// 发送支付成功通知
}
}
/**
* 事务感知的事件发布(应用层Service)
*/
@Service
public class OrderApplicationService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Transactional
public OrderDTO createOrder(CreateOrderCommand command) {
// 1. 创建聚合根(注册领域事件)
Order order = Order.create(
orderRepository.nextId(),
command.getAddress(),
command.getItems()
);
// 2. 保存聚合根
orderRepository.save(order);
// 3. 发布领域事件(Spring自动在事务提交后发布)
// 或使用:TransactionSynchronizationManager注册回调
publishEvents(order);
return OrderAssembler.toDTO(order);
}
private void publishEvents(AggregateRoot<?> aggregate) {
List<DomainEvent> events = aggregate.getDomainEvents();
events.forEach(eventPublisher::publishEvent);
aggregate.clearDomainEvents();
}
}
五、仓储模式(Repository Pattern)
5.1 仓储设计原则
┌─────────────────────────────────────────────────────────┐
│ 领域层(Domain) │
│ interface OrderRepository { │
│ Order findById(OrderId id); │
│ List<Order> findByStatus(OrderStatus status); │
│ Order save(Order order); │
│ void delete(OrderId id); │
│ } │
│ 职责:定义聚合根的持久化契约 │
│ 原则:只操作聚合根,不暴露底层技术细节 │
└─────────────────────────────────────────────────────────┘
△ 依赖倒置(DIP)
│
┌─────────────────────────────────────────────────────────┐
│ 基础设施层(Infrastructure) │
│ class OrderRepositoryImpl implements OrderRepository │
│ JpaOrderRepository / MyBatisOrderMapper │
│ │
│ - 实现领域层定义的接口 │
│ - 处理ORM映射、数据库事务、缓存、MQ等 │
│ - 转换Entity ↔ PO(Data Object) │
└─────────────────────────────────────────────────────────┘
5.2 仓储完整实现
java
// ==================== 领域层:仓储接口 ====================
public interface OrderRepository {
OrderId nextId();
Optional<Order> findById(OrderId id);
/**
* 通过ID查找,不存在则抛异常
*/
default Order findByIdOrThrow(OrderId id) {
return findById(id)
.orElseThrow(() -> new EntityNotFoundException("Order not found: " + id));
}
List<Order> findByBuyerId(BuyerId buyerId, PageParam page);
List<Order> findByStatusAndCreateTimeBefore(OrderStatus status, LocalDateTime time);
Order save(Order order);
void delete(OrderId id);
/**
* 存在性检查(比查询全对象更高效)
*/
boolean existsById(OrderId id);
}
// ==================== 基础设施层:仓储实现 ====================
@Repository
public class OrderRepositoryImpl implements OrderRepository {
@Autowired
private OrderMapper orderMapper; // MyBatis Mapper
@Autowired
private OrderItemMapper itemMapper;
@Autowired
private IdGenerator idGenerator;
@Autowired
private RedisTemplate<String, Order> redisTemplate;
@Override
public OrderId nextId() {
return new OrderId(idGenerator.nextId());
}
@Override
public Optional<Order> findById(OrderId id) {
// 1. 先查缓存
String cacheKey = "order:" + id.getValue();
Order cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return Optional.of(cached);
}
// 2. 查数据库
OrderDO orderDO = orderMapper.selectById(id.getValue());
if (orderDO == null) {
return Optional.empty();
}
// 3. 查订单项
List<OrderItemDO> itemDOs = itemMapper.selectByOrderId(id.getValue());
// 4. 重建聚合根(ORM映射)
Order order = reconstruct(orderDO, itemDOs);
// 5. 写入缓存
redisTemplate.opsForValue().set(cacheKey, order, Duration.ofMinutes(10));
return Optional.of(order);
}
@Override
@Transactional
public Order save(Order order) {
// 1. 转换为数据对象
OrderDO orderDO = OrderConverter.toDataObject(order);
List<OrderItemDO> itemDOs = order.getItems().stream()
.map(OrderConverter::toDataObject)
.collect(Collectors.toList());
// 2. 保存订单主表
if (orderDO.getId() == null) {
orderMapper.insert(orderDO);
} else {
orderMapper.update(orderDO);
}
// 3. 保存订单项(先删后插或增量更新)
itemMapper.deleteByOrderId(order.getId().getValue());
itemMapper.batchInsert(itemDOs);
// 4. 发布领域事件(由应用层调用,这里仅保存)
// 5. 更新缓存
String cacheKey = "order:" + order.getId().getValue();
redisTemplate.opsForValue().set(cacheKey, order, Duration.ofMinutes(10));
return order;
}
/**
* 重建聚合根(从数据状态恢复业务对象)
*/
private Order reconstruct(OrderDO orderDO, List<OrderItemDO> itemDOs) {
// 使用工厂方法或反射重建(绕过业务规则校验,因为数据已存在)
Order order = Order.reconstruct(
new OrderId(orderDO.getId()),
OrderStatus.valueOf(orderDO.getStatus()),
Money.of(orderDO.getTotalAmount(), Currency.valueOf(orderDO.getCurrency())),
Address.builder()
.province(orderDO.getProvince())
.city(orderDO.getCity())
.detail(orderDO.getDetail())
.build(),
itemDOs.stream().map(this::reconstructItem).collect(Collectors.toList()),
orderDO.getCreateTime()
);
return order;
}
}
// ==================== 防腐层:数据对象(DO) ====================
@Data
@TableName("t_order")
public class OrderDO {
private Long id;
private String status;
private BigDecimal totalAmount;
private String currency;
private String province;
private String city;
private String detail;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
六、贫血模型 vs 充血模型深度对比
6.1 核心区别
| 维度 | 贫血模型(Anemic Domain Model) | 充血模型(Rich Domain Model) |
|---|---|---|
| 本质 | 面向过程编程,披着对象外衣 | 面向对象编程,封装+多态+继承 |
| 实体职责 | 只有getter/setter的数据容器 | 封装数据+业务规则+状态流转 |
| 业务逻辑位置 | Service层(事务脚本) | Domain层(领域模型内部) |
| 可维护性 | 业务分散,Service类膨胀 | 高内聚,修改集中在领域对象 |
| 复用性 | 低,逻辑与Service绑定 | 高,领域对象可独立复用 |
| 测试性 | 需Mock依赖,集成测试为主 | 单元测试友好,无外部依赖 |
| 学习成本 | 低,符合传统MVC习惯 | 高,需理解DDD概念 |
| 适用场景 | 简单CRUD、事务脚本、快速原型 | 复杂业务、长期维护、领域复杂 |
6.2 贫血模型代码示例(反模式)
java
/**
* 贫血模型:Order只是数据容器
*/
@Data
public class Order {
private Long id;
private String status; // String而非枚举,无类型安全
private BigDecimal totalAmount;
private List<OrderItem> items;
// 只有getter/setter,无任何业务方法
}
/**
* 贫血模型:所有业务逻辑在Service中
*/
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryService inventoryService;
/**
* 支付逻辑:大量if-else,业务规则散落在Service中
*/
@Transactional
public void pay(Long orderId, BigDecimal amount) {
Order order = orderMapper.selectById(orderId);
// 1. 状态检查(重复代码遍布各个Service)
if (!"CREATED".equals(order.getStatus())) {
throw new RuntimeException("Order status invalid");
}
// 2. 金额检查(BigDecimal比较错误风险)
if (order.getTotalAmount().compareTo(amount) != 0) {
throw new RuntimeException("Amount mismatch");
}
// 3. 更新状态(直接set,无封装)
order.setStatus("PAID");
orderMapper.update(order);
// 4. 扣减库存(业务逻辑与数据访问混杂)
for (OrderItem item : order.getItems()) {
inventoryService.deduct(item.getProductId(), item.getQuantity());
}
// 5. 发送通知(与事务耦合)
sendNotification(order);
}
// 取消、发货等逻辑同样冗长...
}
6.3 充血模型代码示例(DDD推荐)
java
/**
* 充血模型:Order封装业务规则(见第二节完整代码)
* 此处对比关键差异
*/
public class Order extends AggregateRoot<OrderId> {
// 状态使用枚举,类型安全
private OrderStatus status;
// 金额使用值对象Money,封装货币和计算规则
private Money totalAmount;
/**
* 支付逻辑封装在领域对象内部
* 调用方无需了解业务规则细节
*/
public void pay(Money payment) {
// 状态机校验(封装在枚举或状态机中)
status.assertCanTransitionTo(OrderStatus.PAID);
// 金额校验(Money值对象保证类型安全)
if (!this.totalAmount.equals(payment)) {
throw new PaymentAmountMismatchException();
}
// 状态流转
this.status = OrderStatus.PAID;
this.payTime = LocalDateTime.now();
// 注册领域事件(解耦副作用)
registerEvent(new OrderPaidEvent(getId(), payment));
}
}
/**
* 应用层Service:只负责编排,无业务逻辑
*/
@Service
public class OrderApplicationService {
@Autowired
private OrderRepository orderRepository;
/**
* 支付用例:简洁清晰,只表达业务意图
*/
@Transactional
public void pay(OrderId orderId, Money payment) {
// 1. 加载聚合根
Order order = orderRepository.findByIdOrThrow(orderId);
// 2. 执行业务操作(领域层处理所有规则)
order.pay(payment);
// 3. 保存聚合根(自动持久化变更)
orderRepository.save(order);
// 4. 发布领域事件(副作用解耦)
eventPublisher.publishAll(order.getDomainEvents());
}
}
七、生产级DDD实践指南
7.1 模块划分规范
com.example.order
├── application // 应用层
│ ├── service // 应用服务(用例编排)
│ ├── dto // 数据传输对象
│ ├── assembler // DTO ↔ Domain转换
│ └── event // 事件监听
├── domain // 领域层(核心)
│ ├── aggregate // 聚合根
│ ├── entity // 实体
│ ├── vo // 值对象
│ ├── event // 领域事件
│ ├── service // 领域服务(跨聚合逻辑)
│ ├── repository // 仓储接口
│ └── specification // 规格模式
├── infrastructure // 基础设施层
│ ├── repository // 仓储实现
│ ├── persistence // ORM映射
│ ├── external // 外部服务客户端
│ └── config // 配置
└── interfaces // 接口层(可选)
├── rest // Controller
├── mq // 消息监听
└── scheduler // 定时任务
7.2 常见陷阱与解决方案
| 陷阱 | 问题描述 | 解决方案 |
|---|---|---|
| 贫血领域模型 | 只有getter/setter,业务逻辑在Service | 代码审查强制要求业务方法在Domain;使用ArchUnit测试架构 |
| 聚合过大 | 聚合根包含过多实体,性能差 | 按业务一致性边界拆分;延迟加载;考虑最终一致性 |
| 领域事件滥用 | 所有操作都发事件,系统复杂 | 只在跨聚合或异步场景使用;核心流程用同步调用 |
| 值对象可变 | 修改值对象属性,导致并发问题 | 所有字段final;修改方法返回新实例;使用Builder |
| 仓储泄露SQL | Repository返回PO而非Domain | 严格分层;防腐层转换;禁止Domain依赖基础设施 |
| 循环依赖 | 聚合之间双向引用 | 通过ID引用而非对象;领域事件解耦; Saga处理跨聚合事务 |
八、总结
| DDD概念 | 核心要点 | 代码落地 |
|---|---|---|
| 聚合根 | 一致性边界维护者,封装业务规则 | 实体+业务方法+不变量校验+领域事件 |
| 值对象 | 无标识、不可变、通过属性判等 | final字段+无副作用方法+equals/hashCode |
| 领域事件 | 解耦聚合,记录业务事实 | 聚合根注册+应用层发布+事务边界控制 |
| 仓储 | 聚合根的持久化抽象 | 接口在领域层+实现在基础设施层+防腐层转换 |
| 充血模型 | 业务逻辑集中在领域对象 | 拒绝贫血模型,Service只负责编排 |
选型建议:
- 简单CRUD系统:贫血模型+事务脚本,快速交付
- 复杂业务系统:充血模型+DDD,长期维护
- 微服务架构:DDD限界上下文划分服务边界,聚合根对应服务核心
参考文档:
- 《领域驱动设计》Eric Evans
- 《实现领域驱动设计》Vaughn Vernon
- Spring Data DDD Reference
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。