本文通过订单系统案例,对比分析领域驱动设计(DDD)与传统三层架构的差异,帮助你在实际项目中做出正确的架构选择。
1. 项目背景与架构决策
1.1 业务需求分析
我们需要构建一个订单处理系统,核心需求包括:
- 多渠道订单创建和状态管理
- 实时库存验证和预留
- 动态价格计算
- 订单全生命周期追踪
- 与外部系统(库存、客户、定价)集成
1.2 架构选择:二选一的关键决策
在系统设计时,我们需要在两种架构模式中做出选择:
架构模式 | 核心思想 | 适用场景 | 技术特点 |
---|---|---|---|
数据驱动架构 | 以数据模型为中心,贫血模型 | 简单CRUD、报表系统、快速交付 | 实体层+服务层,业务逻辑在Service |
领域驱动架构 | 以业务模型为中心,充血模型 | 复杂业务规则、长期演进系统 | 领域层为核心,业务逻辑在Domain |
我们的选择 :基于订单系统的业务复杂性,选择领域驱动架构。
2. 系统架构设计
2.1 整体架构图
基础设施层 领域层 - 核心业务逻辑 应用层 表现层 数据库仓储 外部服务适配器 消息队列 订单聚合 客户实体 产品实体 领域服务 领域事件 订单应用服务 查询应用服务 Web前端 移动APP API网关
3. 领域驱动设计实现
3.1 领域模型设计
3.1.1 领域类图
1 many 1 1 1 1 Order -OrderId id -CustomerId customerId -OrderStatus status -List<OrderItem> items -Money totalAmount +create(customerId, items) : Order +addItem(product, quantity) : void +removeItem(itemId) : void +approve() : void +cancel() : void +calculateTotal() : Money -validateState() : void OrderItem -OrderItemId id -ProductId productId -Quantity quantity -Money unitPrice +create(productId, quantity, unitPrice) : OrderItem +updateQuantity(quantity) : void +calculateSubtotal() : Money -validateState() : void Customer -CustomerId id -String name -CustomerStatus status +changeName(name) : void +activate() : void +deactivate() : void +isActive() : boolean Product -ProductId id -String name -Money price -StockQuantity stock +changePrice(newPrice) : void +reduceStock(quantity) : void +increaseStock(quantity) : void +isAvailable(quantity) : boolean
3.1.2 领域事件设计
<<interface>> DomainEvent +getEventId() : String +getOccurredOn() : Timestamp +getAggregateId() : String OrderCreatedEvent -OrderId orderId -CustomerId customerId -Money totalAmount +getOrderId() : OrderId +getCustomerId() : CustomerId +getTotalAmount() : Money OrderApprovedEvent -OrderId orderId -Timestamp approvedAt InventoryReservedEvent -OrderId orderId -List<ProductId> reservedProducts
3.2 核心领域实现
java
/**
* 订单聚合根 - 领域驱动设计核心
* 职责:封装订单相关业务逻辑和状态管理
*/
public class Order {
private final OrderId id;
private final CustomerId customerId;
private OrderStatus status;
private final List<OrderItem> items;
private Money totalAmount;
private final Timestamp createdAt;
private Timestamp updatedAt;
// 私有构造函数,强制使用工厂方法
private Order(OrderId id, CustomerId customerId, List<OrderItem> items) {
this.id = Objects.requireNonNull(id, "订单ID不能为空");
this.customerId = Objects.requireNonNull(customerId, "客户ID不能为空");
this.items = new ArrayList<>(Objects.requireNonNull(items, "订单项不能为空"));
this.status = OrderStatus.CREATED;
this.createdAt = Timestamp.now();
this.updatedAt = this.createdAt;
this.totalAmount = calculateTotal();
validateState();
}
/**
* 工厂方法 - 创建新订单
* 输入校验:参数基础验证
*/
public static Order create(CustomerId customerId, List<OrderItem> items) {
if (items == null || items.isEmpty()) {
throw new DomainException("订单必须包含至少一个订单项");
}
return new Order(OrderId.generate(), customerId, items);
}
/**
* 添加订单项 - 核心业务逻辑
* 职责:订单项管理,状态验证,金额重算
*/
public void addItem(Product product, Quantity quantity) {
// 业务规则验证
if (!status.canModify()) {
throw new OrderModificationException("当前订单状态不允许修改");
}
if (!product.isAvailable(quantity)) {
throw new InsufficientStockException(product.getId(), quantity);
}
// 创建订单项
OrderItem newItem = OrderItem.create(product.getId(), quantity, product.getPrice());
items.add(newItem);
// 更新状态
recalculateTotal();
updatedAt = Timestamp.now();
// 发布领域事件
DomainEventPublisher.publish(new OrderItemAddedEvent(id, newItem));
}
/**
* 批准订单 - 状态转换业务逻辑
*/
public void approve() {
// 前置条件验证
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("只能从创建状态批准订单");
}
if (items.isEmpty()) {
throw new EmptyOrderException("空订单不能被批准");
}
// 状态转换
this.status = OrderStatus.APPROVED;
this.updatedAt = Timestamp.now();
// 发布领域事件
DomainEventPublisher.publish(new OrderApprovedEvent(id, totalAmount));
}
/**
* 取消订单 - 业务规则验证
*/
public void cancel() {
if (!status.canCancel()) {
throw new OrderCancellationException("当前订单状态不允许取消");
}
this.status = OrderStatus.CANCELLED;
this.updatedAt = Timestamp.now();
DomainEventPublisher.publish(new OrderCancelledEvent(id));
}
/**
* 业务计算逻辑 - 封装在领域层
*/
private void recalculateTotal() {
this.totalAmount = items.stream()
.map(OrderItem::calculateSubtotal)
.reduce(Money.ZERO, Money::add);
}
/**
* 聚合内部一致性验证 - 领域层职责
*/
private void validateState() {
if (items.isEmpty()) {
throw new DomainException("订单必须包含至少一个订单项");
}
if (totalAmount.isNegative()) {
throw new DomainException("订单总金额不能为负数");
}
// 验证所有订单项
items.forEach(OrderItem::validateState);
}
/**
* 业务查询方法
*/
public boolean containsProduct(ProductId productId) {
return items.stream()
.anyMatch(item -> item.getProductId().equals(productId));
}
public boolean canBeModified() {
return status.canModify();
}
// 值对象getter,保护内部状态
public OrderId getId() { return id; }
public CustomerId getCustomerId() { return customerId; }
public OrderStatus getStatus() { return status; }
public Money getTotalAmount() { return totalAmount; }
public List<OrderItem> getItems() {
return Collections.unmodifiableList(items);
}
}
/**
* 领域层异常体系
*/
public class DomainException extends RuntimeException {
public DomainException(String message) {
super(message);
}
}
public class OrderModificationException extends DomainException {
public OrderModificationException(String message) {
super(message);
}
}
public class InsufficientStockException extends DomainException {
public InsufficientStockException(ProductId productId, Quantity quantity) {
super(String.format("产品%s库存不足,请求数量: %d", productId, quantity.getValue()));
}
}
3.3 应用层协调
java
/**
* 订单应用服务 - 协调领域对象和外部依赖
* 职责:用例编排、事务管理、异常转换
*/
@Service
@Transactional
public class OrderApplicationService {
private final OrderRepository orderRepository;
private final CustomerRepository customerRepository;
private final ProductRepository productRepository;
private final InventoryService inventoryService;
private final DomainEventPublisher eventPublisher;
/**
* 创建订单用例
* 应用层职责:输入验证、流程协调、异常处理
*/
public OrderResult createOrder(CreateOrderCommand command) {
try {
// 1. 命令参数验证
validateCommand(command);
// 2. 加载依赖领域对象
Customer customer = customerRepository.findById(command.getCustomerId())
.orElseThrow(() -> new CustomerNotFoundException(command.getCustomerId()));
// 3. 验证业务规则
if (!customer.isActive()) {
return OrderResult.rejected("客户状态不活跃");
}
// 4. 创建订单项并验证库存
List<OrderItem> orderItems = createOrderItemsWithValidation(command.getItems());
// 5. 创建领域对象(核心业务逻辑)
Order order = Order.create(command.getCustomerId(), orderItems);
// 6. 预留库存(外部系统调用)
inventoryService.reserveInventory(order.getId(), command.getItems());
// 7. 持久化领域对象
orderRepository.save(order);
return OrderResult.success(order.getId());
} catch (DomainException e) {
// 领域异常直接抛出
throw e;
} catch (Exception e) {
// 技术异常转换为应用层异常
throw new ApplicationException("创建订单失败", e);
}
}
/**
* 批准订单用例
*/
public void approveOrder(OrderId orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
try {
// 调用领域对象业务方法
order.approve();
orderRepository.save(order);
} catch (IllegalStateException e) {
// 转换领域异常为业务异常
throw new BusinessException("订单批准失败: " + e.getMessage(), e);
}
}
/**
* 应用层输入验证
*/
private void validateCommand(CreateOrderCommand command) {
if (command == null) {
throw new IllegalArgumentException("创建订单命令不能为空");
}
if (command.getCustomerId() == null) {
throw new IllegalArgumentException("客户ID不能为空");
}
if (command.getItems() == null || command.getItems().isEmpty()) {
throw new IllegalArgumentException("订单项不能为空");
}
command.getItems().forEach(item -> {
if (item.getProductId() == null) {
throw new IllegalArgumentException("产品ID不能为空");
}
if (item.getQuantity() == null || item.getQuantity().getValue() <= 0) {
throw new IllegalArgumentException("订单项数量必须大于0");
}
});
}
private List<OrderItem> createOrderItemsWithValidation(List<OrderItemRequest> requests) {
return requests.stream()
.map(request -> {
Product product = productRepository.findById(request.getProductId())
.orElseThrow(() -> new ProductNotFoundException(request.getProductId()));
if (!product.isAvailable(request.getQuantity())) {
throw new InsufficientStockException(product.getId(), request.getQuantity());
}
return OrderItem.create(
product.getId(),
request.getQuantity(),
product.getPrice()
);
})
.collect(Collectors.toList());
}
}
3.4 基础设施层实现
java
/**
* 订单仓储实现 - 基础设施层
* 职责:领域对象持久化,技术细节封装
*/
@Repository
public class JpaOrderRepository implements OrderRepository {
private final OrderJpaRepository jpaRepository;
private final OrderMapper orderMapper;
@Override
public Optional<Order> findById(OrderId orderId) {
return jpaRepository.findById(orderId.getValue())
.map(orderMapper::toDomain);
}
@Override
public Order save(Order order) {
// 发布领域事件
order.getDomainEvents().forEach(event -> {
// 事件持久化或发送到消息队列
eventPublisher.publish(event);
});
order.clearDomainEvents();
// 持久化领域对象
OrderEntity entity = orderMapper.toEntity(order);
OrderEntity saved = jpaRepository.save(entity);
return orderMapper.toDomain(saved);
}
@Override
public void delete(Order order) {
jpaRepository.deleteById(order.getId().getValue());
}
}
/**
* 领域事件发布器
*/
@Component
public class SpringDomainEventPublisher implements DomainEventPublisher {
private final ApplicationEventPublisher eventPublisher;
@Override
public void publish(DomainEvent event) {
eventPublisher.publishEvent(event);
}
}
3.5 接口层实现
java
/**
* 订单REST控制器 - 接口层
* 职责:HTTP协议处理、输入校验、响应封装
*/
@RestController
@RequestMapping("/api/v1/orders")
@Validated
public class OrderController {
private final OrderApplicationService orderService;
@PostMapping
public ResponseEntity<ApiResponse<OrderResponse>> createOrder(
@Valid @RequestBody CreateOrderRequest request) {
try {
// 转换为应用层命令
CreateOrderCommand command = toCommand(request);
// 调用应用服务
OrderResult result = orderService.createOrder(command);
// 构建响应
return ResponseEntity.ok(ApiResponse.success(toResponse(result)));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(ApiResponse.error(ErrorCode.INVALID_PARAMETER, e.getMessage()));
} catch (BusinessException e) {
return ResponseEntity.badRequest()
.body(ApiResponse.error(ErrorCode.BUSINESS_ERROR, e.getMessage()));
} catch (Exception e) {
log.error("创建订单系统异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error(ErrorCode.SYSTEM_ERROR, "系统繁忙"));
}
}
private CreateOrderCommand toCommand(CreateOrderRequest request) {
List<OrderItemRequest> itemRequests = request.getItems().stream()
.map(this::toItemRequest)
.collect(Collectors.toList());
return new CreateOrderCommand(
CustomerId.of(request.getCustomerId()),
itemRequests
);
}
}
4. 对比:如果选择数据驱动架构
4.1 数据驱动实现(对比参考)
java
/**
* 贫血实体 - 数据驱动架构
* 只有数据,没有行为
*/
@Entity
@Table(name = "orders")
public class OrderEntity {
@Id
private Long id;
private String orderNumber;
private String status;
private Long customerId;
private BigDecimal totalAmount;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItemEntity> items;
// 只有getter/setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
// 没有业务方法,业务逻辑在Service中
}
/**
* 服务层 - 包含所有业务逻辑
*/
@Service
public class OrderService {
public void createOrder(CreateOrderDto dto) {
// 参数验证
validateCreateOrder(dto);
// 业务逻辑都在Service中
CustomerEntity customer = customerRepository.findById(dto.getCustomerId());
if (!"ACTIVE".equals(customer.getStatus())) {
throw new BusinessException("客户不活跃");
}
// 创建实体对象
OrderEntity order = new OrderEntity();
order.setId(generateId());
order.setStatus("CREATED");
order.setCustomerId(dto.getCustomerId());
// 计算金额
BigDecimal total = BigDecimal.ZERO;
for (OrderItemDto itemDto : dto.getItems()) {
ProductEntity product = productRepository.findById(itemDto.getProductId());
if (product.getStock() < itemDto.getQuantity()) {
throw new BusinessException("库存不足");
}
OrderItemEntity item = new OrderItemEntity();
item.setProductId(itemDto.getProductId());
item.setQuantity(itemDto.getQuantity());
item.setUnitPrice(product.getPrice());
BigDecimal subtotal = product.getPrice().multiply(
BigDecimal.valueOf(itemDto.getQuantity()));
total = total.add(subtotal);
order.getItems().add(item);
}
order.setTotalAmount(total);
orderRepository.save(order);
}
public void approveOrder(Long orderId) {
OrderEntity order = orderRepository.findById(orderId);
// 业务规则验证在Service中
if (!"CREATED".equals(order.getStatus())) {
throw new BusinessException("只能批准创建状态的订单");
}
if (order.getItems().isEmpty()) {
throw new BusinessException("空订单不能批准");
}
order.setStatus("APPROVED");
orderRepository.save(order);
}
}
5. 架构选择指南
5.1 何时选择领域驱动架构
选择领域驱动架构当:
- 业务逻辑复杂,有大量业务规则
- 系统需要长期演进和维护
- 团队熟悉DDD概念和方法
- 需要清晰的业务边界和上下文划分
- 业务变更频繁,需要快速响应
优势:
- 业务逻辑集中,易于理解和维护
- 更好的可测试性
- 业务意图明确,代码即文档
- 适应业务变化能力强
5.2 何时选择数据驱动架构
选择数据驱动架构当:
- 主要是CRUD操作,业务逻辑简单
- 需要快速交付和上线
- 团队规模小,技术能力有限
- 系统以报表和查询为主
- 业务稳定,变更较少
优势:
- 开发速度快
- 学习成本低
- 适合简单场景
- 技术栈成熟
5.3 决策矩阵
考虑因素 | 领域驱动架构 | 数据驱动架构 |
---|---|---|
业务复杂度 | 高 | 低 |
开发速度 | 中 | 高 |
维护成本 | 低 | 中 |
团队技能 | 需要培训 | 普遍掌握 |
系统寿命 | 长期 | 短期 |
测试便利性 | 高 | 中 |
6. 总结
在订单系统这个案例中,我们选择了领域驱动架构,因为:
- 业务复杂性:订单处理涉及复杂的状态转换和业务规则
- 长期演进:电商系统需要持续迭代和功能扩展
- 团队能力:具备实施DDD的技术能力
- 维护性:清晰的业务边界降低维护成本
核心收获:
- 领域驱动架构将业务逻辑封装在领域层,使代码更贴近业务语言
- 数据驱动架构适合简单场景,开发效率高
- 架构选择应该基于业务需求、团队能力和系统目标
- 没有最好的架构,只有最适合的架构
通过这个完整的案例,你可以根据自己项目的具体情况,做出正确的架构选择决策。