正在加载,请稍候…

Event-Driven Architecture with Node.js: Patterns and Implementation

Build loosely coupled systems with event-driven architecture in Node.js. Learn event emitters, message queues, Kafka, and event sourcing patterns.

Event-Driven Architecture with Node.js

Event-driven systems react to events rather than direct calls, enabling loose coupling and scalability.

Node.js EventEmitter

import { EventEmitter } from 'events';

interface UserEvents {
  'user.created': [userId: string, email: string];
  'user.deleted': [userId: string];
  'order.placed': [orderId: string, userId: string, amount: number];
}

class ApplicationEventBus extends EventEmitter {
  emit<K extends keyof UserEvents>(event: K, ...args: UserEvents[K]): boolean {
    return super.emit(event, ...args);
  }

  on<K extends keyof UserEvents>(event: K, listener: (...args: UserEvents[K]) => void): this {
    return super.on(event, listener);
  }
}

const bus = new ApplicationEventBus();

// Publishers
bus.emit('user.created', 'user_123', 'alice@example.com');

// Subscribers
bus.on('user.created', async (userId, email) => {
  await emailService.sendWelcome(email);
  await analyticsService.track('user_signup', { userId });
});

Message Queue with Bull (Redis)

import Queue from 'bull';

const emailQueue = new Queue('email', { redis: { host: 'localhost', port: 6379 } });

// Producer
async function onUserCreated(user: User): Promise<void> {
  await emailQueue.add('welcome', { userId: user.id, email: user.email }, {
    attempts: 3,
    backoff: { type: 'exponential', delay: 2000 },
    removeOnComplete: true,
  });
}

// Consumer
emailQueue.process('welcome', async (job) => {
  const { userId, email } = job.data;
  await sendWelcomeEmail(email);
  console.log(`Sent welcome email to ${email}`);
});

emailQueue.on('failed', (job, err) => {
  console.error(`Job ${job.id} failed: ${err.message}`);
});

Kafka with kafkajs

import { Kafka } from 'kafkajs';

const kafka = new Kafka({
  clientId: 'my-app',
  brokers: ['kafka:9092'],
});

// Producer
const producer = kafka.producer();
await producer.connect();

await producer.send({
  topic: 'user-events',
  messages: [{
    key: 'user.created',
    value: JSON.stringify({ userId: 'user_123', email: 'alice@example.com' }),
    headers: { 'event-type': 'user.created' },
  }],
});

// Consumer
const consumer = kafka.consumer({ groupId: 'notification-service' });
await consumer.connect();
await consumer.subscribe({ topic: 'user-events', fromBeginning: false });

await consumer.run({
  eachMessage: async ({ topic, partition, message }) => {
    const event = JSON.parse(message.value!.toString());
    const eventType = message.headers?.['event-type']?.toString();

    switch (eventType) {
      case 'user.created':
        await handleUserCreated(event);
        break;
      case 'order.placed':
        await handleOrderPlaced(event);
        break;
    }
  },
});

Outbox Pattern

Ensures events are published atomically with database changes.

// Within a database transaction:
async function createUserWithOutbox(dto: CreateUserDto): Promise<User> {
  return await db.transaction(async (trx) => {
    const user = await trx.insert('users', { ...dto });

    // Write event to outbox in same transaction
    await trx.insert('outbox_events', {
      id: generateId(),
      aggregate_type: 'User',
      aggregate_id: user.id,
      event_type: 'user.created',
      payload: JSON.stringify({ userId: user.id, email: user.email }),
      created_at: new Date(),
      processed: false,
    });

    return user;
  });
}

// Separate process polls outbox and publishes to Kafka
async function processOutbox(): Promise<void> {
  const events = await db.query(
    'SELECT * FROM outbox_events WHERE processed = false ORDER BY created_at LIMIT 100'
  );

  for (const event of events) {
    await kafkaProducer.send({ topic: event.event_type, messages: [{ value: event.payload }] });
    await db.query('UPDATE outbox_events SET processed = true WHERE id = $1', [event.id]);
  }
}

The outbox pattern guarantees at-least-once delivery without distributed transactions.