DDD领域驱动设计深度解析:从理论到代码实践

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 版权协议,转载请附上原文出处链接和本声明。

相关推荐
我命由我123451 小时前
Java 开发 - 如何让一个类拥有两个父类
java·服务器·开发语言·后端·java-ee·intellij-idea·intellij idea
说私域2 小时前
以非常6+1体系为支撑 融入AI智能名片商城小程序 提升组织建设效能
大数据·人工智能·小程序·流量运营·私域运营
这儿有一堆花2 小时前
Linux 软件包管理:从源码编译到现代自动化运维
linux·运维·自动化
范什么特西2 小时前
狂神--守护线程
java·linux·服务器
德迅云安全_初启2 小时前
2026年十大危险DNS攻击类型及预防措施
linux·服务器·网络
之歆2 小时前
Bash 循环与函数、Linux 进程管理
linux·chrome·bash
何中应2 小时前
CentOS7安装Maven
java·运维·后端·maven
数琨创享TQMS质量数智化2 小时前
数琨创享:以数智化质量目标管理闭环赋能可量化、可追溯、可驱动的质量运营
大数据·人工智能·qms质量管理系统
Volunteer Technology2 小时前
LangGraph的WorkFlow(二)
linux·windows·python