KOA技术分享

专注 Koa.js 框架的编程知识分享

Koa.js 事件驱动架构与 Event Sourcing 实践

引言

事件驱动架构(EDA)和事件溯源(Event Sourcing)是现代分布式系统的重要设计模式。本文将介绍如何在 Koa.js 中实现事件驱动架构,构建可追溯、可扩展的应用系统。

CQRS 架构

命令查询职责分离模式:

组件 职责 说明
Command 写入操作 创建、更新、删除
Query 读取操作 查询、过滤、分页
Event Store 存储事件 不可变事件流
Read Model 查询视图 物化视图

核心代码实现

事件总线实现:

// 事件总线
class EventBus {
  constructor(config = {}) {
    this.subscribers = new Map();
    this.middlewares = [];
    this.eventStore = config.eventStore || new InMemoryEventStore();
    this.logger = config.logger || console;
  }

  // 注册中间件
  use(middleware) {
    this.middlewares.push(middleware);
  }

  // 订阅事件
  subscribe(eventType, handler) {
    if (!this.subscribers.has(eventType)) {
      this.subscribers.set(eventType, []);
    }
    this.subscribers.get(eventType).push(handler);
  }

  // 取消订阅
  unsubscribe(eventType, handler) {
    const handlers = this.subscribers.get(eventType);
    if (handlers) {
      const index = handlers.indexOf(handler);
      if (index > -1) {
        handlers.splice(index, 1);
      }
    }
  }

  // 发布事件
  async publish(event, metadata = {}) {
    const enrichedEvent = {
      ...event,
      eventId: this.generateId(),
      timestamp: new Date().toISOString(),
      metadata
    };

    this.logger.info(`Event published: ${enrichedEvent.eventType}`);

    // 存储事件
    await this.eventStore.append(enrichedEvent);

    // 执行中间件
    let context = { event: enrichedEvent };
    for (const middleware of this.middlewares) {
      context = await middleware(context);
    }

    // 通知订阅者
    const handlers = this.subscribers.get(enrichedEvent.eventType) || [];
    const asyncHandlers = this.subscribers.get('*') || []; // 通配符处理器

    const allHandlers = [...handlers, ...asyncHandlers];

    const results = await Promise.allSettled(
      allHandlers.map(handler =>
        handler(enrichedEvent).catch(err => ({ error: err.message }))
      )
    );

    // 检查失败
    const failures = results.filter(r => r.status === 'rejected');
    if (failures.length > 0) {
      this.logger.error(`${failures.length} handlers failed`);
    }

    return enrichedEvent;
  }

  // 生成唯一ID
  generateId() {
    return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }
}

// 事件存储器
class EventStore {
  async append(event) {
    throw new Error('must be implemented');
  }

  async getEvents(aggregateId, fromVersion = 0) {
    throw new Error('must be implemented');
  }

  async getAllEvents(fromOffset = 0, limit = 100) {
    throw new Error('must be implemented');
  }
}

// 内存事件存储(开发环境)
class InMemoryEventStore extends EventStore {
  constructor() {
    super();
    this.events = [];
    this.aggregateIndexes = new Map();
  }

  async append(event) {
    this.events.push(event);

    // 建立聚合索引
    if (event.aggregateId) {
      if (!this.aggregateIndexes.has(event.aggregateId)) {
        this.aggregateIndexes.set(event.aggregateId, []);
      }
      this.aggregateIndexes.get(event.aggregateId).push(event);
    }
  }

  async getEvents(aggregateId, fromVersion = 0) {
    const events = this.aggregateIndexes.get(aggregateId) || [];
    return events.filter(e => e.aggregateVersion > fromVersion);
  }

  async getAllEvents(fromOffset = 0, limit = 100) {
    return this.events.slice(fromOffset, fromOffset + limit);
  }
}

//MongoDB 事件存储
class MongoEventStore extends EventStore {
  constructor(mongoClient, database = 'events') {
    super();
    this.collection = mongoClient.db(database).collection('events');
    this.collection.createIndex({ aggregateId: 1, aggregateVersion: 1 });
    this.collection.createIndex({ timestamp: 1 });
  }

  async append(event) {
    await this.collection.insertOne({
      ...event,
      _savedAt: new Date()
    });
  }

  async getEvents(aggregateId, fromVersion = 0) {
    return await this.collection.find({
      aggregateId,
      aggregateVersion: { $gt: fromVersion }
    }).sort({ aggregateVersion: 1 }).toArray();
  }

  async getAllEvents(fromOffset = 0, limit = 100) {
    return await this.collection.find({})
      .sort({ timestamp: 1 })
      .skip(fromOffset)
      .limit(limit)
      .toArray();
  }
}

// 命令处理器
class CommandHandler {
  constructor(eventBus, aggregateFactory) {
    this.eventBus = eventBus;
    this.aggregateFactory = aggregateFactory;
    this.handlers = new Map();
  }

  // 注册命令处理器
  register(commandType, handler) {
    this.handlers.set(commandType, handler);
  }

  // 处理命令
  async handle(command) {
    const handler = this.handlers.get(command.type);
    if (!handler) {
      throw new Error(`No handler for command: ${command.type}`);
    }

    // 获取聚合根
    let aggregate;
    if (command.aggregateId) {
      aggregate = await this.aggregateFactory.load(
        command.aggregateId,
        command.aggregateType
      );
    }

    // 执行命令处理
    const result = await handler(aggregate, command);

    // 发布事件
    if (result.events && result.events.length > 0) {
      for (const event of result.events) {
        await this.eventBus.publish(event, {
          commandId: command.commandId,
          aggregateId: command.aggregateId,
          userId: command.userId
        });
      }
    }

    // 保存聚合
    if (aggregate && result.events.length > 0) {
      await this.aggregateFactory.save(aggregate);
    }

    return result;
  }
}

// 聚合根
class AggregateRoot {
  constructor(id, type) {
    this.id = id;
    this.type = type;
    this.version = 0;
    this.uncommittedEvents = [];
    this.state = this.getInitialState();
  }

  getInitialState() {
    return {};
  }

  // 应用事件更新状态
  apply(event) {
    this.version++;
    const handler = `apply${this.capitalize(event.eventType)}`;
    if (this[handler]) {
      this[handler](event);
    }
  }

  // 收集未提交事件
  collectEvents() {
    const events = [...this.uncommittedEvents];
    this.uncommittedEvents = [];
    return events;
  }

  // 添加未提交事件
  addUncommittedEvent(event) {
    this.uncommittedEvents.push({
      ...event,
      aggregateId: this.id,
      aggregateType: this.type,
      aggregateVersion: this.version + 1
    });
  }

  capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }
}

Event Sourcing 实现

事件溯源与状态重建:

// 聚合工厂
class AggregateFactory {
  constructor(eventStore) {
    this.eventStore = eventStore;
    this.aggregates = new Map();
  }

  // 注册聚合类型
  register(type, aggregateClass) {
    this.aggregates.set(type, aggregateClass);
  }

  // 加载聚合
  async load(id, type) {
    const AggregateClass = this.aggregates.get(type);
    if (!AggregateClass) {
      throw new Error(`Unknown aggregate type: ${type}`);
    }

    const aggregate = new AggregateClass(id);
    const events = await this.eventStore.getEvents(id);

    // 重放事件重建状态
    for (const event of events) {
      aggregate.apply(event);
    }

    return aggregate;
  }

  // 保存聚合
  async save(aggregate) {
    const events = aggregate.collectEvents();
    for (const event of events) {
      await this.eventStore.append(event);
    }
  }
}

// 订单聚合示例
class OrderAggregate extends AggregateRoot {
  constructor(orderId) {
    super(orderId, 'Order');
  }

  getInitialState() {
    return {
      customerId: null,
      items: [],
      totalAmount: 0,
      status: 'pending',
      createdAt: null,
      updatedAt: null
    };
  }

  // 创建订单
  createOrder(command) {
    this.addUncommittedEvent({
      eventType: 'OrderCreated',
      data: {
        customerId: command.customerId,
        items: command.items,
        totalAmount: command.totalAmount
      }
    });
  }

  // 应用订单创建事件
  applyOrderCreated(event) {
    this.state.customerId = event.data.customerId;
    this.state.items = event.data.items;
    this.state.totalAmount = event.data.totalAmount;
    this.state.status = 'created';
    this.state.createdAt = event.timestamp;
    this.state.updatedAt = event.timestamp;
  }

  // 添加订单项
  addItem(command) {
    if (this.state.status !== 'created' && this.state.status !== 'pending') {
      throw new Error('Cannot add items to order in current status');
    }

    this.addUncommittedEvent({
      eventType: 'ItemAdded',
      data: {
        itemId: command.itemId,
        productId: command.productId,
        quantity: command.quantity,
        price: command.price
      }
    });
  }

  applyItemAdded(event) {
    this.state.items.push({
      itemId: event.data.itemId,
      productId: event.data.productId,
      quantity: event.data.quantity,
      price: event.data.price
    });
    this.state.totalAmount += event.data.price * event.data.quantity;
    this.state.updatedAt = event.timestamp;
  }

  // 确认订单
  confirmOrder() {
    if (this.state.status !== 'created') {
      throw new Error('Order cannot be confirmed in current status');
    }

    this.addUncommittedEvent({
      eventType: 'OrderConfirmed',
      data: {}
    });
  }

  applyOrderConfirmed(event) {
    this.state.status = 'confirmed';
    this.state.updatedAt = event.timestamp;
  }

  // 完成订单
  completeOrder() {
    if (this.state.status !== 'confirmed') {
      throw new Error('Order cannot be completed in current status');
    }

    this.addUncommittedEvent({
      eventType: 'OrderCompleted',
      data: {}
    });
  }

  applyOrderCompleted(event) {
    this.state.status = 'completed';
    this.state.updatedAt = event.timestamp;
  }
}

// 查询模型管理器
class ReadModelManager {
  constructor(eventBus) {
    this.projectors = new Map();
    this.queryModels = {};
    eventBus.subscribe('*', this.handleEvent.bind(this));
  }

  // 注册投影器
  register(projectionName, projector) {
    this.projectors.set(projectionName, projector);
    this.queryModels[projectionName] = projector.getInitialState();
  }

  // 处理事件
  async handleEvent(event) {
    for (const [name, projector] of this.projectors) {
      try {
        await projector.project(event, this.queryModels[name]);
      } catch (error) {
        console.error(`Projection ${name} failed:`, error);
      }
    }
  }

  // 获取查询模型
  getQueryModel(name) {
    return this.queryModels[name];
  }
}

// 订单查询模型投影器
class OrderQueryProjector {
  getInitialState() {
    return {
      orders: new Map(),
      ordersByCustomer: new Map(),
      ordersByStatus: new Map()
    };
  }

  async project(event, state) {
    switch (event.eventType) {
      case 'OrderCreated':
        state.orders.set(event.aggregateId, {
          id: event.aggregateId,
          customerId: event.data.customerId,
          items: event.data.items,
          totalAmount: event.data.totalAmount,
          status: 'created',
          createdAt: event.timestamp
        });

        // 按客户索引
        if (!state.ordersByCustomer.has(event.data.customerId)) {
          state.ordersByCustomer.set(event.data.customerId, []);
        }
        state.ordersByCustomer.get(event.data.customerId).push(event.aggregateId);

        // 按状态索引
        if (!state.ordersByStatus.has('created')) {
          state.ordersByStatus.set('created', []);
        }
        state.ordersByStatus.get('created').push(event.aggregateId);
        break;

      case 'OrderConfirmed':
        const order1 = state.orders.get(event.aggregateId);
        if (order1) {
          order1.status = 'confirmed';
          this.moveStatusIndex(state.ordersByStatus, event.aggregateId, 'created', 'confirmed');
        }
        break;

      case 'OrderCompleted':
        const order2 = state.orders.get(event.aggregateId);
        if (order2) {
          order2.status = 'completed';
          this.moveStatusIndex(state.ordersByStatus, event.aggregateId, 'confirmed', 'completed');
        }
        break;
    }
  }

  moveStatusIndex(indexMap, orderId, fromStatus, toStatus) {
    if (indexMap.has(fromStatus)) {
      const list = indexMap.get(fromStatus);
      const idx = list.indexOf(orderId);
      if (idx > -1) list.splice(idx, 1);
    }
    if (!indexMap.has(toStatus)) {
      indexMap.set(toStatus, []);
    }
    indexMap.get(toStatus).push(orderId);
  }
}

最佳实践建议

总结

事件驱动架构与 Event Sourcing 为应用带来:

通过在 Koa.js 中实现事件驱动架构,可以构建更健壮、可维护的应用系统。

← 下一篇:Koa.js 与大语言模型集成实践