DDD数据模型
DDD数据模型
CQRS模式
CQRS(Command Query Responsibility Segregation)将读写操作分离到不同的模型中。
CQRS 架构:
写入端(Command) 读取端(Query)
┌─────────────────┐ ┌─────────────────┐
│ Command │ │ Query │
│ Handler │ │ Handler │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 领域模型 │ │ 读模型 │
│ (写优化) │ │ (读优化) │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 写数据库 │──── 同步 ────►│ 读数据库 │
│ (范式化) │ │ (反范式化) │
└─────────────────┘ └─────────────────┘
// Command 端
@CommandHandler
public class CreateOrderCommandHandler {
private final OrderRepository orderRepository;
private final EventPublisher eventPublisher;
@Transactional
public OrderId handle(CreateOrderCommand command) {
Order order = Order.create(
command.getCustomerId(),
command.getItems()
);
orderRepository.save(order);
// 发布事件同步到读端
eventPublisher.publish(new OrderCreatedEvent(order));
return order.getId();
}
}
// Query 端
@RestController
@RequestMapping("/api/orders")
public class OrderQueryController {
private final OrderQueryService queryService;
@GetMapping
public Page<OrderListItem> listOrders(
@RequestParam Long customerId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return queryService.findByCustomerId(customerId, page, size);
}
@GetMapping("/{orderId}")
public OrderDetail getOrderDetail(@PathVariable Long orderId) {
return queryService.findById(orderId);
}
}
// 读模型
@Entity
@Table(name = "order_list_view")
public class OrderListItem {
@Id
private Long id;
private String orderNo;
private String customerName;
private BigDecimal totalAmount;
private String status;
private LocalDateTime createdAt;
}
@Entity
@Table(name = "order_detail_view")
public class OrderDetail {
@Id
private Long id;
private String orderNo;
private CustomerInfo customer;
private List<OrderItemInfo> items;
private BigDecimal totalAmount;
private String status;
private PaymentInfo payment;
private ShippingInfo shipping;
}
事件存储
事件存储将领域事件持久化,支持事件溯源和审计。
// 事件存储实体
@Entity
@Table(name = "event_store")
public class EventEntity {
@Id
private Long id;
private String aggregateId;
private String aggregateType;
private String eventType;
private String eventData; // JSON 格式
private LocalDateTime occurredAt;
private Long version;
}
// 事件存储 Repository
@Repository
public class EventStoreRepository {
private final EventStoreJpaRepository repository;
private final ObjectMapper objectMapper;
public void append(DomainEvent event, String aggregateId, Long version) {
EventEntity entity = new EventEntity();
entity.setAggregateId(aggregateId);
entity.setAggregateType(aggregate.getClass().getSimpleName());
entity.setEventType(event.getClass().getSimpleName());
entity.setEventData(objectMapper.writeValueAsString(event));
entity.setOccurredAt(LocalDateTime.now());
entity.setVersion(version);
repository.save(entity);
}
public List<DomainEvent> getEvents(String aggregateId) {
return repository.findByAggregateIdOrderByVersion(aggregateId)
.stream()
.map(this::toDomainEvent)
.collect(Collectors.toList());
}
private DomainEvent toDomainEvent(EventEntity entity) {
try {
Class<?> eventType = Class.forName(
"com.example.order.domain.event." + entity.getEventType());
return (DomainEvent) objectMapper.readValue(
entity.getEventData(), eventType);
} catch (Exception e) {
throw new EventDeserializationException(e);
}
}
}
// 事件溯源聚合
public class Order {
public static Order reconstitute(List<DomainEvent> events) {
Order order = new Order();
for (DomainEvent event : events) {
order.apply(event);
}
return order;
}
private void apply(DomainEvent event) {
if (event instanceof OrderCreatedEvent) {
applyCreated((OrderCreatedEvent) event);
} else if (event instanceof OrderPaidEvent) {
applyPaid((OrderPaidEvent) event);
}
// 记录已应用的事件
changes.add(event);
}
}
读写分离的优势
- 独立扩展:读写可以独立扩展,读库可以水平扩展
- 优化查询:读模型针对查询场景优化,提升查询性能
- 数据一致性:通过事件同步保证最终一致性
- 审计支持:事件存储提供完整的操作审计日志
CQRS 适合读写比例差异大、查询复杂的场景。