一、领域事件概述
领域事件(Domain Event)是DDD中非常重要的概念:
核心思想:
- 领域中发生的业务事实
- 不可变的事件记录
- 触发后续业务处理
- 实现松耦合
解决的问题:
- 跨聚合根的业务协同
- 微服务间的数据同步
- 业务可追溯性
- 最终一致性
二、领域事件基础
1. 事件结构
┌─────────────────────────────────────────────────────────────────┐
│ 领域事件结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 领域事件 │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ eventId: UUID - 事件唯一标识 │ │ │
│ │ │ eventType: String - 事件类型 │ │ │
│ │ │ aggregateId: String - 聚合根ID │ │ │
│ │ │ aggregateType: String - 聚合根类型 │ │ │
│ │ │ occurredOn: LocalDateTime - 发生时间 │ │ │
│ │ │ eventData: Object - 事件数据 │ │ │
│ │ │ metadata: Map - 元数据 │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2. 事件定义
java
// 基础领域事件
public abstract class DomainEvent {
private final String eventId;
private final LocalDateTime occurredOn;
private final Map<String, Object> metadata;
protected DomainEvent() {
this.eventId = UUID.randomUUID().toString();
this.occurredOn = LocalDateTime.now();
this.metadata = new HashMap<>();
}
public String getEventId() {
return eventId;
}
public LocalDateTime getOccurredOn() {
return occurredOn;
}
public Map<String, Object> getMetadata() {
return metadata;
}
}
// 订单相关事件
public class OrderCreatedEvent extends DomainEvent {
private final OrderId orderId;
private final CustomerId customerId;
private final Money totalAmount;
private final List<OrderItemData> items;
public OrderCreatedEvent(Order order) {
super();
this.orderId = order.getId();
this.customerId = order.getCustomerId();
this.totalAmount = order.getTotalAmount();
this.items = order.getItems().stream()
.map(OrderItemData::from)
.collect(Collectors.toList());
}
public OrderId getOrderId() {
return orderId;
}
}
public class OrderPaidEvent extends DomainEvent {
private final OrderId orderId;
private final String paymentId;
private final Money paidAmount;
public OrderPaidEvent(OrderId orderId, String paymentId, Money paidAmount) {
super();
this.orderId = orderId;
this.paymentId = paymentId;
this.paidAmount = paidAmount;
}
}
public class OrderShippedEvent extends DomainEvent {
private final OrderId orderId;
private final String trackingNumber;
private final String carrier;
public OrderShippedEvent(OrderId orderId, String trackingNumber, String carrier) {
super();
this.orderId = orderId;
this.trackingNumber = trackingNumber;
this.carrier = carrier;
}
}
3. 聚合根发布事件
java
// 聚合根发布事件
public abstract class AggregateRoot {
private final List<DomainEvent> domainEvents = new ArrayList<>();
protected void registerEvent(DomainEvent event) {
domainEvents.add(event);
}
public List<DomainEvent> pullDomainEvents() {
List<DomainEvent> events = new ArrayList<>(domainEvents);
domainEvents.clear();
return events;
}
public List<DomainEvent> getDomainEvents() {
return Collections.unmodifiableList(domainEvents);
}
}
// 订单聚合根
public class Order extends AggregateRoot {
private OrderId id;
private CustomerId customerId;
private OrderStatus status;
private List<OrderItem> items;
private Money totalAmount;
public static Order create(OrderId id, CustomerId customerId) {
Order order = new Order();
order.id = id;
order.customerId = customerId;
order.status = OrderStatus.DRAFT;
order.items = new ArrayList<>();
order.totalAmount = Money.ZERO;
// 注册创建事件
order.registerEvent(new OrderCreatedEvent(order));
return order;
}
public void addItem(Product product, int quantity) {
if (status != OrderStatus.DRAFT) {
throw new BusinessException("只有草稿状态的订单可以添加商品");
}
items.add(OrderItem.create(product, quantity));
recalculateTotal();
registerEvent(new OrderItemAddedEvent(id, product.getId(), quantity));
}
public void submit() {
if (items.isEmpty()) {
throw new BusinessException("订单不能为空");
}
this.status = OrderStatus.SUBMITTED;
registerEvent(new OrderSubmittedEvent(id));
}
public void pay(String paymentId, Money paidAmount) {
if (status != OrderStatus.SUBMITTED) {
throw new BusinessException("只有已提交的订单可以支付");
}
this.status = OrderStatus.PAID;
this.paymentId = paymentId;
registerEvent(new OrderPaidEvent(id, paymentId, paidAmount));
}
public void ship(String trackingNumber, String carrier) {
if (status != OrderStatus.PAID) {
throw new BusinessException("只有已支付的订单可以发货");
}
this.status = OrderStatus.SHIPPED;
this.trackingNumber = trackingNumber;
registerEvent(new OrderShippedEvent(id, trackingNumber, carrier));
}
}
三、事件发布与订阅
1. 事件发布
java
// 事件发布服务
@Service
public class DomainEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Autowired
private KafkaTemplate<String, Object> kafkaTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
// 本地发布(Spring事件)
public void publishLocal(DomainEvent event) {
applicationEventPublisher.publishEvent(event);
}
// 分布式发布(Kafka)
public void publishKafka(DomainEvent event, String topic) {
kafkaTemplate.send(topic, event.getEventId(), event)
.whenComplete((result, ex) -> {
if (ex != null) {
log.error("事件发布失败: eventId={}", event.getEventId(), ex);
} else {
log.info("事件发布成功: eventId={}, topic={}",
event.getEventId(), topic);
}
});
}
// 消息队列发布
public void publishRabbitMQ(DomainEvent event, String exchange, String routingKey) {
rabbitTemplate.convertAndSend(exchange, routingKey, event);
}
}
// 应用服务处理聚合根生命周期
@Service
public class OrderApplicationService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private DomainEventPublisher eventPublisher;
@Transactional
public Order createOrder(CreateOrderCommand command) {
// 创建订单聚合根
Order order = Order.create(
OrderId.generate(),
CustomerId.of(command.getCustomerId())
);
// 添加商品
for (OrderItemData itemData : command.getItems()) {
Product product = productRepository.findById(itemData.getProductId());
order.addItem(product, itemData.getQuantity());
}
// 提交订单
order.submit();
// 保存
orderRepository.save(order);
// 发布聚合根中的事件
for (DomainEvent event : order.pullDomainEvents()) {
eventPublisher.publishKafka(event, "order-events");
}
return order;
}
}
2. 事件订阅处理
java
// 本地事件监听
@Component
public class OrderEventListener {
@Autowired
private NotificationService notificationService;
@Autowired
private InventoryService inventoryService;
// 监听订单创建事件
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
log.info("收到订单创建事件: orderId={}", event.getOrderId());
// 发送通知
notificationService.sendOrderCreatedNotification(event.getCustomerId());
}
// 监听订单提交事件
@EventListener
public void handleOrderSubmitted(OrderSubmittedEvent event) {
log.info("收到订单提交事件: orderId={}", event.getOrderId());
}
}
// Kafka事件订阅
@Component
public class OrderKafkaConsumer {
@KafkaListener(topics = "order-events", groupId = "inventory-service")
public void handleOrderEvent(ConsumerRecord<String, DomainEvent> record) {
DomainEvent event = record.value();
if (event instanceof OrderCreatedEvent) {
handleOrderCreated((OrderCreatedEvent) event);
} else if (event instanceof OrderPaidEvent) {
handleOrderPaid((OrderPaidEvent) event);
} else if (event instanceof OrderShippedEvent) {
handleOrderShipped((OrderShippedEvent) event);
}
}
private void handleOrderCreated(OrderCreatedEvent event) {
// 预占库存
for (OrderItemData item : event.getItems()) {
inventoryService.reserveStock(item.getProductId(), item.getQuantity());
}
}
}
四、业务编排
1. 编排器模式
java
// Saga编排器
@Service
public class OrderSagaOrchestrator {
@Autowired
private KafkaTemplate kafkaTemplate;
@Autowired
private OrderSagaStateRepository sagaStateRepository;
// 启动Saga
public String startCreateOrderSaga(CreateOrderCommand command) {
String sagaId = UUID.randomUUID().toString();
// 1. 创建Saga状态
OrderSagaState state = OrderSagaState.builder()
.sagaId(sagaId)
.status(SagaStatus.STARTED)
.currentStep(0)
.command(command)
.createdAt(LocalDateTime.now())
.build();
sagaStateRepository.save(state);
// 2. 发送创建订单命令
kafkaTemplate.send("order-commands", sagaId,
new CreateOrderCommand(sagaId, command));
return sagaId;
}
// 处理Saga步骤结果
public void handleStepResult(SagaStepResult result) {
String sagaId = result.getSagaId();
OrderSagaState state = sagaStateRepository.findById(sagaId);
if (result.isSuccess()) {
// 执行下一步
executeNextStep(state);
} else {
// 补偿
compensate(state, result.getFailedStep());
}
}
private void executeNextStep(OrderSagaState state) {
int nextStep = state.getCurrentStep() + 1;
if (nextStep > 3) {
// Saga完成
state.setStatus(SagaStatus.COMPLETED);
sagaStateRepository.save(state);
return;
}
state.setCurrentStep(nextStep);
state.setStatus(SagaStatus.PROCESSING);
sagaStateRepository.save(state);
// 根据步骤发送不同命令
switch (nextStep) {
case 1 -> kafkaTemplate.send("inventory-commands", state.getSagaId(),
new ReserveStockCommand(state.getSagaId(), state.getCommand().getItems()));
case 2 -> kafkaTemplate.send("payment-commands", state.getSagaId(),
new ProcessPaymentCommand(state.getSagaId(), state.getCommand().getTotalAmount()));
case 3 -> kafkaTemplate.send("logistics-commands", state.getSagaId(),
new CreateDeliveryCommand(state.getSagaId()));
}
}
private void compensate(OrderSagaState state, int failedStep) {
state.setStatus(SagaStatus.COMPENSATING);
sagaStateRepository.save(state);
// 逆向补偿
for (int i = failedStep - 1; i >= 0; i--) {
switch (i) {
case 0 -> kafkaTemplate.send("order-compensate", state.getSagaId(),
new CancelOrderCommand(state.getSagaId()));
case 1 -> kafkaTemplate.send("payment-compensate", state.getSagaId(),
new RefundCommand(state.getSagaId()));
case 2 -> kafkaTemplate.send("inventory-compensate", state.getSagaId(),
new ReleaseStockCommand(state.getSagaId()));
}
}
}
}
2. 流程管理器
java
// 流程管理器
@Service
public class OrderProcessManager {
@Autowired
private KafkaTemplate kafkaTemplate;
@Autowired
private OrderProcessStateRepository stateRepository;
// 启动订单流程
public String startProcess(Order order) {
String processId = UUID.randomUUID().toString();
OrderProcessState state = OrderProcessState.builder()
.processId(processId)
.orderId(order.getId())
.currentPhase(OrderPhase.INVENTORY_CHECK)
.status(ProcessStatus.RUNNING)
.phases(new ArrayList<>())
.build();
// 添加库存检查阶段
state.getPhases().add(PhaseState.builder()
.phase(OrderPhase.INVENTORY_CHECK)
.status(PhaseStatus.PENDING)
.build());
stateRepository.save(state);
// 发送库存检查命令
kafkaTemplate.send("order-process", processId,
new InventoryCheckCommand(processId, order.getItems()));
return processId;
}
// 处理阶段结果
public void handlePhaseResult(PhaseResult result) {
OrderProcessState state = stateRepository.findByProcessId(result.getProcessId());
// 更新当前阶段状态
PhaseState currentPhase = state.getCurrentPhase();
currentPhase.setStatus(result.isSuccess() ? PhaseStatus.COMPLETED : PhaseStatus.FAILED);
if (!result.isSuccess()) {
// 流程失败
state.setStatus(ProcessStatus.FAILED);
state.setErrorMessage(result.getErrorMessage());
stateRepository.save(state);
return;
}
// 执行下一阶段
OrderPhase nextPhase = getNextPhase(state.getCurrentPhase().getPhase());
if (nextPhase == null) {
// 流程完成
state.setStatus(ProcessStatus.COMPLETED);
state.setCompletedAt(LocalDateTime.now());
} else {
// 进入下一阶段
state.setCurrentPhase(PhaseState.builder()
.phase(nextPhase)
.status(PhaseStatus.RUNNING)
.startedAt(LocalDateTime.now())
.build());
sendPhaseCommand(state, nextPhase);
}
stateRepository.save(state);
}
private void sendPhaseCommand(OrderProcessState state, OrderPhase phase) {
switch (phase) {
case INVENTORY_CHECK -> kafkaTemplate.send("order-process", state.getProcessId(),
new InventoryCheckCommand(state.getProcessId(), state.getItems()));
case PAYMENT -> kafkaTemplate.send("order-process", state.getProcessId(),
new PaymentCommand(state.getProcessId(), state.getTotalAmount()));
case DELIVERY -> kafkaTemplate.send("order-process", state.getProcessId(),
new DeliveryCommand(state.getProcessId()));
}
}
}
五、事件驱动架构
1. CQRS + Event Sourcing
┌─────────────────────────────────────────────────────────────────┐
│ CQRS + Event Sourcing │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 命令端: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Order Aggregate │ │
│ │ - 处理命令 │ │
│ │ - 发布事件 │ │
│ │ - 存储事件 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 事件存储 │ │
│ │ OrderCreatedEvent │ │
│ │ OrderItemAddedEvent │ │
│ │ OrderSubmittedEvent │ │
│ │ OrderPaidEvent │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 查询端: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 投影处理器 │ │
│ │ - OrderProjection (订单只读模型) │ │
│ │ - OrderListProjection (订单列表) │ │
│ │ - OrderDetailProjection (订单详情) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2. 投影实现
java
// 订单投影
@Service
public class OrderProjection {
@Autowired
private JdbcTemplate jdbcTemplate;
// 处理订单创建事件
public void onOrderCreated(OrderCreatedEvent event) {
String sql = """
INSERT INTO orders (id, customer_id, status, total_amount, created_at)
VALUES (?, ?, ?, ?, ?)
""";
jdbcTemplate.update(sql,
event.getOrderId().getValue(),
event.getCustomerId().getValue(),
OrderStatus.PENDING.name(),
event.getTotalAmount().getAmount(),
event.getOccurredOn()
);
}
// 处理订单项添加事件
public void onOrderItemAdded(OrderItemAddedEvent event) {
String updateSql = """
UPDATE orders SET total_amount = total_amount + ?
WHERE id = ?
""";
jdbcTemplate.update(updateSql,
event.getItemPrice().multiply(event.getQuantity()).getAmount(),
event.getOrderId().getValue()
);
String itemSql = """
INSERT INTO order_items (id, order_id, product_id, quantity, price)
VALUES (?, ?, ?, ?, ?)
""";
jdbcTemplate.update(itemSql,
UUID.randomUUID().toString(),
event.getOrderId().getValue(),
event.getProductId().getValue(),
event.getQuantity(),
event.getItemPrice().getAmount()
);
}
// 处理订单提交事件
public void onOrderSubmitted(OrderSubmittedEvent event) {
String sql = """
UPDATE orders SET status = ?, submitted_at = ?
WHERE id = ?
""";
jdbcTemplate.update(sql,
OrderStatus.SUBMITTED.name(),
event.getOccurredOn(),
event.getOrderId().getValue()
);
}
// 处理订单支付事件
public void onOrderPaid(OrderPaidEvent event) {
String sql = """
UPDATE orders SET status = ?, payment_id = ?, paid_at = ?
WHERE id = ?
""";
jdbcTemplate.update(sql,
OrderStatus.PAID.name(),
event.getPaymentId(),
event.getOccurredOn(),
event.getOrderId().getValue()
);
}
}
六、事件版本管理
1. 事件升级
java
// 事件升级器
@Component
public class EventUpgrader {
private final Map<String, BiFunction<Map<String, Object>, Integer, Map<String, Object>>> upgraders =
new HashMap<>();
@PostConstruct
public void init() {
// v1 -> v2
upgraders.put("OrderCreatedEvent:1", (data, version) -> {
// 添加新字段
data.put("version", 2);
data.put("channel", "UNKNOWN"); // 默认值
return data;
});
// v2 -> v3
upgraders.put("OrderCreatedEvent:2", (data, version) -> {
data.put("version", 3);
data.put("source", "DEFAULT"); // 新增字段
return data;
});
}
public DomainEvent upgrade(DomainEvent event, int currentVersion) {
int targetVersion = getCurrentVersion(event.getClass());
if (currentVersion >= targetVersion) {
return event;
}
Map<String, Object> data = extractEventData(event);
for (int v = currentVersion; v < targetVersion; v++) {
String key = event.getClass().getSimpleName() + ":" + v;
BiFunction upgrader = upgraders.get(key);
if (upgrader != null) {
data = (Map<String, Object>) upgrader.apply(data, v);
}
}
return recreateEvent(event, data);
}
private int getCurrentVersion(Class<? extends DomainEvent> eventClass) {
// 从事件类获取当前版本
return 3;
}
}
七、事务处理
1. 事务性发件箱
java
// 事务性发件箱
@Entity
@Table(name = "outbox_events")
public class OutboxEvent {
@Id
private String eventId;
private String aggregateType;
private String aggregateId;
private String eventType;
private String eventData;
private int version;
private LocalDateTime createdAt;
private LocalDateTime publishedAt;
private int retryCount;
private String lastError;
}
// 发件箱处理器
@Component
public class OutboxProcessor {
@Autowired
private OutboxEventRepository outboxRepository;
@Autowired
private KafkaTemplate kafkaTemplate;
@Scheduled(fixedDelay = 100)
public void processOutbox() {
List<OutboxEvent> events = outboxRepository
.findUnpublished(100); // 一次最多处理100条
for (OutboxEvent event : events) {
try {
// 发布到Kafka
kafkaTemplate.send(
getTopic(event.getEventType()),
event.getAggregateId(),
JSON.parse(event.getEventData())
);
// 标记已发布
event.setPublishedAt(LocalDateTime.now());
outboxRepository.save(event);
} catch (Exception e) {
event.setRetryCount(event.getRetryCount() + 1);
event.setLastError(e.getMessage());
outboxRepository.save(event);
if (event.getRetryCount() > 10) {
// 超过重试次数,发送告警
sendAlert(event);
}
}
}
}
}
// 应用服务使用发件箱
@Service
public class OrderApplicationServiceWithOutbox {
@Autowired
private OrderRepository orderRepository;
@Autowired
private OutboxEventRepository outboxRepository;
@Transactional
public Order createOrder(CreateOrderCommand command) {
Order order = Order.create(OrderId.generate(), CustomerId.of(command.getCustomerId()));
orderRepository.save(order);
// 写入发件箱
for (DomainEvent event : order.pullDomainEvents()) {
OutboxEvent outbox = OutboxEvent.builder()
.eventId(event.getEventId())
.aggregateType("Order")
.aggregateId(order.getId().getValue())
.eventType(event.getClass().getSimpleName())
.eventData(JSON.toJSONString(event))
.createdAt(LocalDateTime.now())
.retryCount(0)
.build();
outboxRepository.save(outbox);
}
return order;
}
}
八、总结
领域事件与业务编排是现代架构的核心:
- 领域事件:捕获业务事实,实现松耦合
- 事件发布:支持本地和分布式场景
- 业务编排:Saga和流程管理器
- 事务处理:发件箱模式保证一致性
最佳实践:
- 设计有意义的事件名称
- 包含足够的事件数据
- 做好事件版本管理
- 使用发件箱保证事务性
个人观点,仅供参考