Java领域驱动设计案例

wen java案例 1

本文目录导读:

Java领域驱动设计案例

  1. 背景与业务愿景
  2. 战略设计(限界上下文映射)
  3. 战术建模(订单子域)
  4. 关键设计模式
  5. 测试策略
  6. 架构演进建议

下面通过一个完整的“订单履约”业务场景,展示Java领域驱动设计(DDD)的落地过程,我们围绕订单、商品、库存、支付四个核心子域展开,重点在订单子域演示战术建模。

背景与业务愿景

场景:一个电商平台,用户下单后系统自动进行库存锁定、支付、履约,需要支撑高并发订单流程,后续可扩展至多仓发货、预售、分拆订单等复杂场景。


战略设计(限界上下文映射)

核心子域划分

限界上下文 优先级 关键职责
订单(Order Context) 核心域 订单状态机、生命周期、订单履行的聚合根
商品(Catalog Context) 支撑域 商品信息、SKU、价格
库存(Inventory Context) 支撑域 库存扣减、锁定、回滚
支付(Payment Context) 通用域 支付预授权、回调处理
履约(Fulfillment Context) 核心域 仓配调度(可扩展)

上下文映射(COLA风格)

// 上下文间通过防腐层(Anti-Corruption Layer)通信
public class OrderContext implements ContextBoundary {
    private final InventoryServiceClient inventoryClient;
    private final PaymentServiceClient paymentClient;
    private final DomainEventBus eventBus;
    public OrderContext(...) {
        // 注入各类客户端
    }
    public Order createOrder(CreateOrderCommand cmd) {
        // 1. 校验商品(通过ACL调用Catalog)
        // 2. 锁定库存(通过ACL调用Inventory)
        // 3. 创建订单聚合
        // 4. 发布OrderCreatedEvent
    }
}

战术建模(订单子域)

领域模型核心类

聚合根:Order(订单)

@Entity
@AggregateRoot
public class Order {
    @EmbeddedId
    private OrderId orderId;
    @Embedded
    private CustomerId customerId;      // 值对象
    private OrderStatus status;          // 枚举:INITIATED, CONFIRMED, PAID, FULFILLING, COMPLETED, CANCELLED
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderLine> orderLines;  // 订单项集合
    @Embedded
    private Address shippingAddress;     // 值对象
    private ZonedDateTime createdAt;
    private ZonedDateTime updatedAt;
    // 领域事件列表(非持久化)
    @Transient
    private List<DomainEvent> domainEvents = new ArrayList<>();
    // 构造函数(业务创建)
    public static Order create(CreateOrderCommand cmd) {
        Order order = new Order();
        order.orderId = new OrderId(UUID.randomUUID());
        order.customerId = new CustomerId(cmd.customerId());
        order.status = OrderStatus.INITIATED;
        order.createdAt = ZonedDateTime.now();
        cmd.lineItems().forEach(item -> {
            order.addLine(
                new ProductId(item.productId()),
                item.quantity(),
                new Money(item.unitPrice()),
                new Sku(item.sku())
            );
        });
        return order;
    }
    // 领域行为:确认订单
    public void confirm(InventoryLockResult lockResult) {
        if (!canConfirm()) {
            throw new OrderStateException("Order cannot be confirmed in state: " + status);
        }
        this.status = OrderStatus.CONFIRMED;
        this.updatedAt = ZonedDateTime.now();
        this.addDomainEvent(new OrderConfirmedEvent(orderId, Instant.now()));
    }
    // 领域行为:支付成功
    public void markPaid(PaymentReceipt paymentReceipt) {
        if (status != OrderStatus.CONFIRMED) {
            throw new OrderStateException("Only confirmed orders can be paid");
        }
        this.status = OrderStatus.PAID;
        this.updatedAt = ZonedDateTime.now();
        this.addDomainEvent(new OrderPaidEvent(orderId, paymentReceipt.transactionId()));
    }
    // 领域行为:取消订单(带回滚逻辑)
    public void cancel(String reason) {
        if (status.isCancellable()) {
            this.status = OrderStatus.CANCELLED;
            this.cancelReason = reason;
            this.addDomainEvent(new OrderCancelledEvent(orderId, reason, this.orderLines));
        }
    }
    // 领域行为:验证库存是否充足
    private boolean canConfirm() {
        return this.status == OrderStatus.INITIATED 
               && this.orderLines.stream().allMatch(OrderLine::hasStock);
    }
    // 获取领域事件
    public List<DomainEvent> getDomainEvents() { return List.copyOf(domainEvents); }
    public void clearDomainEvents() { this.domainEvents.clear(); }
    private void addDomainEvent(DomainEvent event) {
        this.domainEvents.add(event);
    }
}

实体:OrderLine(订单行)

@Entity
public class OrderLine {
    @Id private Long id;
    private ProductId productId;
    private Money unitPrice;
    private Quantity quantity;
    private boolean stockLocked = false;
    public void lockStock() {
        this.stockLocked = true;
    }
    public Money total() {
        return unitPrice.multiply(quantity.value());
    }
}

值对象:Money(金额)

@Embeddable
public class Money {
    private BigDecimal amount;
    private Currency currency;
    public Money(BigDecimal amount, Currency currency) {
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("Money cannot be negative");
        }
        this.amount = amount;
        this.currency = currency;
    }
    protected Money() {} // JPA uses reflection
    public Money add(Money other) {
        requireSameCurrency(other);
        return new Money(amount.add(other.amount), currency);
    }
    public Money multiply(int factor) {
        return new Money(amount.multiply(BigDecimal.valueOf(factor)), currency);
    }
    private void requireSameCurrency(Money other) {
        if (!currency.equals(other.currency)) {
            throw new CurrencyMismatchException("Currencies do not match");
        }
    }
}

领域服务:InventoryLockService

@Service
public class InventoryLockService {
    private final InventoryClient inventoryClient;  // 防腐层
    @Transactional
    public InventoryLockResult lockStockForOrder(Order order) {
        // 1. 查询所有需要锁定的SKU
        Map<String, Integer> skuQuantities = order.getOrderLines().stream()
            .collect(Collectors.toMap(...));
        // 2. 调用库存服务锁定库存
        List<InventoryLockResult.Item> results = skuQuantities.entrySet().stream()
            .map(entry -> inventoryClient.lockSku(entry.getKey(), entry.getValue()))
            .toList();
        // 3. 如果任何一项锁定失败,回滚所有锁定
        if (results.stream().anyMatch(Item::failed)) {
            results.stream().map(Item::lockId).forEach(inventoryClient::unlock);
            throw new InventoryLockException("库存不足");
        }
        // 4. 标记订单行库存已锁定
        order.getOrderLines().forEach(OrderLine::lockStock);
        return new InventoryLockResult(results);
    }
}

领域事件与消息

// 领域事件基类
public interface DomainEvent {
    Instant occurredOn();
}
// 具体事件
public record OrderConfirmedEvent(OrderId orderId, Instant occurredOn) implements DomainEvent {}
public record OrderPaidEvent(OrderId orderId, String transactionId) implements DomainEvent {}
public record OrderCancelledEvent(OrderId orderId, String reason, List<OrderLine> lines) implements DomainEvent {}

应用层(用例驱动)

@Service
@UseCase
public class CreateOrderUseCase implements CommandHandler<CreateOrderCommand, OrderDTO> {
    private final OrderRepository orderRepository;
    private final OrderFactory orderFactory;
    private final InventoryLockService inventoryLockService;
    private final EventPublisher eventPublisher;
    @Override
    @Transactional
    public OrderDTO handle(CreateOrderCommand cmd) {
        // 1. 创建订单(聚合根)
        Order order = orderFactory.createFrom(cmd);
        // 2. 锁定库存(领域服务)
        InventoryLockResult lockResult = inventoryLockService.lockStockForOrder(order);
        // 3. 确认订单(领域行为)
        order.confirm(lockResult);
        // 4. 保存订单
        orderRepository.save(order);
        // 5. 发布领域事件(最终一致性)
        order.getDomainEvents().forEach(eventPublisher::publish);
        return OrderMapper.toDTO(order);
    }
}

基础设施层(仓储实现)

@Repository
public class JpaOrderRepository implements OrderRepository {
    @PersistenceContext private EntityManager em;
    private final EventBus eventBus;
    @Override
    @Transactional
    public void save(Order order) {
        em.persist(order);
        // 发布事件到消息队列
        order.getDomainEvents().forEach(eventBus::publish);
    }
    @Override
    public Optional<Order> findById(OrderId id) {
        Order order = em.find(Order.class, id);
        if (order != null) {
            // 加载时获取领域事件(可广播给其他订阅者)
            order.getDomainEvents().forEach(eventBus::publish);
        }
        return Optional.ofNullable(order);
    }
}

防腐层(Anti-Corruption Layer)

@Component
public class InventoryServiceClient {
    private final RestTemplate restTemplate;
    public InventoryLockResponse lockSku(String sku, int quantity) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<Map<String, Object>> request = new HttpEntity<>(
            Map.of("sku", sku, "quantity", quantity), headers
        );
        try {
            ResponseEntity<InventoryLockResponse> response = 
                restTemplate.postForEntity("http://inventory-service/api/v1/lock", request, InventoryLockResponse.class);
            return response.getBody();
        } catch (HttpClientErrorException e) {
            if (e.getStatusCode() == HttpStatus.CONFLICT) {
                return InventoryLockResponse.failed();
            }
            throw new RemoteServiceException("Inventory service error", e);
        }
    }
}

关键设计模式

状态机模式(Order Status)

public enum OrderStatus {
    INITIATED {
        @Override
        public boolean canTransitionTo(OrderStatus target) {
            return target == CONFIRMED || target == CANCELLED;
        }
    },
    CONFIRMED {
        @Override
        public boolean canTransitionTo(OrderStatus target) {
            return target == PAID || target == CANCELLED;
        }
    },
    PAID {
        @Override
        public boolean canTransitionTo(OrderStatus target) {
            return target == FULFILLING || target == REFUND_PENDING;
        }
    },
    // ... 其他状态
    ;
    public abstract boolean canTransitionTo(OrderStatus target);
}

领域事件追踪

// 使用Spring @TransactionalEventListener 保证事件边界
@Component
public class OrderEventListener {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleOrderConfirmed(OrderConfirmedEvent event) {
        // 发送通知
        notificationService.notifyOrderConfirmed(event.orderId());
    }
}

测试策略

领域模型单元测试

class OrderTest {
    @Test
    void shouldConfirmOrderWhenStockAvailable() {
        Order order = Order.create(orderCommand);
        order.confirm(lockResult.success());
        assertEquals(OrderStatus.CONFIRMED, order.getStatus());
        assertEquals(1, order.getDomainEvents().size());
        assertTrue(order.getDomainEvents().get(0) instanceof OrderConfirmedEvent);
    }
    @Test
    void shouldRejectConfirmWhenNoStock() {
        Order order = Order.create(orderCommand);
        assertThrows(InventoryLockException.class, 
            () -> order.confirm(lockResult.failure()));
    }
}

应用用例测试(带Mock)

@SpringBootTest
@Transactional
class CreateOrderUseCaseTest {
    @MockBean InventoryServiceClient inventoryClient;
    @Autowired CreateOrderUseCase createOrderUseCase;
    @Test
    void shouldCreateOrderSuccessfully() {
        // 模拟库存锁定成功
        when(inventoryClient.lockSku(anyString(), anyInt()))
            .thenReturn(InventoryLockResponse.success());
        CreateOrderCommand command = new CreateOrderCommand(
            "customer123", 
            List.of(new LineItem("sku1", 2, "10.00"))
        );
        OrderDTO dto = createOrderUseCase.handle(command);
        assertNotNull(dto.orderId());
        assertEquals("CONFIRMED", dto.status());
        // Verify event published
        verify(eventPublisher, times(1)).publish(any(OrderConfirmedEvent.class));
    }
}

架构演进建议

阶段 关注点 具体措施
初期 快速验证 单模块+简单分层,核心领域逻辑放domain包内
中期 拆分上下文 订单、库存拆分独立模块,使用OpenFeign/HTTP通信
后期 性能优化 引入CQRS(读模型使用Elasticsearch)、引入Event Sourcing存储
扩展 多仓支持 履约上下文单独拆分为微服务,实现分仓路由

这个案例展示了: ✅ 战略建模:合理划分限界上下文
核心模型:聚合根、实体、值对象、领域服务
战术落地:状态机、领域事件、防腐层
测试策略:聚焦业务规则的单元测试与集成测试
演进路径:支持逐步微服务化

它将DDD与Spring Boot、JPA、EventBus等Java生态结合,适合直接作为生产项目的基础模板。

上一篇Event Sourcing案例

下一篇CQRS案例

抱歉,评论功能暂时关闭!