AI摘要

文章以供应链系统重构为例,展示从贫血模型到DDD的演进:先指出贫血模型“失忆、碎片化、难维护”的痛点;再给出富血实体、值对象、领域服务、聚合、领域事件等战术设计,让业务规则内聚于代码;通过测试、分层架构、渐进式迁移策略,实现“代码即文档”,最终达成业务与技术同频,提升系统可维护性与响应变化能力。
前言:在我工作的第四年,接手了一个“代码写得不错,但业务逻辑看不懂”的供应链系统。实体类里全是getter/setter,Service层里充斥着if(status == 1){...}else if(status == 2){...}。更可怕的是,业务专家说“订单超过30分钟未支付自动取消”,代码里却分散在三个地方:定时任务、支付回调、订单查询。这次经历让我意识到:如果代码不能清晰表达业务,那它只是在模拟业务,而不是实现业务

一、 从“数据表驱动”到“领域驱动”的认知转变

传统贫血模型的困局

先看看我接手的那个“反例”——一个典型的贫血模型实现:

// 贫血的订单实体(只是个数据容器)
@Data
@Entity
@Table(name = "t_order")
public class Order {
    @Id
    private Long id;
    private String orderNo;
    private BigDecimal amount;
    private Integer status; // 1-待支付 2-已支付 3-已发货 4-已完成 5-已取消
    private Long userId;
    private LocalDateTime createTime;
    private LocalDateTime payTime;
    private LocalDateTime deliverTime;
    private LocalDateTime completeTime;
    private LocalDateTime cancelTime;
    private String cancelReason;
    // 还有30多个字段...
}

// 贫血的服务层(承载所有业务逻辑)
@Service
@Transactional
@Slf4j
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private MessageService messageService;
    
    /**
     * 创建订单(业务逻辑分散在各个Service中)
     */
    public Long createOrder(CreateOrderRequest request) {
        // 1. 参数校验(业务规则?)
        if (request.getItems() == null || request.getItems().isEmpty()) {
            throw new RuntimeException("订单商品不能为空");
        }
        if (request.getUserId() == null) {
            throw new RuntimeException("用户ID不能为空");
        }
        
        // 2. 创建订单实体(只是set数据)
        Order order = new Order();
        order.setOrderNo(generateOrderNo());
        order.setUserId(request.getUserId());
        order.setAmount(calculateAmount(request.getItems()));
        order.setStatus(1); // 硬编码状态:待支付
        order.setCreateTime(LocalDateTime.now());
        
        // 3. 保存订单
        Order savedOrder = orderRepository.save(order);
        
        // 4. 扣减库存(业务逻辑在另一个Service)
        inventoryService.deductStock(request.getItems());
        
        // 5. 发送消息(另一个Service)
        messageService.sendOrderCreatedMessage(savedOrder.getId());
        
        return savedOrder.getId();
    }
    
    /**
     * 支付订单(if-else的海洋)
     */
    public void payOrder(PayOrderRequest request) {
        Order order = orderRepository.findById(request.getOrderId())
            .orElseThrow(() -> new RuntimeException("订单不存在"));
        
        // 状态检查(业务规则分散在各处)
        if (order.getStatus() != 1) {
            throw new RuntimeException("订单状态不是待支付,不能支付");
        }
        
        // 支付金额校验
        if (order.getAmount().compareTo(request.getPayAmount()) != 0) {
            throw new RuntimeException("支付金额不正确");
        }
        
        // 调用支付服务
        PaymentResult result = paymentService.pay(
            order.getUserId(), order.getAmount(), request.getPayMethod());
        
        if (result.isSuccess()) {
            // 更新订单状态
            order.setStatus(2); // 硬编码:已支付
            order.setPayTime(LocalDateTime.now());
            orderRepository.save(order);
            
            // 发送支付成功消息
            messageService.sendPaymentSuccessMessage(order.getId());
            
            // 触发发货检查(又调用另一个Service)
            deliveryService.checkAndDeliver(order.getId());
        } else {
            throw new RuntimeException("支付失败: " + result.getErrorMessage());
        }
    }
    
    /**
     * 取消订单(重复的状态检查)
     */
    public void cancelOrder(Long orderId, String reason) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new RuntimeException("订单不存在"));
        
        // 又来了!重复的状态检查逻辑
        if (order.getStatus() != 1 && order.getStatus() != 2) {
            throw new RuntimeException("订单当前状态不能取消");
        }
        
        // 检查是否超过30分钟(业务规则!但写在Service里)
        if (order.getStatus() == 1) { // 待支付订单
            LocalDateTime now = LocalDateTime.now();
            if (order.getCreateTime().plusMinutes(30).isAfter(now)) {
                // 未超时,可以取消
                order.setStatus(5); // 已取消
                order.setCancelTime(now);
                order.setCancelReason(reason);
                orderRepository.save(order);
                
                // 恢复库存
                inventoryService.restoreStock(order.getId());
            } else {
                throw new RuntimeException("待支付订单超过30分钟,系统会自动取消");
            }
        } else if (order.getStatus() == 2) { // 已支付订单
            // 不同的取消逻辑...
        }
    }
}

问题诊断

这种代码有四个致命问题

  1. 贫血失忆症Order类不知道自己能做什么,只是数据的搬运工
  2. 业务逻辑碎片化:状态检查、业务规则分散在各个Service方法中
  3. 重复代码泛滥:同样的状态检查逻辑出现在多个地方
  4. 业务知识丢失:为什么是30分钟?为什么某些状态不能取消?这些业务知识淹没在代码里

二、 DDD的核心武器:用代码表达业务

第一步:识别核心领域和限界上下文

我和业务专家一起工作坊,画出了供应链系统的核心领域:

供应链核心域:
┌─────────────────────────────────────────────────────────┐
│                   订单域 (Order)                         │
│  - 订单创建、支付、取消、发货、完成                       │
│  - 核心规则:支付超时、库存校验、状态流转                 │
├─────────────────────────────────────────────────────────┤
│                   商品域 (Product)                       │
│  - 商品信息、库存管理、价格策略                          │
│  - 核心规则:库存扣减、价格计算                          │
├─────────────────────────────────────────────────────────┤
│                   用户域 (User)                          │
│  - 用户信息、地址管理、积分体系                          │
│  - 核心规则:地址校验、积分计算                          │
└─────────────────────────────────────────────────────────┘

关键洞察:订单域是我们的核心域,其他是支撑域。我们决定从订单域开始DDD改造。

第二步:设计领域模型(战术模式落地)

1. 实体(Entity)设计:有身份和行为

/**
 * 订单聚合根 - 富血模型
 * 核心原则:实体不是数据容器,是业务概念的载体
 */
@AggregateRoot
@Entity
@Table(name = "t_order")
@Getter // 注意:只有getter,没有setter!
@Slf4j
public class Order extends BaseEntity {
    
    @EmbeddedId
    private OrderId id; // 值对象作为ID
    
    private OrderNo orderNo; // 值对象:订单号
    private Money amount; // 值对象:金额
    
    @Enumerated(EnumType.STRING)
    private OrderStatus status; // 枚举:状态
    
    @Embedded
    private CustomerInfo customerInfo; // 值对象:客户信息
    
    @Embedded
    private Address shippingAddress; // 值对象:收货地址
    
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "order_id")
    private List<OrderItem> items = new ArrayList<>(); // 实体集合
    
    @Embedded
    private OrderTimeline timeline; // 值对象:时间线
    
    // 重要:保护无参构造器,仅给JPA使用
    protected Order() {}
    
    /**
     * 创建订单 - 静态工厂方法
     * 业务规则:必须有商品、必须有客户、必须计算金额
     */
    public static Order create(
        CustomerInfo customerInfo,
        Address shippingAddress,
        List<OrderItem> items,
        OrderPolicy orderPolicy) {
        
        // 业务规则校验(在实体内部!)
        if (items == null || items.isEmpty()) {
            throw new DomainException("订单必须包含商品");
        }
        if (customerInfo == null) {
            throw new DomainException("客户信息不能为空");
        }
        
        // 应用业务策略:计算金额
        Money amount = orderPolicy.calculateAmount(items);
        
        // 创建订单(业务概念的体现)
        Order order = new Order();
        order.id = OrderId.nextId();
        order.orderNo = OrderNo.generate();
        order.customerInfo = customerInfo;
        order.shippingAddress = shippingAddress;
        order.items = new ArrayList<>(items);
        order.amount = amount;
        order.status = OrderStatus.CREATED; // 初始状态
        order.timeline = OrderTimeline.create();
        
        // 领域事件:订单已创建
        order.registerEvent(new OrderCreatedEvent(order.id, order.orderNo));
        
        log.info("订单创建成功: {}", order.orderNo);
        return order;
    }
    
    /**
     * 支付订单 - 核心业务行为
     * 业务规则:只能支付待支付订单、金额必须匹配、更新支付时间
     */
    public void pay(Payment payment, PaymentValidator validator) {
        // 业务规则:状态检查
        if (!status.canPay()) {
            throw new DomainException(
                String.format("订单[%s]当前状态[%s]不能支付", orderNo, status));
        }
        
        // 业务规则:金额校验
        if (!amount.equals(payment.getAmount())) {
            throw new DomainException(
                String.format("支付金额[%s]与订单金额[%s]不匹配", 
                    payment.getAmount(), amount));
        }
        
        // 业务规则:支付方式校验(使用领域服务)
        validator.validate(payment);
        
        // 状态转换(业务逻辑的核心)
        this.status = OrderStatus.PAID;
        this.timeline.recordPaid(LocalDateTime.now());
        
        // 领域事件:订单已支付
        registerEvent(new OrderPaidEvent(this.id, payment.getPaymentNo()));
        
        log.info("订单支付成功: {}, 支付方式: {}", orderNo, payment.getMethod());
    }
    
    /**
     * 取消订单 - 复杂的业务逻辑
     * 业务规则:不同状态有不同的取消逻辑
     */
    public void cancel(CancelReason reason, CancelPolicy cancelPolicy) {
        // 业务规则:能否取消
        if (!status.canCancel()) {
            throw new DomainException(
                String.format("订单[%s]当前状态[%s]不能取消", orderNo, status));
        }
        
        // 业务策略:应用取消策略(如超时检查)
        CancelResult cancelResult = cancelPolicy.apply(this, reason);
        
        if (cancelResult.isAllowed()) {
            // 执行取消
            this.status = OrderStatus.CANCELLED;
            this.timeline.recordCancelled(LocalDateTime.now(), reason);
            
            // 领域事件:订单已取消
            registerEvent(new OrderCancelledEvent(
                this.id, 
                this.status, 
                reason
            ));
            
            log.info("订单取消成功: {}, 原因: {}", orderNo, reason);
        } else {
            throw new DomainException(cancelResult.getRejectReason());
        }
    }
    
    /**
     * 检查支付超时 - 业务规则的具体体现
     * 业务规则:创建后30分钟未支付自动取消
     */
    public boolean isPaymentTimeout() {
        if (status != OrderStatus.CREATED) {
            return false;
        }
        
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime timeoutTime = timeline.getCreatedTime().plusMinutes(30);
        
        return now.isAfter(timeoutTime);
    }
    
    // 其他业务行为:发货、完成、退货等...
    
    /**
     * 关键:没有setter!业务状态只能通过业务方法改变
     */
}

2. 值对象(Value Object)设计:描述特征,没有身份

/**
 * 订单号 - 值对象
 * 特征:不可变、无标识、自解释
 */
@Embeddable
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode
public class OrderNo implements Serializable {
    
    private String value;
    
    // 工厂方法
    public static OrderNo generate() {
        // 业务规则:订单号生成规则
        String timestamp = LocalDateTime.now().format(
            DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        String random = String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
        return new OrderNo("ORD" + timestamp + random);
    }
    
    // 业务方法
    public String getDisplayNo() {
        return value.substring(0, 4) + "..." + value.substring(value.length() - 4);
    }
    
    // 验证方法
    public boolean isValid() {
        return value != null && value.startsWith("ORD") && value.length() == 23;
    }
    
    @Override
    public String toString() {
        return value;
    }
}

/**
 * 金额 - 值对象
 * 封装金额相关的所有业务规则
 */
@Embeddable
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Money implements Comparable<Money>, Serializable {
    
    private static final Currency CNY = Currency.getInstance("CNY");
    
    private BigDecimal amount;
    private Currency currency;
    
    // 工厂方法
    public static Money of(BigDecimal amount) {
        return new Money(amount.setScale(2, RoundingMode.HALF_UP), CNY);
    }
    
    public static Money zero() {
        return of(BigDecimal.ZERO);
    }
    
    // 业务操作
    public Money add(Money other) {
        validateSameCurrency(other);
        return new Money(amount.add(other.amount), currency);
    }
    
    public Money subtract(Money other) {
        validateSameCurrency(other);
        return new Money(amount.subtract(other.amount), currency);
    }
    
    public Money multiply(BigDecimal multiplier) {
        return new Money(amount.multiply(multiplier).setScale(2, RoundingMode.HALF_UP), currency);
    }
    
    // 业务规则:不能为负
    public boolean isPositive() {
        return amount.compareTo(BigDecimal.ZERO) > 0;
    }
    
    public boolean isNegative() {
        return amount.compareTo(BigDecimal.ZERO) < 0;
    }
    
    private void validateSameCurrency(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new DomainException("货币类型不一致");
        }
    }
    
    @Override
    public int compareTo(Money other) {
        validateSameCurrency(other);
        return this.amount.compareTo(other.amount);
    }
    
    @Override
    public String toString() {
        return currency.getSymbol() + amount;
    }
}

3. 领域服务(Domain Service)设计:协调多个实体的业务逻辑

/**
 * 订单支付领域服务
 * 职责:协调订单、支付记录、库存等多个实体的复杂业务逻辑
 */
@Service
@Transactional
@Slf4j
public class OrderPaymentService {
    
    private final OrderRepository orderRepository;
    private final PaymentRepository paymentRepository;
    private final InventoryService inventoryService;
    private final DomainEventPublisher eventPublisher;
    
    /**
     * 执行支付 - 跨聚合的业务逻辑
     */
    public PaymentResult executePayment(PayOrderCommand command) {
        // 1. 获取订单聚合根
        Order order = orderRepository.findById(command.getOrderId())
            .orElseThrow(() -> new OrderNotFoundException(command.getOrderId()));
        
        // 2. 创建支付记录(另一个聚合根)
        Payment payment = Payment.create(
            command.getPaymentNo(),
            order.getId(),
            order.getAmount(),
            command.getPayMethod(),
            command.getPayTime()
        );
        
        try {
            // 3. 调用订单的支付行为(核心业务逻辑在实体内部)
            order.pay(payment, new DefaultPaymentValidator());
            
            // 4. 保存支付记录
            paymentRepository.save(payment);
            
            // 5. 预留库存(调用领域服务)
            inventoryService.reserveStock(order);
            
            // 6. 发布领域事件
            eventPublisher.publishAll(order.getDomainEvents());
            order.clearDomainEvents();
            
            return PaymentResult.success(payment.getPaymentNo());
            
        } catch (DomainException e) {
            log.error("订单支付失败: {}", e.getMessage());
            payment.fail(e.getMessage());
            paymentRepository.save(payment);
            
            return PaymentResult.failure(e.getMessage());
        }
    }
}

4. 领域事件(Domain Event)设计:解耦业务逻辑

/**
 * 订单已支付领域事件
 * 特征:用过去时态命名,包含事件发生的时间
 */
@Getter
@AllArgsConstructor
public class OrderPaidEvent implements DomainEvent {
    
    private OrderId orderId;
    private String paymentNo;
    private LocalDateTime occurredOn;
    
    public OrderPaidEvent(OrderId orderId, String paymentNo) {
        this.orderId = orderId;
        this.paymentNo = paymentNo;
        this.occurredOn = LocalDateTime.now();
    }
    
    @Override
    public String getEventType() {
        return "order.paid";
    }
}

/**
 * 领域事件处理器 - 发送支付成功通知
 * 关键:响应式处理,不阻塞主流程
 */
@Component
@Slf4j
public class OrderPaidEventHandler {
    
    private final MessageService messageService;
    private final DeliveryService deliveryService;
    
    @EventListener
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleOrderPaid(OrderPaidEvent event) {
        log.info("处理订单支付事件: {}", event.getOrderId());
        
        try {
            // 1. 发送支付成功通知(非核心业务)
            messageService.sendPaymentSuccessNotification(event.getOrderId());
            
            // 2. 触发发货检查(异步)
            deliveryService.scheduleDeliveryCheck(event.getOrderId());
            
        } catch (Exception e) {
            log.error("处理订单支付事件失败: {}", e.getMessage(), e);
            // 重要:事件处理失败不应影响主流程
        }
    }
}

三、 聚合设计:保护业务不变性

订单聚合的边界设计

/**
 * 订单聚合 - 定义聚合边界
 * 设计原则:一个事务只修改一个聚合
 */
@Aggregate
public class OrderAggregate {
    
    // 聚合根
    private Order root;
    
    // 聚合内的实体(都在一个事务边界内)
    private List<OrderItem> items;
    private List<OrderLog> logs;
    
    // 业务规则:总金额必须等于所有商品金额之和
    public void validateTotalAmount() {
        Money total = items.stream()
            .map(OrderItem::getSubtotal)
            .reduce(Money.zero(), Money::add);
        
        if (!root.getAmount().equals(total)) {
            throw new DomainException("订单金额校验失败");
        }
    }
}

/**
 * 订单项 - 聚合内的实体
 * 特征:没有自己的Repository,生命周期由聚合根管理
 */
@Entity
@Table(name = "t_order_item")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderItem {
    
    @EmbeddedId
    private OrderItemId id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;
    
    private ProductId productId;
    private String productName;
    private Money unitPrice;
    private Integer quantity;
    
    // 业务方法
    public Money getSubtotal() {
        return unitPrice.multiply(new BigDecimal(quantity));
    }
    
    // 工厂方法
    public static OrderItem create(
        Order order,
        ProductId productId,
        String productName,
        Money unitPrice,
        Integer quantity) {
        
        // 业务规则校验
        if (quantity <= 0) {
            throw new DomainException("商品数量必须大于0");
        }
        if (unitPrice.isNegative()) {
            throw new DomainException("商品单价不能为负");
        }
        
        OrderItem item = new OrderItem();
        item.id = OrderItemId.nextId();
        item.order = order;
        item.productId = productId;
        item.productName = productName;
        item.unitPrice = unitPrice;
        item.quantity = quantity;
        
        return item;
    }
}

四、 应用服务设计:薄薄的一层

/**
 * 订单应用服务
 * 职责:协调领域对象、事务管理、权限校验(非业务逻辑)
 */
@Service
@Transactional
@RequiredArgsConstructor
public class OrderApplicationService {
    
    private final OrderRepository orderRepository;
    private final OrderFactory orderFactory;
    private final OrderPaymentService orderPaymentService;
    private final DomainEventPublisher eventPublisher;
    
    /**
     * 创建订单 - 应用服务方法
     */
    public OrderId createOrder(CreateOrderCommand command) {
        // 1. 参数校验(技术层面)
        Validate.notNull(command, "命令不能为空");
        Validate.notEmpty(command.getItems(), "订单商品不能为空");
        
        // 2. 权限校验(技术层面)
        SecurityUtils.checkPermission("ORDER_CREATE");
        
        // 3. 调用领域层创建订单(业务逻辑在领域层)
        Order order = orderFactory.createOrder(
            command.getCustomerId(),
            command.getShippingAddress(),
            command.getItems()
        );
        
        // 4. 保存聚合根
        orderRepository.save(order);
        
        // 5. 发布领域事件
        eventPublisher.publishAll(order.getDomainEvents());
        
        return order.getId();
    }
    
    /**
     * 支付订单 - 应用服务方法
     */
    public PaymentResult payOrder(PayOrderCommand command) {
        // 技术层面的校验
        Validate.notNull(command, "支付命令不能为空");
        
        // 委托给领域服务处理业务逻辑
        return orderPaymentService.executePayment(command);
    }
    
    /**
     * 取消订单 - 应用服务方法
     */
    public void cancelOrder(CancelOrderCommand command) {
        Order order = orderRepository.findById(command.getOrderId())
            .orElseThrow(() -> new OrderNotFoundException(command.getOrderId()));
        
        // 业务逻辑在实体内部!
        order.cancel(
            CancelReason.of(command.getReason()),
            new TimeoutCancelPolicy()
        );
        
        orderRepository.save(order);
        eventPublisher.publishAll(order.getDomainEvents());
    }
}

五、 测试驱动:用测试表达业务规则

领域模型的单元测试

class OrderTest {
    
    @Test
    void should_create_order_with_items_and_calculate_amount() {
        // Given
        CustomerInfo customer = CustomerInfo.of("user123", "张三");
        Address address = Address.of("上海市", "浦东新区", "张江路123号");
        List<OrderItem> items = Arrays.asList(
            OrderItem.create(null, ProductId.of("p1"), "iPhone13", Money.of(new BigDecimal("5999")), 1),
            OrderItem.create(null, ProductId.of("p2"), "AirPods", Money.of(new BigDecimal("1299")), 2)
        );
        
        // When
        Order order = Order.create(customer, address, items, new DefaultOrderPolicy());
        
        // Then
        assertThat(order).isNotNull();
        assertThat(order.getOrderNo()).isNotNull();
        assertThat(order.getAmount()).isEqualTo(Money.of(new BigDecimal("8597"))); // 5999 + 1299*2
        assertThat(order.getStatus()).isEqualTo(OrderStatus.CREATED);
        assertThat(order.getItems()).hasSize(2);
    }
    
    @Test
    void should_throw_exception_when_create_order_without_items() {
        // When & Then
        assertThatThrownBy(() -> 
            Order.create(
                CustomerInfo.of("user123", "张三"),
                Address.of("上海", "浦东", "张江路"),
                Collections.emptyList(),
                new DefaultOrderPolicy()
            )
        ).isInstanceOf(DomainException.class)
         .hasMessage("订单必须包含商品");
    }
    
    @Test
    void should_pay_order_successfully() {
        // Given
        Order order = createTestOrder();
        Payment payment = new Payment("pay123", order.getAmount(), PayMethod.ALIPAY);
        
        // When
        order.pay(payment, new DefaultPaymentValidator());
        
        // Then
        assertThat(order.getStatus()).isEqualTo(OrderStatus.PAID);
        assertThat(order.getTimeline().getPaidTime()).isNotNull();
    }
    
    @Test
    void should_throw_exception_when_pay_cancelled_order() {
        // Given
        Order order = createTestOrder();
        order.cancel(CancelReason.of("用户取消"), new AlwaysAllowCancelPolicy());
        Payment payment = new Payment("pay123", order.getAmount(), PayMethod.ALIPAY);
        
        // When & Then
        assertThatThrownBy(() -> 
            order.pay(payment, new DefaultPaymentValidator())
        ).isInstanceOf(DomainException.class)
         .hasMessageContaining("不能支付");
    }
    
    @Test
    void should_auto_cancel_when_payment_timeout() {
        // Given
        Order order = createTestOrder();
        // 模拟31分钟前创建的订单
        order.getTimeline().setCreatedTime(LocalDateTime.now().minusMinutes(31));
        
        // When & Then
        assertThat(order.isPaymentTimeout()).isTrue();
    }
}

六、 架构演进:分层架构落地

清晰的分层架构

订单域分层架构:
┌─────────────────────────────────────────────────────────┐
│                   接口层 (Interface Layer)                │
│  - Controller:接收请求、返回响应                         │
│  - DTO转换:Command/Query/Response                       │
└──────────────────────────┬──────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────┐
│                   应用层 (Application Layer)             │
│  - ApplicationService:事务边界、权限校验                 │
│  - 事件发布:发布领域事件                                 │
└──────────────────────────┬──────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────┐
│                   领域层 (Domain Layer)                  │
│  - 实体、值对象、领域服务:核心业务逻辑                    │
│  - 领域事件:业务事件定义                                 │
└──────────────────────────┬──────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────┐
│                   基础设施层 (Infrastructure Layer)       │
│  - Repository实现:数据持久化                            │
│  - 消息队列、缓存、外部服务调用                           │
└─────────────────────────────────────────────────────────┘

依赖注入的实现

// 领域层定义接口(不依赖基础设施)
public interface OrderRepository {
    Optional<Order> findById(OrderId orderId);
    Order save(Order order);
    List<Order> findPaymentTimeoutOrders(LocalDateTime timeoutTime);
}

// 基础设施层实现接口
@Repository
public class OrderRepositoryImpl implements OrderRepository {
    
    private final JpaOrderRepository jpaRepository;
    private final OrderMapper mapper;
    
    @Override
    public Optional<Order> findById(OrderId orderId) {
        return jpaRepository.findById(orderId.getValue())
            .map(mapper::toDomain);
    }
    
    @Override
    public Order save(Order order) {
        OrderEntity entity = mapper.toEntity(order);
        OrderEntity saved = jpaRepository.save(entity);
        return mapper.toDomain(saved);
    }
}

七、 实战经验:DDD落地的坑与解决方案

坑1:聚合设计过大

// 错误:把所有关联实体都放进一个聚合
public class OrderAggregate {
    private Order order;
    private List<OrderItem> items;
    private List<Payment> payments; // 错误:支付应该是独立聚合
    private List<Delivery> deliveries; // 错误:发货应该是独立聚合
}

// 正确:只放真正属于一个事务边界的实体
public class OrderAggregate {
    private Order order;
    private List<OrderItem> items; // 订单项生命周期与订单一致
    private List<OrderLog> logs; // 订单日志生命周期与订单一致
}

坑2:领域服务滥用

// 错误:把本应在实体中的逻辑放到领域服务
@Service
public class OrderService {
    public void payOrder(Long orderId, Payment payment) {
        Order order = repository.findById(orderId);
        // 错误:业务逻辑在Service中
        if (order.getStatus() != OrderStatus.CREATED) {
            throw new Exception("不能支付");
        }
        order.setStatus(OrderStatus.PAID); // 错误:直接set状态
    }
}

// 正确:业务逻辑在实体中
@Entity
public class Order {
    public void pay(Payment payment) {
        // 正确:业务逻辑在实体内部
        if (!status.canPay()) {
            throw new DomainException("不能支付");
        }
        this.status = OrderStatus.PAID; // 正确:通过业务方法改变状态
    }
}

坑3:贫血模型复发

// 错误:又变回了getter/setter
@Data
@Entity
public class Order {
    private OrderStatus status;
    
    // 错误:暴露了setter
    public void setStatus(OrderStatus status) {
        this.status = status;
    }
}

// 正确:只有业务方法能改变状态
@Entity
public class Order {
    private OrderStatus status;
    
    // 正确:没有setter
    public void pay() {
        // 业务规则检查
        if (!status.canPay()) {
            throw new DomainException("不能支付");
        }
        // 状态转换
        this.status = OrderStatus.PAID;
    }
}

八、 DDD带来的实际价值

价值1:代码即文档

// 看代码就能理解业务规则
public class Order {
    // 业务规则:创建后30分钟未支付自动取消
    public boolean isPaymentTimeout() {
        if (status != OrderStatus.CREATED) return false;
        return LocalDateTime.now().isAfter(
            timeline.getCreatedTime().plusMinutes(30)
        );
    }
    
    // 业务规则:只有待支付和已支付订单可以取消
    public boolean canCancel() {
        return status == OrderStatus.CREATED || status == OrderStatus.PAID;
    }
}

价值2:业务逻辑内聚

// 所有订单状态转换逻辑都在一起
public enum OrderStatus {
    CREATED {
        @Override public boolean canPay() { return true; }
        @Override public boolean canCancel() { return true; }
        @Override public OrderStatus afterPay() { return PAID; }
    },
    PAID {
        @Override public boolean canPay() { return false; }
        @Override public boolean canCancel() { return true; }
        @Override public boolean canDeliver() { return true; }
    },
    CANCELLED {
        @Override public boolean canPay() { return false; }
        @Override public boolean canCancel() { return false; }
    };
    
    public abstract boolean canPay();
    public abstract boolean canCancel();
    public boolean canDeliver() { return false; }
    public OrderStatus afterPay() { throw new IllegalStateException("不能支付"); }
}

价值3:应对业务变化

当业务规则从"30分钟未支付自动取消"变为"VIP用户60分钟,普通用户30分钟":

// 只需要修改策略类,实体代码不变
public class VipCancelPolicy implements CancelPolicy {
    @Override
    public CancelResult apply(Order order, CancelReason reason) {
        if (order.isVipCustomer()) {
            // VIP用户60分钟
            LocalDateTime timeout = order.getCreatedTime().plusMinutes(60);
            boolean isTimeout = LocalDateTime.now().isAfter(timeout);
            return isTimeout ? 
                CancelResult.reject("VIP用户有60分钟支付时间") :
                CancelResult.allow();
        } else {
            // 普通用户30分钟
            LocalDateTime timeout = order.getCreatedTime().plusMinutes(30);
            boolean isTimeout = LocalDateTime.now().isAfter(timeout);
            return isTimeout ? 
                CancelResult.reject("订单已超时") :
                CancelResult.allow();
        }
    }
}

九、 从CRUD到DDD的迁移策略

策略1:渐进式重构

  1. 第一步:识别核心域(订单)
  2. 第二步:设计领域模型(纸上设计)
  3. 第三步:实现值对象(无风险)
  4. 第四步:改造核心实体(从创建订单开始)
  5. 第五步:逐步迁移业务逻辑

策略2:新老系统并存

// 老系统接口保持不变
@Deprecated
public class LegacyOrderService {
    // 老代码
}

// 新系统实现相同接口
@Primary
public class DDDOrderService implements OrderService {
    // DDD实现
}

// 配置开关
@Configuration
public class OrderConfig {
    
    @Bean
    @ConditionalOnProperty(name = "order.ddd.enabled", havingValue = "true")
    public OrderService dddOrderService() {
        return new DDDOrderService();
    }
    
    @Bean
    @ConditionalOnProperty(name = "order.ddd.enabled", havingValue = "false", matchIfMissing = true)
    public OrderService legacyOrderService() {
        return new LegacyOrderService();
    }
}

十、 总结:DDD不是银弹,是思维转变

经过一年的DDD实践,我最大的收获不是技术,而是思维的转变

  1. 从数据建模到业务建模:不再先设计数据库表,而是先理解业务
  2. 从过程式编程到声明式编程:不再写"怎么做",而是定义"是什么"
  3. 从技术驱动到业务驱动:技术为业务服务,不是相反

DDD的核心价值

  • 代码能清晰地表达业务意图
  • 业务逻辑高度内聚,不再分散
  • 面对业务变化时,修改点明确且可控

开始DDD的建议

  1. 从小处开始:选一个核心域
  2. 与业务专家合作:领域模型是共同语言
  3. 注重实践,不纠结理论:能解决实际问题的就是好DDD

记住:DDD不是关于框架和技术的,而是关于如何用代码更好地表达业务。当你的代码能让新同事一眼看懂业务规则时,你就成功了。

版权声明 ▶ 本网站名称:黄磊的博客
▶ 本文标题:DDD(领域驱动设计)实践初探:如何用代码表达业务逻辑?
▶ 本文链接:https://www.huangleicole.com/backend-related/70.html
▶ 转载本站文章需要遵守:商业转载请联系站长,非商业转载请注明出处!!

如果觉得我的文章对你有用,请随意赞赏