Harith Zahid

Use Cases for Event-Driven Architecture

Event-driven architecture is ideal when you have loosely coupled systems that need to react to changes or when actions trigger multiple side effects. Here are some practical use cases:

1. E-Commerce Order Processing

When a user places an order, multiple things need to happen independently:

const EventEmitter = require('events');

class OrderSystem extends EventEmitter {
    placeOrder(order) {
        console.log(`Order placed: ${order.id}`);
        
        // Emit event instead of calling functions directly
        this.emit('orderPlaced', order);
        
        return { success: true, orderId: order.id };
    }
}

const orderSystem = new OrderSystem();

// Different services listen for the event
orderSystem.on('orderPlaced', (order) => {
    console.log('📧 Sending confirmation email to:', order.customerEmail);
});

orderSystem.on('orderPlaced', (order) => {
    console.log('📦 Updating inventory for items:', order.items);
});

orderSystem.on('orderPlaced', (order) => {
    console.log('💳 Processing payment of $', order.total);
});

orderSystem.on('orderPlaced', (order) => {
    console.log('📊 Logging analytics event');
});

orderSystem.on('orderPlaced', (order) => {
    console.log('🔔 Notifying warehouse for shipment');
});

// Place an order - all listeners react automatically
orderSystem.placeOrder({
    id: '12345',
    customerEmail: 'user@example.com',
    items: ['laptop', 'mouse'],
    total: 1299.99
});

Why event-driven here?

  • Each service (email, inventory, payment) operates independently
  • Easy to add new services without modifying order placement code
  • Services can fail independently without breaking the order flow

2. Real-Time Notifications System

const EventEmitter = require('events');

class NotificationService extends EventEmitter {
    constructor() {
        super();
        this.setupListeners();
    }
    
    setupListeners() {
        // Listen for various app events
        this.on('userSignup', (user) => {
            this.sendWelcomeEmail(user);
            this.sendSlackNotification(`New user: ${user.name}`);
        });
        
        this.on('commentAdded', (data) => {
            this.notifyPostAuthor(data);
            this.pushNotification(data.userId, 'New comment on your post');
        });
        
        this.on('paymentFailed', (data) => {
            this.sendUrgentEmail(data.userId);
            this.sendSMS(data.phone, 'Payment failed');
        });
    }
    
    sendWelcomeEmail(user) {
        console.log(`✉️  Sending welcome email to ${user.email}`);
    }
    
    sendSlackNotification(message) {
        console.log(`💬 Slack: ${message}`);
    }
    
    notifyPostAuthor(data) {
        console.log(`🔔 Notifying post author about comment`);
    }
    
    pushNotification(userId, message) {
        console.log(`📱 Push to ${userId}: ${message}`);
    }
    
    sendUrgentEmail(userId) {
        console.log(`🚨 Urgent email to user ${userId}`);
    }
    
    sendSMS(phone, message) {
        console.log(`📲 SMS to ${phone}: ${message}`);
    }
}

// Usage
const notifications = new NotificationService();

// Anywhere in your app, emit events
notifications.emit('userSignup', { 
    name: 'Alice', 
    email: 'alice@example.com' 
});

notifications.emit('commentAdded', { 
    userId: '123', 
    postId: '456',
    comment: 'Great post!'
});

3. File Upload Processing Pipeline

const EventEmitter = require('events');
const fs = require('fs');

class FileProcessor extends EventEmitter {
    uploadFile(file) {
        console.log(`📁 File uploaded: ${file.name}`);
        this.emit('fileUploaded', file);
    }
}

const processor = new FileProcessor();

// Image processing pipeline
processor.on('fileUploaded', async (file) => {
    if (file.type === 'image') {
        console.log('🖼️  Generating thumbnail...');
        // Simulate async processing
        setTimeout(() => {
            processor.emit('thumbnailCreated', { file, thumbnail: 'thumb.jpg' });
        }, 1000);
    }
});

processor.on('thumbnailCreated', (data) => {
    console.log('☁️  Uploading to cloud storage...');
    processor.emit('cloudUploadComplete', data);
});

processor.on('cloudUploadComplete', (data) => {
    console.log('🗄️  Updating database with file URL...');
});

processor.on('fileUploaded', (file) => {
    console.log('🦠 Running virus scan...');
});

processor.on('fileUploaded', (file) => {
    console.log('📊 Updating user storage quota...');
});

// Trigger the pipeline
processor.uploadFile({ 
    name: 'photo.jpg', 
    type: 'image', 
    size: 2048576 
});

4. Chat Application

const EventEmitter = require('events');

class ChatRoom extends EventEmitter {
    constructor(name) {
        super();
        this.name = name;
        this.users = new Map();
    }
    
    addUser(userId, username) {
        this.users.set(userId, username);
        this.emit('userJoined', { userId, username });
    }
    
    removeUser(userId) {
        const username = this.users.get(userId);
        this.users.delete(userId);
        this.emit('userLeft', { userId, username });
    }
    
    sendMessage(userId, message) {
        this.emit('message', {
            userId,
            username: this.users.get(userId),
            message,
            timestamp: new Date()
        });
    }
}

const gameRoom = new ChatRoom('Gaming');

// Different clients listen for events
gameRoom.on('userJoined', (data) => {
    console.log(`👋 ${data.username} joined the chat`);
});

gameRoom.on('userLeft', (data) => {
    console.log(`👋 ${data.username} left the chat`);
});

gameRoom.on('message', (data) => {
    console.log(`[${data.timestamp.toLocaleTimeString()}] ${data.username}: ${data.message}`);
    
    // Trigger secondary events
    if (data.message.includes('@everyone')) {
        gameRoom.emit('mentionEveryone', data);
    }
});

gameRoom.on('mentionEveryone', (data) => {
    console.log('🔔 Sending notifications to all users...');
});

// Simulate chat activity
gameRoom.addUser('user1', 'Alice');
gameRoom.addUser('user2', 'Bob');
gameRoom.sendMessage('user1', 'Hey everyone!');
gameRoom.sendMessage('user2', '@everyone check this out!');
gameRoom.removeUser('user1');

5. Microservices Communication

const EventEmitter = require('events');

// Central event bus
class EventBus extends EventEmitter {}
const eventBus = new EventBus();

// User Service
class UserService {
    constructor(eventBus) {
        this.eventBus = eventBus;
    }
    
    createUser(userData) {
        const user = { id: Date.now(), ...userData };
        console.log('👤 User created:', user.email);
        
        // Publish event
        this.eventBus.emit('user.created', user);
        return user;
    }
}

// Email Service (subscribes to user events)
class EmailService {
    constructor(eventBus) {
        eventBus.on('user.created', (user) => {
            this.sendWelcomeEmail(user);
        });
        
        eventBus.on('order.completed', (order) => {
            this.sendOrderConfirmation(order);
        });
    }
    
    sendWelcomeEmail(user) {
        console.log('📧 Sending welcome email to:', user.email);
    }
    
    sendOrderConfirmation(order) {
        console.log('📧 Sending order confirmation');
    }
}

// Analytics Service
class AnalyticsService {
    constructor(eventBus) {
        eventBus.on('user.created', (user) => {
            console.log('📊 Tracking new user signup');
        });
        
        eventBus.on('order.completed', (order) => {
            console.log('📊 Tracking order completion');
        });
    }
}

// Setup services
const userService = new UserService(eventBus);
const emailService = new EmailService(eventBus);
const analyticsService = new AnalyticsService(eventBus);

// Create a user - other services react automatically
userService.createUser({ 
    email: 'newuser@example.com', 
    name: 'Charlie' 
});

Key Benefits of Event-Driven Architecture:

  1. Loose Coupling: Services don't need to know about each other
  2. Scalability: Easy to add new event listeners without changing existing code
  3. Asynchronous: Non-blocking operations that can happen in parallel
  4. Flexibility: Easy to add/remove functionality
  5. Real-time: Perfect for applications that need instant reactions

Event-driven architecture shines when you have systems where one action needs to trigger multiple independent reactions, especially when those reactions might be added or modified over time.