【架构实战】领域事件与业务编排架构

一、领域事件概述

领域事件(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和流程管理器
  • 事务处理:发件箱模式保证一致性

最佳实践:

  1. 设计有意义的事件名称
  2. 包含足够的事件数据
  3. 做好事件版本管理
  4. 使用发件箱保证事务性

个人观点,仅供参考

相关推荐
月落归舟5 小时前
带你了解Collections和Collection!!!
java·collections·collection
直奔標竿5 小时前
Java开发者AI转型第二十课!Spring AI MCP 双向实战:客户端与服务端手把手落地
java·开发语言·人工智能·spring boot·后端·spring
天码-行空5 小时前
深入拆解 Tomcat 架构:高层组件与启动流程设计
java·架构·tomcat
ting94520005 小时前
微软 VibeVoice 万字深度解析:从原理、架构、部署到行业落地,重新定义长音频 AI
人工智能·架构·音视频
天码-行空5 小时前
深入拆解 Tomcat 架构:一键启停与生命周期设计
java·架构·tomcat
c++之路5 小时前
C++ 高频易错点
java·jvm·c++
qcx235 小时前
Warp源码深度解析(一):GPU加速+AI Agent的下一代终端架构全景
人工智能·架构·rust
java1234_小锋5 小时前
Spring AI 2.0 开发Java Agent智能体 - 新建 HelloWorld 项目
java·人工智能·spring·spring ai