Skip to content
清晨的一缕阳光
返回

领域驱动设计实战

领域驱动设计实战

领域驱动设计(DDD)是应对复杂业务的有效方法论。通过统一语言、限界上下文、聚合根等概念,可以将业务逻辑与技术实现分离,构建可维护、可扩展的系统。本文详解 DDD 落地实践,包括战略设计、战术设计、分层架构等核心内容。

一、DDD 核心概念

1.1 战略设计

mindmap
  root((DDD 战略设计))
    通用语言 Ubiquitous Language
      业务和技术人员<br/>使用统一术语
    限界上下文 Bounded Context
      明确模型的<br/>适用范围和边界
    子域划分
      核心域<br/>核心竞争力重点投入
      支撑域<br/>支持核心域自主开发
      通用域<br/>通用功能可外购
    上下文映射
      合作关系
      上下游关系
      共享内核

1.2 战术设计

mindmap
  root((DDD 战术设计))
    实体 Entity
      有唯一标识<br/>有生命周期
    值对象 Value Object
      无唯一标识<br/>不可变
    聚合根 Aggregate Root
      实体集合入口<br/>保证一致性
    领域服务 Domain Service
      不属于单个实体<br/>的业务逻辑
    领域事件 Domain Event
      领域内发生的<br/>重要事件
    仓储 Repository
      持久化抽象<br/>隔离基础设施

二、分层架构

2.1 标准分层

graph TB
    UI[用户接口层<br/>Controller/DTO/视图] --> App[应用层<br/>应用服务/事务控制/权限校验]
    App --> Domain[领域层<br/>实体/值对象/聚合根<br/>领域服务/领域事件/仓储接口]
    Domain --> Infra[基础设施层<br/>数据库访问/消息队列<br/>外部服务/仓储实现]

2.2 项目结构

src/main/java/com/example/
├── interfaces/                    # 用户接口层
│   ├── controller/
│   │   ├── OrderController.java
│   │   └── UserController.java
│   ├── dto/
│   │   ├── OrderDTO.java
│   │   └── UserDTO.java
│   └── vo/
│       └── ResponseVO.java

├── application/                   # 应用层
│   ├── service/
│   │   ├── OrderAppService.java
│   │   └── UserAppService.java
│   ├── command/
│   │   ├── CreateOrderCommand.java
│   │   └── UpdateUserCommand.java
│   └── event/
│       └── OrderCreatedEvent.java

├── domain/                        # 领域层
│   ├── model/
│   │   ├── aggregate/
│   │   │   ├── OrderAggregate.java
│   │   │   └── UserAggregate.java
│   │   ├── entity/
│   │   │   ├── Order.java
│   │   │   ├── OrderItem.java
│   │   │   └── User.java
│   │   ├── valueobject/
│   │   │   ├── Money.java
│   │   │   ├── Address.java
│   │   │   └── OrderStatus.java
│   │   └── event/
│   │       ├── OrderCreatedEvent.java
│   │       └── OrderPaidEvent.java
│   ├── service/
│   │   ├── OrderDomainService.java
│   │   └── PricingService.java
│   └── repository/
│       ├── OrderRepository.java
│       └── UserRepository.java

└── infrastructure/                # 基础设施层
    ├── persistence/
    │   ├── mapper/
    │   │   ├── OrderMapper.java
    │   │   └── UserMapper.java
    │   ├── entity/
    │   │   ├── OrderPO.java
    │   │   └── UserPO.java
    │   └── repository/
    │       ├── OrderRepositoryImpl.java
    │       └── UserRepositoryImpl.java
    ├── messaging/
    │   └── MessagePublisher.java
    └── config/
        └── DomainConfig.java

三、领域建模

3.1 实体设计

/**
 * 订单实体(聚合根)
 */
@Entity
@Table(name = "t_order")
public class Order {
    
    @Id
    private String id;
    
    @Embedded
    private OrderNo orderNo;
    
    private Long userId;
    
    @Embedded
    private Money totalAmount;
    
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
    
    private LocalDateTime createTime;
    
    // 关联子实体
    @OneToMany(mappedBy = "orderId", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>();
    
    // 行为方法(充血模型)
    public void pay() {
        if (this.status != OrderStatus.UNPAID) {
            throw new DomainException("只有未支付订单才能支付");
        }
        this.status = OrderStatus.PAID;
        // 发布领域事件
        DomainEventPublisher.publish(new OrderPaidEvent(this.id));
    }
    
    public void cancel() {
        if (this.status != OrderStatus.UNPAID) {
            throw new DomainException("只有未支付订单才能取消");
        }
        this.status = OrderStatus.CANCELLED;
    }
    
    public void addItem(Product product, int quantity) {
        OrderItem item = new OrderItem(this.id, product, quantity);
        this.items.add(item);
        this.recalculateTotal();
    }
    
    private void recalculateTotal() {
        Money total = Money.ZERO;
        for (OrderItem item : items) {
            total = total.add(item.getSubtotal());
        }
        this.totalAmount = total;
    }
    
    // Getter 方法
    public String getId() { return id; }
    public OrderNo getOrderNo() { return orderNo; }
    public Long getUserId() { return userId; }
    public Money getTotalAmount() { return totalAmount; }
    public OrderStatus getStatus() { return status; }
    public List<OrderItem> getItems() { return Collections.unmodifiableList(items); }
}

/**
 * 订单明细(子实体)
 */
@Entity
@Table(name = "t_order_item")
public class OrderItem {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String orderId;
    
    private Long productId;
    
    private String productName;
    
    @Embedded
    private Money price;
    
    private Integer quantity;
    
    @Embedded
    private Money subtotal;
    
    // 构造方法
    public OrderItem(String orderId, Product product, int quantity) {
        this.orderId = orderId;
        this.productId = product.getId();
        this.productName = product.getName();
        this.price = product.getPrice();
        this.quantity = quantity;
        this.subtotal = product.getPrice().multiply(quantity);
    }
    
    // Getter 方法
}

3.2 值对象设计

/**
 * 金额值对象
 */
@Embeddable
public class Money implements Serializable {
    
    public static final Money ZERO = new Money(BigDecimal.ZERO);
    
    @Column(name = "amount", precision = 10, scale = 2)
    private BigDecimal amount;
    
    @Column(name = "currency", length = 3)
    private String currency;
    
    // 默认构造方法(JPA 要求)
    protected Money() {}
    
    public Money(BigDecimal amount) {
        this(amount, "CNY");
    }
    
    public Money(BigDecimal amount, String currency) {
        if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("金额不能为负数");
        }
        this.amount = amount.setScale(2, RoundingMode.HALF_UP);
        this.currency = currency;
    }
    
    // 值对象方法
    public Money add(Money other) {
        checkCurrency(other);
        return new Money(this.amount.add(other.amount), this.currency);
    }
    
    public Money subtract(Money other) {
        checkCurrency(other);
        return new Money(this.amount.subtract(other.amount), this.currency);
    }
    
    public Money multiply(int multiplier) {
        return new Money(this.amount.multiply(new BigDecimal(multiplier)), this.currency);
    }
    
    private void checkCurrency(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("货币类型不一致");
        }
    }
    
    // 值对象比较(基于属性,非 ID)
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Money money = (Money) o;
        return Objects.equals(amount, money.amount) &&
               Objects.equals(currency, money.currency);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(amount, currency);
    }
    
    // Getter 方法
    public BigDecimal getAmount() { return amount; }
    public String getCurrency() { return currency; }
}

/**
 * 地址值对象
 */
@Embeddable
public class Address implements Serializable {
    
    @Column(name = "province", length = 50)
    private String province;
    
    @Column(name = "city", length = 50)
    private String city;
    
    @Column(name = "district", length = 50)
    private String district;
    
    @Column(name = "street", length = 200)
    private String street;
    
    @Column(name = "zip_code", length = 20)
    private String zipCode;
    
    protected Address() {}
    
    public Address(String province, String city, String district, String street, String zipCode) {
        this.province = province;
        this.city = city;
        this.district = district;
        this.street = street;
        this.zipCode = zipCode;
    }
    
    // 值对象不可变,无 setter
    public String getProvince() { return province; }
    public String getCity() { return city; }
    public String getDistrict() { return district; }
    public String getStreet() { return street; }
    public String getZipCode() { return zipCode; }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return Objects.equals(province, address.province) &&
               Objects.equals(city, address.city) &&
               Objects.equals(district, address.district) &&
               Objects.equals(street, address.street) &&
               Objects.equals(zipCode, address.zipCode);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(province, city, district, street, zipCode);
    }
}

3.3 领域服务

/**
 * 订单领域服务
 * 处理跨聚合的业务逻辑
 */
@Service
public class OrderDomainService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private InventoryService inventoryService;
    
    /**
     * 创建订单
     */
    @Transactional
    public Order createOrder(Long userId, List<OrderItemCommand> items) {
        // 1. 验证用户
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new DomainException("用户不存在"));
        
        // 2. 验证商品库存
        for (OrderItemCommand item : items) {
            Product product = productRepository.findById(item.getProductId())
                .orElseThrow(() -> new DomainException("商品不存在"));
            
            if (!inventoryService.checkStock(product.getId(), item.getQuantity())) {
                throw new DomainException("库存不足:" + product.getName());
            }
        }
        
        // 3. 创建订单聚合根
        Order order = new Order();
        order.setId(IdGenerator.generate());
        order.setOrderNo(OrderNo.generate());
        order.setUserId(userId);
        order.setStatus(OrderStatus.UNPAID);
        order.setCreateTime(LocalDateTime.now());
        
        // 4. 添加订单项
        for (OrderItemCommand item : items) {
            Product product = productRepository.findById(item.getProductId()).get();
            order.addItem(product, item.getQuantity());
            
            // 5. 扣减库存
            inventoryService.deductStock(product.getId(), item.getQuantity());
        }
        
        // 6. 保存订单
        orderRepository.save(order);
        
        // 7. 发布领域事件
        DomainEventPublisher.publish(new OrderCreatedEvent(order.getId()));
        
        return order;
    }
    
    /**
     * 订单支付
     */
    @Transactional
    public void payOrder(String orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new DomainException("订单不存在"));
        
        // 调用聚合根行为方法
        order.pay();
        
        // 保存订单状态变更
        orderRepository.save(order);
    }
    
    /**
     * 取消订单
     */
    @Transactional
    public void cancelOrder(String orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new DomainException("订单不存在"));
        
        // 取消订单
        order.cancel();
        
        // 恢复库存
        for (OrderItem item : order.getItems()) {
            inventoryService.restoreStock(item.getProductId(), item.getQuantity());
        }
        
        orderRepository.save(order);
    }
}

四、仓储实现

4.1 仓储接口

/**
 * 订单仓储接口(定义在领域层)
 */
public interface OrderRepository {
    
    Order findById(String id);
    
    Order findByOrderNo(OrderNo orderNo);
    
    List<Order> findByUserId(Long userId);
    
    List<Order> findByStatus(OrderStatus status);
    
    void save(Order order);
    
    void delete(String id);
    
    Page<Order> findAll(Pageable pageable);
}

4.2 仓储实现

/**
 * 订单仓储实现(基础设施层)
 */
@Repository
public class OrderRepositoryImpl implements OrderRepository {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private OrderItemMapper orderItemMapper;
    
    @Override
    public Order findById(String id) {
        // 查询订单主表
        OrderPO orderPO = orderMapper.selectById(id);
        if (orderPO == null) {
            return null;
        }
        
        // 查询订单项
        List<OrderItemPO> itemPOs = orderItemMapper.selectByOrderId(id);
        
        // DO 转 Domain
        return convertToDomain(orderPO, itemPOs);
    }
    
    @Override
    public void save(Order order) {
        // Domain 转 DO
        OrderPO orderPO = convertToPO(order);
        
        if (orderPO.getId() == null) {
            // 新增
            orderMapper.insert(orderPO);
            
            // 插入订单项
            for (OrderItem item : order.getItems()) {
                OrderItemPO itemPO = convertItemToPO(item);
                orderItemMapper.insert(itemPO);
            }
        } else {
            // 更新
            orderMapper.update(orderPO);
            
            // 删除旧的订单项
            orderItemMapper.deleteByOrderId(orderPO.getId());
            
            // 插入新的订单项
            for (OrderItem item : order.getItems()) {
                OrderItemPO itemPO = convertItemToPO(item);
                orderItemMapper.insert(itemPO);
            }
        }
    }
    
    private Order convertToDomain(OrderPO po, List<OrderItemPO> itemPOs) {
        Order order = new Order();
        order.setId(po.getId());
        order.setOrderNo(new OrderNo(po.getOrderNo()));
        order.setUserId(po.getUserId());
        order.setTotalAmount(new Money(po.getTotalAmount()));
        order.setStatus(OrderStatus.valueOf(po.getStatus()));
        order.setCreateTime(po.getCreateTime());
        
        for (OrderItemPO itemPO : itemPOs) {
            OrderItem item = new OrderItem(
                po.getId(),
                itemPO.getProductId(),
                itemPO.getProductName(),
                new Money(itemPO.getPrice()),
                itemPO.getQuantity(),
                new Money(itemPO.getSubtotal())
            );
            order.getItems().add(item);
        }
        
        return order;
    }
    
    private OrderPO convertToPO(Order order) {
        OrderPO po = new OrderPO();
        po.setId(order.getId());
        po.setOrderNo(order.getOrderNo().getValue());
        po.setUserId(order.getUserId());
        po.setTotalAmount(order.getTotalAmount().getAmount());
        po.setStatus(order.getStatus().name());
        po.setCreateTime(order.getCreateTime());
        return po;
    }
    
    private OrderItemPO convertItemToPO(OrderItem item) {
        OrderItemPO po = new OrderItemPO();
        po.setOrderId(item.getOrderId());
        po.setProductId(item.getProductId());
        po.setProductName(item.getProductName());
        po.setPrice(item.getPrice().getAmount());
        po.setQuantity(item.getQuantity());
        po.setSubtotal(item.getSubtotal().getAmount());
        return po;
    }
}

五、应用服务

5.1 应用服务设计

/**
 * 订单应用服务
 */
@Service
@Validated
public class OrderAppService {
    
    @Autowired
    private OrderDomainService orderDomainService;
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private EventPublisher eventPublisher;
    
    /**
     * 创建订单
     */
    @Transactional
    public OrderDTO createOrder(CreateOrderCommand command) {
        // 参数校验
        ValidationUtils.validate(command);
        
        // 调用领域服务
        Order order = orderDomainService.createOrder(
            command.getUserId(),
            command.getItems()
        );
        
        // Domain 转 DTO
        return convertToDTO(order);
    }
    
    /**
     * 查询订单详情
     */
    @Transactional(readOnly = true)
    public OrderDTO getOrderDetail(String orderId) {
        Order order = orderRepository.findById(orderId);
        if (order == null) {
            throw new NotFoundException("订单不存在");
        }
        return convertToDTO(order);
    }
    
    /**
     * 支付订单
     */
    @Transactional
    public void payOrder(String orderId) {
        orderDomainService.payOrder(orderId);
    }
    
    /**
     * 取消订单
     */
    @Transactional
    public void cancelOrder(String orderId) {
        orderDomainService.cancelOrder(orderId);
    }
    
    private OrderDTO convertToDTO(Order order) {
        OrderDTO dto = new OrderDTO();
        dto.setId(order.getId());
        dto.setOrderNo(order.getOrderNo().getValue());
        dto.setUserId(order.getUserId());
        dto.setTotalAmount(order.getTotalAmount().getAmount());
        dto.setStatus(order.getStatus().name());
        dto.setCreateTime(order.getCreateTime());
        
        List<OrderItemDTO> itemDTOs = order.getItems().stream()
            .map(this::convertItemToDTO)
            .collect(Collectors.toList());
        dto.setItems(itemDTOs);
        
        return dto;
    }
    
    private OrderItemDTO convertItemToDTO(OrderItem item) {
        OrderItemDTO dto = new OrderItemDTO();
        dto.setProductId(item.getProductId());
        dto.setProductName(item.getProductName());
        dto.setPrice(item.getPrice().getAmount());
        dto.setQuantity(item.getQuantity());
        dto.setSubtotal(item.getSubtotal().getAmount());
        return dto;
    }
}

六、领域事件

6.1 事件定义

/**
 * 订单创建事件
 */
public class OrderCreatedEvent implements DomainEvent {
    
    private final String orderId;
    private final Long userId;
    private final LocalDateTime occurredOn;
    
    public OrderCreatedEvent(String orderId) {
        this.orderId = orderId;
        this.userId = extractUserId(orderId);
        this.occurredOn = LocalDateTime.now();
    }
    
    @Override
    public LocalDateTime occurredOn() {
        return occurredOn;
    }
    
    // Getter 方法
    public String getOrderId() { return orderId; }
    public Long getUserId() { return userId; }
}

/**
 * 订单支付事件
 */
public class OrderPaidEvent implements DomainEvent {
    
    private final String orderId;
    private final Money amount;
    private final LocalDateTime occurredOn;
    
    public OrderPaidEvent(String orderId, Money amount) {
        this.orderId = orderId;
        this.amount = amount;
        this.occurredOn = LocalDateTime.now();
    }
    
    @Override
    public LocalDateTime occurredOn() {
        return occurredOn;
    }
    
    // Getter 方法
    public String getOrderId() { return orderId; }
    public Money getAmount() { return amount; }
}

6.2 事件发布与订阅

/**
 * 领域事件发布器
 */
@Component
public class DomainEventPublisher {
    
    @Autowired
    private ApplicationEventPublisher publisher;
    
    public static void publish(DomainEvent event) {
        // Spring 事件发布
        applicationContext.publishEvent(event);
    }
}

/**
 * 订单创建事件处理器
 */
@Component
public class OrderCreatedEventHandler {
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private NotificationService notificationService;
    
    @Autowired
    private MessageQueueService mqService;
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handle(OrderCreatedEvent event) {
        log.info("处理订单创建事件:orderId={}", event.getOrderId());
        
        // 1. 扣减库存(本地事务已处理,这里是补偿)
        // inventoryService.confirmDeduct(event.getOrderId());
        
        // 2. 发送通知
        notificationService.sendOrderCreated(event.getOrderId());
        
        // 3. 发送到消息队列(供其他系统消费)
        mqService.send("order_created", event);
    }
}

/**
 * 订单支付事件处理器
 */
@Component
public class OrderPaidEventHandler {
    
    @Autowired
    private ShippingService shippingService;
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handle(OrderPaidEvent event) {
        log.info("处理订单支付事件:orderId={}", event.getOrderId());
        
        // 1. 创建发货单
        shippingService.createShippingOrder(event.getOrderId());
        
        // 2. 发送支付成功通知
        notificationService.sendPaymentSuccess(event.getOrderId());
    }
}

七、总结

7.1 核心要点

  1. 战略设计:限界上下文、子域划分、通用语言
  2. 战术设计:实体、值对象、聚合根、领域服务
  3. 分层架构:接口层、应用层、领域层、基础设施层
  4. 充血模型:业务逻辑放在实体中
  5. 仓储模式:隔离持久化细节
  6. 领域事件:解耦业务流程

7.2 实施建议

mindmap
  root((DDD 实施建议))
    推荐做法
      从核心域开始实施
      保持聚合根小而精
      值对象不可变
      仓储只负责持久化
      领域层不依赖基础设施
    避免做法
      贫血模型
      大聚合
      领域层依赖数据库
      应用层包含业务逻辑
      过度设计

DDD 不是银弹,适合复杂业务场景。对于简单 CRUD 系统,传统分层架构可能更合适。


分享这篇文章到:

上一篇文章
CQRS 模式实战:命令查询职责分离架构设计
下一篇文章
缓存策略设计实战