System Design

Publish-Subscribe pattern

Pub/Sub — bir xabarni ko’plab subscriber’larga yuborish.

Queue vs Pub/Sub

Queue (1-to-1)

Producer → Queue → 1 Consumer
(har bir message bitta consumer)

Pub/Sub (1-to-Many)

Publisher → Topic → Subscriber 1
                  → Subscriber 2
                  → Subscriber 3
(har bir message barcha subscriber'larga)

Use Cases

1. Real-time Notifications

// User likes post
publisher.publish('post:123:liked', { userId: 456 });

// Multiple subscribers
subscriber1.on('post:123:liked', () => {
  // Update counter in UI
});
subscriber2.on('post:123:liked', () => {
  // Send notification to author
});
subscriber3.on('post:123:liked', () => {
  // Update analytics
});

2. Microservices Communication

Order Service → publishes "order.created"

  ┌─────────────┼─────────────┐
  ▼             ▼             ▼
Email Service  Inventory    Analytics
(send receipt) (decrease)   (track)

3. Cache Invalidation

// Update user
await db.updateUser(123, data);
publisher.publish('user:123:updated', data);

// All cache servers listen
subscriber.on('user:*:updated', (data) => {
  cache.del(`user:${data.id}`);
});

Redis Pub/Sub

const redis = require('redis');

// Publisher
const pub = redis.createClient();
await pub.publish('notifications', JSON.stringify({ message: 'Hello' }));

// Subscriber
const sub = redis.createClient();
await sub.subscribe('notifications', (message) => {
  console.log('Received:', JSON.parse(message));
});

// Pattern subscription
await sub.pSubscribe('user:*:updated', (message, channel) => {
  console.log('Channel:', channel);
  console.log('Message:', message);
});

Reliability: Redis pub/sub не гарантирует delivery (subscriber offline = message lost)

Reliable Pub/Sub (Redis Streams)

// Publisher
await redis.xAdd('events', '*', {
  type: 'user.created',
  data: JSON.stringify({ id: 123 })
});

// Consumer group (reliable)
await redis.xGroupCreate('events', 'processors', '0');
await redis.xReadGroup('processors', 'consumer1', {
  key: 'events',
  id: '>'
});

Guaranteed delivery
Consumer groups
Message persistence

Apache Kafka (Production-grade)

// Producer
await producer.send({
  topic: 'user-events',
  messages: [
    { key: '123', value: JSON.stringify({ type: 'created' }) }
  ]
});

// Consumer
await consumer.subscribe({ topic: 'user-events' });
await consumer.run({
  eachMessage: async ({ topic, partition, message }) => {
    console.log({
      value: message.value.toString()
    });
  }
});

Features:

Best Practices

1. Idempotent subscribers

// Message may arrive multiple times
subscriber.on('order.created', async (order) => {
  const exists = await db.exists(`processed:${order.id}`);
  if (exists) return; // Skip
  
  await processOrder(order);
  await db.set(`processed:${order.id}`, true);
});

2. Error handling

subscriber.on('event', async (data) => {
  try {
    await process(data);
  } catch (err) {
    console.error('Failed to process:', err);
    // Don't throw - other subscribers should still receive
  }
});

3. Schema versioning

// Forward compatible
{
  version: 2,
  type: 'user.created',
  data: { ... }
}

subscriber.on('user.created', (event) => {
  if (event.version === 1) {
    // Handle old format
  } else if (event.version === 2) {
    // Handle new format
  }
});

Xulosa

Pub/Sub:

Tools:

Keyingi dars: Event-Driven Architecture.