事件驱动架构与领域事件:构建松耦合的响应式系统

深入讲解事件驱动架构的设计原则与实现模式,涵盖领域事件、事件存储、事件溯源、CQRS、事件总线等核心概念,提供Node.js和Java的完整实战代码。

事件驱动 vs 请求驱动:架构选型决策

在设计微服务通信方式时,工程师常常面临一个核心抉择:使用同步的请求/响应(REST/gRPC),还是异步的事件驱动。两种模式各有适用边界,错误选型会导致系统耦合、性能瓶颈或调试困难。

维度请求驱动(REST/gRPC)事件驱动(EDA)
耦合度调用方知道被调用方Producer 不知道 Consumer
可用性下游故障级联影响上游下游故障不影响上游
响应时间同步等待,尾延迟累积异步处理,用户感觉更快
一致性强一致性(立刻生效)最终一致性(存在窗口期)
调试难度调用栈清晰需要链路追踪
适用场景实时查询、必须成功通知、解耦、批量处理

决策树:

需要同步返回结果?
  ├─ 是 → 下游必须在本次请求中完成?
  │        ├─ 是 → 使用 gRPC / REST 同步调用
  │        └─ 否 → 异步 Job + 轮询 / Webhook 回调
  └─ 否 → 多个服务需要知道这件事?
           ├─ 是 → 事件驱动(发布-订阅)
           └─ 否 → 消息队列(点对点)

核心原则:不要为了追求"架构先进性"而把所有通信都改为事件驱动。账户余额扣减、库存扣减等强一致性场景, Saga 模式下的补偿事件比纯事件驱动更安全。

事件驱动架构概述

事件驱动架构(EDA)是一种通过事件的产生、检测和消费来实现系统组件间松耦合通信的架构风格。

事件驱动架构优势:
┌─────────────────────────────────────────┐
│ ✓ 松耦合:生产者不知道消费者是谁        │
│ ✓ 可扩展:新增消费者不影响现有系统      │
│ ✓ 审计追踪:完整的事件历史记录          │
│ ✓ 时间解耦:异步处理,提高响应速度      │
│ ✓ 弹性:失败可重试,不影响主流程        │
└─────────────────────────────────────────┘

领域事件设计

事件定义

// src/domain/events/baseEvent.ts
export interface DomainEvent {
  eventId: string;
  eventType: string;
  aggregateId: string;
  aggregateType: string;
  timestamp: Date;
  version: number;
  metadata?: Record<string, any>;
}

// src/domain/events/orderEvents.ts
export class OrderCreatedEvent implements DomainEvent {
  readonly eventId: string;
  readonly eventType = 'OrderCreated';
  readonly aggregateType = 'Order';
  readonly timestamp: Date;
  readonly version = 1;
  
  constructor(
    public readonly aggregateId: string,
    public readonly userId: string,
    public readonly items: OrderItem[],
    public readonly totalAmount: number,
    public readonly shippingAddress: Address,
    metadata?: Record<string, any>
  ) {
    this.eventId = generateUUID();
    this.timestamp = new Date();
    this.metadata = metadata;
  }
}

export class OrderConfirmedEvent implements DomainEvent {
  readonly eventId: string;
  readonly eventType = 'OrderConfirmed';
  readonly aggregateType = 'Order';
  readonly timestamp: Date;
  readonly version = 1;
  
  constructor(
    public readonly aggregateId: string,
    public readonly confirmedAt: Date,
    public readonly estimatedDelivery: Date,
    metadata?: Record<string, any>
  ) {
    this.eventId = generateUUID();
    this.timestamp = new Date();
    this.metadata = metadata;
  }
}

export class OrderCancelledEvent implements DomainEvent {
  readonly eventId: string;
  readonly eventType = 'OrderCancelled';
  readonly aggregateType = 'Order';
  readonly timestamp: Date;
  readonly version = 1;
  
  constructor(
    public readonly aggregateId: string,
    public readonly reason: string,
    public readonly cancelledBy: string,
    metadata?: Record<string, any>
  ) {
    this.eventId = generateUUID();
    this.timestamp = new Date();
    this.metadata = metadata;
  }
}

export class OrderShippedEvent implements DomainEvent {
  readonly eventId: string;
  readonly eventType = 'OrderShipped';
  readonly aggregateType = 'Order';
  readonly timestamp: Date;
  readonly version = 1;
  
  constructor(
    public readonly aggregateId: string,
    public readonly trackingNumber: string,
    public readonly carrier: string,
    public readonly shippedAt: Date,
    metadata?: Record<string, any>
  ) {
    this.eventId = generateUUID();
    this.timestamp = new Date();
    this.metadata = metadata;
  }
}

聚合根发布事件

// src/domain/entities/Order.ts
import { DomainEvent } from '../events/baseEvent';
import { OrderCreatedEvent, OrderConfirmedEvent, OrderCancelledEvent } from '../events/orderEvents';

export class Order {
  private domainEvents: DomainEvent[] = [];
  
  constructor(
    public readonly id: string,
    public userId: string,
    public items: OrderItem[],
    public totalAmount: number,
    public shippingAddress: Address,
    public status: OrderStatus = 'PENDING',
    public createdAt: Date = new Date()
  ) {}
  
  static create(
    userId: string,
    items: OrderItem[],
    shippingAddress: Address
  ): Order {
    const totalAmount = items.reduce(
      (sum, item) => sum + item.price * item.quantity,
      0
    );
    
    const order = new Order(
      generateUUID(),
      userId,
      items,
      totalAmount,
      shippingAddress
    );
    
    // 发布领域事件
    order.addDomainEvent(
      new OrderCreatedEvent(
        order.id,
        userId,
        items,
        totalAmount,
        shippingAddress,
        { correlationId: generateCorrelationId() }
      )
    );
    
    return order;
  }
  
  confirm(estimatedDelivery: Date): void {
    if (this.status !== 'PENDING') {
      throw new BusinessRuleError('Only pending orders can be confirmed');
    }
    
    this.status = 'CONFIRMED';
    
    this.addDomainEvent(
      new OrderConfirmedEvent(
        this.id,
        new Date(),
        estimatedDelivery
      )
    );
  }
  
  cancel(reason: string, cancelledBy: string): void {
    if (this.status === 'SHIPPED') {
      throw new BusinessRuleError('Cannot cancel shipped order');
    }
    
    this.status = 'CANCELLED';
    
    this.addDomainEvent(
      new OrderCancelledEvent(
        this.id,
        reason,
        cancelledBy
      )
    );
  }
  
  // 获取并清除领域事件
  pullDomainEvents(): DomainEvent[] {
    const events = [...this.domainEvents];
    this.domainEvents = [];
    return events;
  }
  
  private addDomainEvent(event: DomainEvent): void {
    this.domainEvents.push(event);
  }
}

// src/application/services/OrderApplicationService.ts
export class OrderApplicationService {
  constructor(
    private orderRepository: OrderRepository,
    private eventPublisher: EventPublisher
  ) {}
  
  async createOrder(command: CreateOrderCommand): Promise<string> {
    // 1. 创建聚合根
    const order = Order.create(
      command.userId,
      command.items,
      command.shippingAddress
    );
    
    // 2. 保存到数据库(事务)
    await this.orderRepository.save(order);
    
    // 3. 发布领域事件
    const events = order.pullDomainEvents();
    await this.eventPublisher.publishAll(events);
    
    return order.id;
  }
  
  async confirmOrder(orderId: string, estimatedDelivery: Date): Promise<void> {
    const order = await this.orderRepository.findById(orderId);
    if (!order) {
      throw new NotFoundError('Order', orderId);
    }
    
    order.confirm(estimatedDelivery);
    await this.orderRepository.save(order);
    
    const events = order.pullDomainEvents();
    await this.eventPublisher.publishAll(events);
  }
}

事件总线实现

内存事件总线

// src/infrastructure/events/InMemoryEventBus.ts
import { EventEmitter } from 'events';
import { DomainEvent } from '../../domain/events/baseEvent';

type EventHandler = (event: DomainEvent) => Promise<void>;

export class InMemoryEventBus {
  private emitter: EventEmitter;
  
  constructor() {
    this.emitter = new EventEmitter();
    this.emitter.setMaxListeners(100); // 增加监听器数量限制
  }
  
  subscribe(eventType: string, handler: EventHandler): void {
    this.emitter.on(eventType, async (event: DomainEvent) => {
      try {
        await handler(event);
      } catch (error) {
        console.error(`Error handling event ${eventType}:`, error);
        // 可以发送到死信队列
      }
    });
  }
  
  async publish(event: DomainEvent): Promise<void> {
    this.emitter.emit(event.eventType, event);
  }
  
  async publishAll(events: DomainEvent[]): Promise<void> {
    for (const event of events) {
      await this.publish(event);
    }
  }
}

// 使用示例
const eventBus = new InMemoryEventBus();

// 订阅订单创建事件
eventBus.subscribe('OrderCreated', async (event: OrderCreatedEvent) => {
  await inventoryService.reserveStock(event.aggregateId, event.items);
});

eventBus.subscribe('OrderCreated', async (event: OrderCreatedEvent) => {
  await notificationService.sendOrderConfirmation(event.userId, event.aggregateId);
});

// 订阅订单确认事件
eventBus.subscribe('OrderConfirmed', async (event: OrderConfirmedEvent) => {
  await warehouseService.prepareShipment(event.aggregateId);
});

Kafka事件总线

// src/infrastructure/events/KafkaEventBus.ts
import { Kafka, Producer, Consumer, EachMessagePayload } from 'kafkajs';
import { DomainEvent } from '../../domain/events/baseEvent';

export class KafkaEventBus {
  private producer: Producer;
  private consumers: Map<string, Consumer> = new Map();
  private topic: string;
  
  constructor(
    private brokers: string[],
    private clientId: string,
    topic: string = 'domain-events'
  ) {
    const kafka = new Kafka({
      clientId,
      brokers
    });
    
    this.producer = kafka.producer();
    this.topic = topic;
  }
  
  async connect(): Promise<void> {
    await this.producer.connect();
    console.log('Kafka producer connected');
  }
  
  async publish(event: DomainEvent): Promise<void> {
    await this.producer.send({
      topic: this.topic,
      messages: [
        {
          key: event.aggregateId,
          value: JSON.stringify(event),
          headers: {
            eventType: event.eventType,
            aggregateType: event.aggregateType,
            timestamp: event.timestamp.toISOString()
          }
        }
      ]
    });
    
    console.log(`Published event: ${event.eventType} for ${event.aggregateId}`);
  }
  
  async subscribe(
    eventType: string,
    groupId: string,
    handler: (event: DomainEvent) => Promise<void>
  ): Promise<void> {
    const kafka = new Kafka({
      clientId: this.clientId,
      brokers: this.brokers
    });
    
    const consumer = kafka.consumer({ groupId });
    await consumer.connect();
    await consumer.subscribe({ topic: this.topic, fromBeginning: false });
    
    await consumer.run({
      eachMessage: async ({ topic, partition, message }: EachMessagePayload) => {
        const eventTypeHeader = message.headers?.eventType?.toString();
        
        if (eventTypeHeader === eventType) {
          const event = JSON.parse(message.value?.toString() || '{}');
          
          try {
            await handler(event);
          } catch (error) {
            console.error(`Error handling event ${eventType}:`, error);
            // 发送到死信队列或重试
            throw error;
          }
        }
      }
    });
    
    this.consumers.set(`${eventType}-${groupId}`, consumer);
    console.log(`Subscribed to ${eventType} with group ${groupId}`);
  }
  
  async disconnect(): Promise<void> {
    await this.producer.disconnect();
    
    for (const consumer of this.consumers.values()) {
      await consumer.disconnect();
    }
    
    console.log('Kafka disconnected');
  }
}

// 使用示例
const eventBus = new KafkaEventBus(
  ['kafka1:9092', 'kafka2:9092'],
  'order-service'
);

await eventBus.connect();

// 发布事件
await eventBus.publish(orderCreatedEvent);

// 订阅事件
await eventBus.subscribe(
  'OrderCreated',
  'inventory-service-group',
  async (event) => {
    await inventoryService.reserveStock(event.aggregateId, event.items);
  }
);

Saga 模式:分布式事务的编排方案

微服务架构下,传统 ACID 事务无法跨服务边界工作。Saga 模式通过一系列本地事务 + 补偿操作,实现最终一致性的"分布式事务"。

Saga 两种实现方式

类型原理优点缺点
编排式 Saga各服务监听事件,自主决定下一步松耦合、弹性好流程分散,难以整体把握
编排器 Saga中央协调器调度各服务流程集中,可视性强协调器成为单点

订单处理 Saga(编排式)

// 订单创建流程的 Saga 事件链
// OrderCreatedEvent → InventoryReservedEvent → PaymentProcessedEvent → OrderConfirmedEvent
// 如果 Payment 失败 → PaymentFailedEvent → InventoryReleasedEvent (补偿)

eventBus.subscribe('OrderCreated', 'saga-orchestrator', async (event: OrderCreatedEvent) => {
  // 步骤 1:预留库存
  await inventoryService.reserveStock(event.aggregateId, event.items);
});

eventBus.subscribe('InventoryReserved', 'saga-orchestrator', async (event) => {
  // 步骤 2:扣款
  try {
    await paymentService.charge(event.orderId, event.totalAmount);
  } catch (error) {
    // 补偿:释放库存
    await eventBus.publish(new InventoryReleasedEvent(event.orderId));
  }
});

eventBus.subscribe('PaymentProcessed', 'saga-orchestrator', async (event) => {
  // 步骤 3:确认订单
  await orderService.confirmOrder(event.orderId);
});

// 补偿事件处理器
eventBus.subscribe('PaymentFailed', 'saga-compensator', async (event) => {
  await inventoryService.releaseStock(event.orderId);
  await notificationService.sendPaymentFailedEmail(event.userId);
});

Saga 的关键设计点:

  1. 补偿操作必须幂等:同一补偿事件可能被重试多次
  2. 补偿顺序与正向操作相反:先做的最后补偿(栈结构)
  3. 隔离性牺牲:Saga 执行期间,中间状态对外可见(库存已预留但订单未确认)
  4. 语义锁:通过预留/冻结代替真实扣减,避免客户看到不一致的中间态

对比两阶段提交(2PC):2PC 是强一致性但阻塞式的(协调器故障会导致资源锁定);Saga 是最终一致性但非阻塞的(故障只影响当前流程)。互联网系统更倾向于 Saga。

事件溯源(Event Sourcing)

事件存储

// src/infrastructure/persistence/EventStore.ts
import { Pool } from 'pg';
import { DomainEvent } from '../../domain/events/baseEvent';

export class PostgresEventStore {
  constructor(private pool: Pool) {
    this.initializeSchema();
  }
  
  private async initializeSchema(): Promise<void> {
    await this.pool.query(`
      CREATE TABLE IF NOT EXISTS events (
        event_id UUID PRIMARY KEY,
        event_type VARCHAR(255) NOT NULL,
        aggregate_id UUID NOT NULL,
        aggregate_type VARCHAR(255) NOT NULL,
        event_data JSONB NOT NULL,
        metadata JSONB,
        version INTEGER NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
        UNIQUE(aggregate_id, version)
      );
      
      CREATE INDEX IF NOT EXISTS idx_events_aggregate 
        ON events(aggregate_id, version);
      
      CREATE INDEX IF NOT EXISTS idx_events_type 
        ON events(event_type, created_at);
    `);
  }
  
  async saveEvents(
    aggregateId: string,
    events: DomainEvent[],
    expectedVersion: number
  ): Promise<void> {
    const client = await this.pool.connect();
    
    try {
      await client.query('BEGIN');
      
      // 乐观锁检查
      const currentVersion = await this.getAggregateVersion(client, aggregateId);
      
      if (currentVersion !== expectedVersion) {
        throw new ConcurrencyError(
          `Expected version ${expectedVersion}, but got ${currentVersion}`
        );
      }
      
      // 保存事件
      for (const event of events) {
        await client.query(
          `INSERT INTO events 
           (event_id, event_type, aggregate_id, aggregate_type, 
            event_data, metadata, version, created_at)
           VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
          [
            event.eventId,
            event.eventType,
            event.aggregateId,
            event.aggregateType,
            JSON.stringify(event),
            event.metadata ? JSON.stringify(event.metadata) : null,
            event.version,
            event.timestamp
          ]
        );
      }
      
      await client.query('COMMIT');
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }
  
  async loadEvents(aggregateId: string): Promise<DomainEvent[]> {
    const result = await this.pool.query(
      `SELECT event_data FROM events 
       WHERE aggregate_id = $1 
       ORDER BY version ASC`,
      [aggregateId]
    );
    
    return result.rows.map(row => {
      const eventData = row.event_data;
      return this.deserializeEvent(eventData);
    });
  }
  
  private async getAggregateVersion(
    client: any,
    aggregateId: string
  ): Promise<number> {
    const result = await client.query(
      `SELECT MAX(version) as version FROM events WHERE aggregate_id = $1`,
      [aggregateId]
    );
    
    return result.rows[0]?.version || 0;
  }
  
  private deserializeEvent(eventData: any): DomainEvent {
    // 根据eventType反序列化为具体的事件类
    const eventMap = {
      'OrderCreated': OrderCreatedEvent,
      'OrderConfirmed': OrderConfirmedEvent,
      'OrderCancelled': OrderCancelledEvent,
      // ...
    };
    
    const EventClass = eventMap[eventData.eventType];
    if (!EventClass) {
      throw new Error(`Unknown event type: ${eventData.eventType}`);
    }
    
    return Object.assign(new EventClass(), eventData);
  }
}

// src/domain/aggregates/EventSourcedOrder.ts
export class EventSourcedOrder {
  private version: number = 0;
  
  constructor(
    public readonly id: string,
    public userId: string = '',
    public items: OrderItem[] = [],
    public totalAmount: number = 0,
    public status: OrderStatus = 'PENDING',
    private changes: DomainEvent[] = []
  ) {}
  
  // 从事件流重建状态
  loadFromHistory(events: DomainEvent[]): void {
    for (const event of events) {
      this.apply(event, false);
    }
  }
  
  // 创建新订单
  static create(
    userId: string,
    items: OrderItem[],
    shippingAddress: Address
  ): EventSourcedOrder {
    const order = new EventSourcedOrder(generateUUID());
    
    const totalAmount = items.reduce(
      (sum, item) => sum + item.price * item.quantity,
      0
    );
    
    const event = new OrderCreatedEvent(
      order.id,
      userId,
      items,
      totalAmount,
      shippingAddress
    );
    
    order.apply(event, true);
    
    return order;
  }
  
  confirm(estimatedDelivery: Date): void {
    if (this.status !== 'PENDING') {
      throw new BusinessRuleError('Only pending orders can be confirmed');
    }
    
    const event = new OrderConfirmedEvent(
      this.id,
      new Date(),
      estimatedDelivery
    );
    
    this.apply(event, true);
  }
  
  // 应用事件
  private apply(event: DomainEvent, isNew: boolean): void {
    switch (event.eventType) {
      case 'OrderCreated':
        const createdEvent = event as OrderCreatedEvent;
        this.userId = createdEvent.userId;
        this.items = createdEvent.items;
        this.totalAmount = createdEvent.totalAmount;
        this.status = 'PENDING';
        break;
        
      case 'OrderConfirmed':
        this.status = 'CONFIRMED';
        break;
        
      case 'OrderCancelled':
        this.status = 'CANCELLED';
        break;
    }
    
    this.version++;
    
    if (isNew) {
      this.changes.push(event);
    }
  }
  
  // 获取未保存的变更
  getChanges(): DomainEvent[] {
    return this.changes;
  }
  
  clearChanges(): void {
    this.changes = [];
  }
}

// src/application/services/EventSourcedOrderService.ts
export class EventSourcedOrderService {
  constructor(
    private eventStore: PostgresEventStore,
    private eventPublisher: EventPublisher
  ) {}
  
  async createOrder(command: CreateOrderCommand): Promise<string> {
    const order = EventSourcedOrder.create(
      command.userId,
      command.items,
      command.shippingAddress
    );
    
    const changes = order.getChanges();
    
    await this.eventStore.saveEvents(order.id, changes, 0);
    await this.eventPublisher.publishAll(changes);
    
    order.clearChanges();
    
    return order.id;
  }
  
  async confirmOrder(orderId: string, estimatedDelivery: Date): Promise<void> {
    const events = await this.eventStore.loadEvents(orderId);
    
    if (events.length === 0) {
      throw new NotFoundError('Order', orderId);
    }
    
    const order = new EventSourcedOrder(orderId);
    order.loadFromHistory(events);
    
    const currentVersion = events.length;
    
    order.confirm(estimatedDelivery);
    
    const changes = order.getChanges();
    await this.eventStore.saveEvents(orderId, changes, currentVersion);
    await this.eventPublisher.publishAll(changes);
  }
}

CQRS(命令查询职责分离)

// src/application/commands/OrderCommands.ts
export interface Command {
  commandId: string;
  timestamp: Date;
}

export class CreateOrderCommand implements Command {
  readonly commandId = generateUUID();
  readonly timestamp = new Date();
  
  constructor(
    public readonly userId: string,
    public readonly items: OrderItem[],
    public readonly shippingAddress: Address
  ) {}
}

// src/application/queries/OrderQueries.ts
export interface OrderReadModel {
  id: string;
  userId: string;
  items: OrderItem[];
  totalAmount: number;
  status: string;
  createdAt: Date;
  customerName?: string;
  productNames?: string[];
}

export class GetOrderQuery {
  constructor(public readonly orderId: string) {}
}

export class GetUserOrdersQuery {
  constructor(
    public readonly userId: string,
    public readonly page: number = 1,
    public readonly pageSize: number = 20
  ) {}
}

// src/infrastructure/persistence/OrderReadModelProjector.ts
export class OrderReadModelProjector {
  constructor(private readDb: Pool) {
    this.initializeSchema();
  }
  
  private async initializeSchema(): Promise<void> {
    await this.readDb.query(`
      CREATE TABLE IF NOT EXISTS order_read_models (
        id UUID PRIMARY KEY,
        user_id UUID NOT NULL,
        items JSONB NOT NULL,
        total_amount DECIMAL NOT NULL,
        status VARCHAR(50) NOT NULL,
        customer_name VARCHAR(255),
        product_names TEXT[],
        created_at TIMESTAMP WITH TIME ZONE NOT NULL,
        updated_at TIMESTAMP WITH TIME ZONE NOT NULL
      );
      
      CREATE INDEX IF NOT EXISTS idx_orders_user 
        ON order_read_models(user_id, created_at DESC);
      
      CREATE INDEX IF NOT EXISTS idx_orders_status 
        ON order_read_models(status);
    `);
  }
  
  async project(event: DomainEvent): Promise<void> {
    switch (event.eventType) {
      case 'OrderCreated':
        await this.handleOrderCreated(event as OrderCreatedEvent);
        break;
      case 'OrderConfirmed':
        await this.updateOrderStatus(event.aggregateId, 'CONFIRMED');
        break;
      case 'OrderCancelled':
        await this.updateOrderStatus(event.aggregateId, 'CANCELLED');
        break;
    }
  }
  
  private async handleOrderCreated(event: OrderCreatedEvent): Promise<void> {
    // 查询客户信息
    const customer = await this.readDb.query(
      'SELECT name FROM users WHERE id = $1',
      [event.userId]
    );
    
    // 查询产品名称
    const productIds = event.items.map(item => item.productId);
    const products = await this.readDb.query(
      'SELECT id, name FROM products WHERE id = ANY($1)',
      [productIds]
    );
    
    const productNames = products.rows.map(p => p.name);
    
    await this.readDb.query(
      `INSERT INTO order_read_models 
       (id, user_id, items, total_amount, status, customer_name, product_names, created_at, updated_at)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $8)`,
      [
        event.aggregateId,
        event.userId,
        JSON.stringify(event.items),
        event.totalAmount,
        'PENDING',
        customer.rows[0]?.name || 'Unknown',
        productNames,
        event.timestamp
      ]
    );
  }
  
  private async updateOrderStatus(orderId: string, status: string): Promise<void> {
    await this.readDb.query(
      `UPDATE order_read_models 
       SET status = $1, updated_at = NOW() 
       WHERE id = $2`,
      [status, orderId]
    );
  }
}

// src/application/queries/OrderQueryService.ts
export class OrderQueryService {
  constructor(private readDb: Pool) {}
  
  async getOrder(query: GetOrderQuery): Promise<OrderReadModel | null> {
    const result = await this.readDb.query(
      'SELECT * FROM order_read_models WHERE id = $1',
      [query.orderId]
    );
    
    return result.rows[0] || null;
  }
  
  async getUserOrders(query: GetUserOrdersQuery): Promise<{
    orders: OrderReadModel[];
    total: number;
  }> {
    const offset = (query.page - 1) * query.pageSize;
    
    const [ordersResult, countResult] = await Promise.all([
      this.readDb.query(
        `SELECT * FROM order_read_models 
         WHERE user_id = $1 
         ORDER BY created_at DESC 
         LIMIT $2 OFFSET $3`,
        [query.userId, query.pageSize, offset]
      ),
      this.readDb.query(
        'SELECT COUNT(*) FROM order_read_models WHERE user_id = $1',
        [query.userId]
      )
    ]);
    
    return {
      orders: ordersResult.rows,
      total: parseInt(countResult.rows[0].count)
    };
  }
}

// 订阅事件并投影
const projector = new OrderReadModelProjector(readDb);

eventBus.subscribe('OrderCreated', 'read-model-projector', async (event) => {
  await projector.project(event);
});

eventBus.subscribe('OrderConfirmed', 'read-model-projector', async (event) => {
  await projector.project(event);
});

eventBus.subscribe('OrderCancelled', 'read-model-projector', async (event) => {
  await projector.project(event);
});

事件模式反模式与避坑指南

事件驱动架构在实践中容易陷入几个典型陷阱,新手尤其需要注意:

1. 事件贫血症(Anemic Events)
事件只携带 ID,消费者需要再查询一次才能获取完整信息。这导致 N+1 查询问题和额外的服务依赖。正确的做法是:事件应包含足够上下文(至少包含消费者处理所需的核心字段),同时避免过度膨胀。

2. 分布式大泥球(Distributed Big Ball of Mud)
所有服务监听所有事件,事件链无限延伸,一个事件触发十个消费者,每个消费者又发布新事件,形成难以追踪的"事件风暴"。解法是绘制事件依赖图,限制事件传播深度,设置超时熔断。

3. 过早的事件溯源
并非所有实体都适合事件溯源。简单配置表、日志记录等读多写少的数据,直接用关系型存储更经济。事件溯源的适用条件:业务规则复杂、审计要求严格、需要状态回放(如交易、工单、游戏状态)。

4. 消息顺序假设
分布式消息队列不保证全局顺序(除非单分区单消费者)。如果业务依赖严格顺序(如账户流水),需要在应用层实现顺序控制(如基于聚合 ID 的分区或版本号校验)。

5. 忽略死信队列
消费失败后无限重试会阻塞整个分区。必须配置重试策略(如 3 次重试后转入死信队列),并建立死信告警和人工介入机制。

// 带死信队列的 Kafka 消费者配置
const consumer = kafka.consumer({
  groupId: 'order-service',
  retry: {
    retries: 3,
    initialRetryTime: 1000,
    factor: 2,         // 指数退避
  },
});

// 消费失败处理
await consumer.run({
  eachMessage: async ({ topic, partition, message }) => {
    try {
      await processMessage(message);
    } catch (error) {
      // 超过重试次数后发送到死信队列
      await deadLetterProducer.send({
        topic: `${topic}.dlq`,
        messages: [{ key: message.key, value: message.value, headers: { error: error.message } }],
      });
    }
  },
});

总结

事件驱动架构的核心价值:

  1. 领域事件:捕获业务中发生的重要事实,作为系统通信的基础
  2. 事件总线:实现组件间的松耦合通信,支持多个消费者
  3. 事件溯源:通过事件流重建状态,提供完整的审计追踪
  4. CQRS:分离命令和查询,优化读写性能
  5. Saga 模式:分布式事务的编排方案,保证最终一致性

关键原则:

  • 事件是不可变的事实记录,命名用过去式(OrderCreated,不是 CreateOrder)
  • 使用事件实现服务间的松耦合,但不要用事件替代所有同步调用
  • 事件溯源提供强大的审计和调试能力,但不是银弹
  • CQRS 允许针对读写场景分别优化,但会增加系统复杂度
  • 实现幂等性处理重复事件,分布式系统必须假设消息会重复投递
  • 死信队列和重试策略是生产环境的底线要求

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「backend」更多文章

  1. 零信任安全架构:从边界防御到身份中心的安全范式
  2. 混沌工程实践:构建高可用系统的故障注入与弹性测试
  3. 流式数据处理:Kafka Streams与Flink实战指南