领域驱动设计(DDD)实战:构建清晰边界的企业级应用
一、DDD概述
1.1 什么是DDD
领域驱动设计(Domain-Driven Design,DDD)是一种软件开发方法论,强调:
- 以业务领域为核心:将业务逻辑放在核心位置
- 通用语言:开发团队与业务专家使用统一语言
- 限界上下文:划分清晰的问题边界
- 聚合设计:将相关实体和值对象组织在一起
1.2 DDD核心概念
┌─────────────────────────────────────────────────────────────────┐ │ 限界上下文 (Bounded Context) │ ├─────────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 聚合 │ │ 聚合 │ │ 聚合 │ │ │ │ (Aggregate)│ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ 实体 │ │ 实体 │ │ │ │ (Entity) │ │ │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ 值对象 │ │ 值对象 │ │ │ │(Value Object)│ │ │ │ │ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────────┘1.3 DDD分层架构
┌────────────────────────────────────────────────────────────────┐ │ 用户界面层 (UI Layer) │ ├────────────────────────────────────────────────────────────────┤ │ 应用层 (Application Layer) │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ 应用服务 │ │ DTO │ │ │ │ (Application │ │ (Data Transfer │ │ │ │ Services) │ │ Objects) │ │ │ └──────────────────┘ └──────────────────┘ │ ├────────────────────────────────────────────────────────────────┤ │ 领域层 (Domain Layer) │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │聚合根 │ │ 实体 │ │值对象 │ │领域服务│ │仓储接口│ │ │ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ ├────────────────────────────────────────────────────────────────┤ │ 基础设施层 (Infrastructure Layer) │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ 仓储实现 │ │ 外部服务 │ │ │ │ (Repository │ │ (External │ │ │ │ Implementation) │ │ Services) │ │ │ └──────────────────┘ └──────────────────┘ │ └────────────────────────────────────────────────────────────────┘二、聚合设计
2.1 聚合根
/** * 订单聚合根 - 订单是订单系统的核心实体 */ public class Order extends AggregateRoot { private OrderId id; private CustomerId customerId; private Money totalAmount; private OrderStatus status; private List<OrderItem> items; private ShippingAddress shippingAddress; private PaymentInfo paymentInfo; // 私有构造函数,通过工厂方法创建 private Order(OrderId id, CustomerId customerId, List<OrderItem> items) { this.id = id; this.customerId = customerId; this.items = new ArrayList<>(items); this.totalAmount = calculateTotal(); this.status = OrderStatus.PENDING; this.createdAt = LocalDateTime.now(); // 添加领域事件 registerEvent(new OrderCreatedEvent(this.id, this.customerId, this.totalAmount)); } // 工厂方法 public static Order create(CustomerId customerId, List<OrderItem> items) { Objects.requireNonNull(customerId, "CustomerId cannot be null"); Objects.requireNonNull(items, "Items cannot be null"); if (items.isEmpty()) { throw new IllegalArgumentException("Order must have at least one item"); } return new Order(OrderId.generate(), customerId, items); } // 唯一通过聚合根修改状态 public void confirm() { if (this.status != OrderStatus.PENDING) { throw new OrderStateException("Order can only be confirmed from PENDING state"); } this.status = OrderStatus.CONFIRMED; registerEvent(new OrderConfirmedEvent(this.id)); } public void pay(PaymentInfo paymentInfo) { if (this.status != OrderStatus.CONFIRMED) { throw new OrderStateException("Order must be confirmed before payment"); } this.paymentInfo = paymentInfo; this.status = OrderStatus.PAID; registerEvent(new OrderPaidEvent(this.id, paymentInfo.getTransactionId())); } public void ship(String trackingNumber) { if (this.status != OrderStatus.PAID) { throw new OrderStateException("Order must be paid before shipping"); } this.status = OrderStatus.SHIPPED; registerEvent(new OrderShippedEvent(this.id, trackingNumber)); } }2.2 实体
/** * 订单项实体 */ public class OrderItem { private ProductId productId; private String productName; private Money unitPrice; private int quantity; private Money subtotal; // 实体需要唯一标识 private OrderItemId id; public OrderItem(ProductId productId, String productName, Money unitPrice, int quantity) { this.id = OrderItemId.generate(); this.productId = productId; this.productName = productName; this.unitPrice = unitPrice; this.quantity = quantity; this.subtotal = unitPrice.multiply(quantity); } // 实体相等性基于ID @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; OrderItem that = (OrderItem) o; return id.equals(that.id); } @Override public int hashCode() { return id.hashCode(); } }2.3 值对象
/** * 金额值对象 - 不可变,表示货币金额 */ public final class Money { private final BigDecimal amount; private final Currency currency; public Money(BigDecimal amount, Currency currency) { this.amount = amount.setScale(2, RoundingMode.HALF_UP); this.currency = currency; } public static Money of(BigDecimal amount) { return new Money(amount, Currency.getInstance("CNY")); } public static Money of(double amount) { return new Money(BigDecimal.valueOf(amount), Currency.getInstance("CNY")); } // 值对象操作返回新实例 public Money add(Money other) { if (!this.currency.equals(other.currency)) { throw new IllegalArgumentException("Cannot add different currencies"); } return new Money(this.amount.add(other.amount), this.currency); } public Money multiply(int factor) { return new Money(this.amount.multiply(BigDecimal.valueOf(factor)), this.currency); } // 值对象相等性基于属性值 @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.equals(money.currency); } @Override public int hashCode() { return Objects.hash(amount, currency); } } /** * 地址值对象 */ public final class ShippingAddress { private final String province; private final String city; private final String district; private final String street; private final String zipCode; public ShippingAddress(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; } public String getFullAddress() { return String.format("%s%s%s%s", province, city, district, street); } }三、仓储模式
3.1 仓储接口
/** * 仓储接口 - 属于领域层 */ public interface OrderRepository { // 根据ID查找 Optional<Order> findById(OrderId id); // 保存聚合 void save(Order order); // 批量保存 void saveAll(List<Order> orders); // 分页查询 Page<Order> findByCustomerId(CustomerId customerId, PageRequest pageRequest); // 条件查询 List<Order> findByStatus(OrderStatus status); // 统计查询 long countByStatus(OrderStatus status); // 删除 void delete(OrderId id); }3.2 仓储实现
/** * JPA仓储实现 - 属于基础设施层 */ @Repository public class JpaOrderRepository implements OrderRepository { @Autowired private OrderJpaRepository jpaRepository; @Autowired private OrderMapper orderMapper; @Override public Optional<Order> findById(OrderId id) { return jpaRepository.findById(id.getValue()) .map(orderMapper::toDomain); } @Override public void save(Order order) { OrderEntity entity = orderMapper.toEntity(order); jpaRepository.save(entity); } @Override public Page<Order> findByCustomerId(CustomerId customerId, PageRequest pageRequest) { return jpaRepository.findByCustomerId(customerId.getValue(), pageRequest) .map(orderMapper::toDomain); } } /** * MyBatis仓储实现 */ @Mapper public interface OrderMyBatisRepository extends OrderRepository { @Select("SELECT * FROM orders WHERE id = #{id}") @Results({ @Result(property = "id", column = "id", id = true), @Result(property = "customerId", column = "customer_id"), @Result(property = "totalAmount", column = "total_amount"), @Result(property = "status", column = "status") }) Optional<Order> findById(@Param("id") String id); }3.3 领域事件发布
public interface DomainEventPublisher { void publish(DomainEvent event); void publishAll(List<DomainEvent> events); } /** * Spring事件发布实现 */ @Component public class SpringDomainEventPublisher implements DomainEventPublisher { @Autowired private ApplicationEventPublisher eventPublisher; @Override public void publish(DomainEvent event) { eventPublisher.publishEvent(new DomainEventWrapper(event)); } @Override public void publishAll(List<DomainEvent> events) { events.forEach(this::publish); } }四、领域服务
4.1 领域服务定义
/** * 领域服务 - 处理跨聚合的业务逻辑 */ @DomainService public class OrderDomainService { /** * 计算订单总价,包含优惠活动 */ public Money calculateOrderTotal(List<OrderItem> items, List<Promotion> applicablePromotions) { Money subtotal = items.stream() .map(OrderItem::getSubtotal) .reduce(Money.ZERO, Money::add); Money discount = applicablePromotions.stream() .map(promo -> promo.calculateDiscount(subtotal)) .reduce(Money.ZERO, Money::add); return subtotal.subtract(discount); } /** * 验证订单是否可以取消 */ public boolean canCancel(Order order) { return order.getStatus() == OrderStatus.PENDING || order.getStatus() == OrderStatus.CONFIRMED; } /** * 执行订单取消 */ public Order cancel(Order order, CancelReason reason) { if (!canCancel(order)) { throw new IllegalStateException("Order cannot be cancelled in current state"); } order.cancel(reason); return order; } }4.2 防腐层
/** * 防腐层 - 隔离外部服务的影响 */ @Component public class PaymentAdapter implements PaymentPort { @Autowired private PaymentExternalService paymentService; @Autowired private PaymentMapper mapper; @Override public PaymentResult processPayment(PaymentRequest request) { try { ExternalPaymentRequest externalRequest = mapper.toExternal(request); ExternalPaymentResponse response = paymentService.pay(externalRequest); return mapper.toDomain(response); } catch (ExternalServiceException e) { throw new PaymentException("Payment processing failed", e); } } @Override public RefundResult processRefund(RefundRequest request) { try { ExternalRefundRequest externalRequest = mapper.toExternal(request); ExternalRefundResponse response = paymentService.refund(externalRequest); return mapper.toDomain(response); } catch (ExternalServiceException e) { throw new RefundException("Refund processing failed", e); } } }五、应用服务
5.1 应用服务设计
/** * 应用服务 - 协调领域对象和外部服务 */ @Service @Transactional public class OrderApplicationService { @Autowired private OrderRepository orderRepository; @Autowired private ProductRepository productRepository; @Autowired private PromotionService promotionService; @Autowired private PaymentPort paymentPort; @Autowired private DomainEventPublisher eventPublisher; /** * 创建订单 */ public OrderDTO createOrder(CreateOrderCommand command) { // 1. 获取商品信息 List<Product> products = productRepository.findByIds(command.getProductIds()); // 2. 构建订单项 List<OrderItem> items = buildOrderItems(products, command.getQuantities()); // 3. 计算价格 List<Promotion> promotions = promotionService.getApplicablePromotions(command.getCustomerId()); Order order = Order.create(command.getCustomerId(), items); Money finalAmount = orderDomainService.calculateOrderTotal(items, promotions); // 4. 保存订单 orderRepository.save(order); // 5. 发布领域事件 eventPublisher.publishAll(order.getDomainEvents()); return orderMapper.toDTO(order); } /** * 支付订单 */ public PaymentDTO payOrder(PayOrderCommand command) { Order order = orderRepository.findById(command.getOrderId()) .orElseThrow(() -> new OrderNotFoundException(command.getOrderId())); PaymentRequest request = PaymentRequest.builder() .orderId(order.getId()) .amount(order.getTotalAmount()) .paymentMethod(command.getPaymentMethod()) .build(); PaymentResult result = paymentPort.processPayment(request); if (result.isSuccess()) { order.pay(result.getTransactionId()); orderRepository.save(order); } return paymentMapper.toDTO(result); } }5.2 命令与查询分离
/** * 命令端点 */ @RestController @RequestMapping("/api/orders") @RequiredArgsConstructor public class OrderCommandController { private final OrderApplicationService orderService; @PostMapping public ResponseEntity<OrderDTO> createOrder(@RequestBody @Valid CreateOrderCommand command) { OrderDTO result = orderService.createOrder(command); return ResponseEntity.status(HttpStatus.CREATED).body(result); } @PostMapping("/{id}/pay") public ResponseEntity<PaymentDTO> payOrder( @PathVariable String id, @RequestBody @Valid PayOrderCommand command) { command.setOrderId(OrderId.of(id)); PaymentDTO result = orderService.payOrder(command); return ResponseEntity.ok(result); } } /** * 查询端点 */ @RestController @RequestMapping("/api/orders") @RequiredArgsConstructor public class OrderQueryController { private final OrderQueryService queryService; @GetMapping("/{id}") public ResponseEntity<OrderDTO> getOrder(@PathVariable String id) { return queryService.findById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @GetMapping public ResponseEntity<Page<OrderDTO>> listOrders( @RequestParam String customerId, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { Page<OrderDTO> result = queryService.findByCustomerId(customerId, PageRequest.of(page, size)); return ResponseEntity.ok(result); } }六、限界上下文集成
6.1 上下文映射
/** * 客户上下文的客户DTO */ public class CustomerDTO { private CustomerId id; private String name; private Email email; } /** * 订单上下文使用客户信息 */ public class Order { private CustomerId customerId; // 不直接持有Customer对象,而是引用其ID } /** * 上下文间通信 - 使用防腐层 */ @Service public class CustomerIntegrationService { @Autowired private CustomerService customerService; // 外部客户服务 public Customer getCustomer(CustomerId customerId) { CustomerDTO dto = customerService.getById(customerId.getValue()); return CustomerMapper.toDomain(dto); } public boolean isVipCustomer(CustomerId customerId) { CustomerDTO dto = customerService.getById(customerId.getValue()); return dto.isVip(); } }6.2 消息集成
/** * 发布客户注册事件 */ @RabbitListener(queues = "customer.events") public class CustomerEventListener { @Autowired private OrderRepository orderRepository; @Autowired private LoyaltyPointService loyaltyPointService; @RabbitHandler public void handleCustomerRegistered(CustomerRegisteredEvent event) { // 为新注册客户创建初始订单记录或积分 loyaltyPointService.createAccount(event.getCustomerId()); } } /** * 发布订单事件供其他上下文消费 */ @Service public class OrderEventPublisher { @Autowired private RabbitTemplate rabbitTemplate; public void publishOrderCreated(OrderCreatedEvent event) { OrderCreatedMessage message = OrderCreatedMessage.builder() .orderId(event.getOrderId().getValue()) .customerId(event.getCustomerId().getValue()) .totalAmount(event.getTotalAmount().getValue()) .build(); rabbitTemplate.convertAndSend("order.events", "order.created", message); } }七、DDD与微服务
7.1 按DDD划分微服务
┌─────────────────────────────────────────────────────────────────┐ │ 订单限界上下文 │ │ 订单聚合 | 订单项值对象 | 订单领域服务 │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 客户限界上下文 │ │ 客户聚合 | 地址值对象 | 客户领域服务 │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 产品限界上下文 │ │ 产品聚合 | 库存实体 | 价格值对象 │ └─────────────────────────────────────────────────────────────────┘7.2 微服务间通信
/** * REST同步调用 */ @Service public class ProductIntegration { @Autowired private WebClient webClient; public List<Product> getProducts(List<ProductId> ids) { return webClient.post() .uri("http://product-service/api/products/batch") .bodyValue(ids.stream().map(ProductId::getValue).collect(Collectors.toList())) .retrieve() .bodyToFlux(ProductDTO.class) .map(this::toDomain) .collect(Collectors.toList()) .block(); } } /** * 消息异步通信 */ @Service public class OrderNotificationService { @Autowired private RabbitTemplate rabbitTemplate; public void notifyOrderCreated(Order order) { OrderNotification notification = OrderNotification.builder() .orderId(order.getId().getValue()) .customerEmail(order.getCustomerId().getValue()) // 需要查询客户服务 .items(order.getItems().size()) .build(); rabbitTemplate.convertAndSend("notifications.order.created", notification); } }八、实践建议
8.1 DDD实施清单
| 阶段 | 任务 | 产出物 |
|---|---|---|
| 战略设计 | 识别限界上下文 | 上下文映射图 |
| 定义通用语言 | 领域词汇表 | |
| 确定核心域 | 优先级矩阵 | |
| 战术设计 | 设计聚合 | 聚合图 |
| 定义实体和值对象 | 领域模型 | |
| 设计仓储接口 | 仓储接口定义 | |
| 实现 | 实现领域层 | 聚合、领域服务 |
| 实现应用层 | 应用服务 | |
| 实现基础设施 | 仓储实现、集成 |
8.2 常见问题处理
| 问题 | 解决方案 |
|---|---|
| 聚合过大 | 拆分为多个小聚合 |
| 贫血模型 | 将行为移入领域对象 |
| 循环依赖 | 使用领域事件解耦 |
| 仓储滥用 | 领域服务只依赖接口 |
九、总结
领域驱动设计是构建复杂业务系统的重要方法论,通过本文的介绍,你可以:
- DDD核心概念:限界上下文、聚合、实体、值对象
- 聚合设计:聚合根、实体、值对象的实现
- 仓储模式:仓储接口定义和实现
- 领域服务:处理跨聚合的业务逻辑
- 应用服务:协调领域对象和外部服务
- 限界上下文集成:上下文映射和通信模式
- DDD与微服务:按DDD划分微服务边界
DDD不仅是一种技术方案,更是一种思维方式,需要业务专家和开发团队的紧密协作,才能真正发挥其价值。