# Event Bus Pattern

## Problem

In a modular application, components need to communicate without directly depending on each other. Directly calling between modules creates tight coupling — adding a new module means modifying existing ones. But a full message broker is overkill for in-process communication.

## Solution

Implement an **event bus** — a lightweight, in-process pub/sub mechanism. Components emit events (plain objects) onto the bus. Other components register handlers for event types they care about. The bus routes events to all registered handlers. Unlike a network message broker, this runs entirely in-memory within a single process.

## When to Use

- Modular monoliths where modules need loose coupling
- Plugin/extension systems
- UI event systems (frameworks like React, Vue use this internally)
- Decoupling domain events from side effects (notifications, logging)
- When a full message broker adds unnecessary infrastructure

## When to Avoid

- Cross-service communication (use a real message broker)
- When you need durability or persistence of messages
- When you need guaranteed delivery or retry semantics
- Simple applications with only 2-3 components

## Pseudocode

```
class EventBus:
    handlers = {}  # event_type → [handler, ...]

    function register(event_type, handler):
        handlers[event_type].add(handler)

    function emit(event):
        for handler in handlers[type(event)]:
            handler(event)
```

## Python Implementation

```python
from collections import defaultdict
from dataclasses import dataclass
from typing import Callable, Any


class EventBus:
    def __init__(self):
        self._handlers: dict[type, list[Callable]] = defaultdict(list)
        self._event_log: list[Any] = []

    def register(self, event_type: type, handler: Callable) -> None:
        self._handlers[event_type].append(handler)

    def emit(self, event: Any) -> int:
        self._event_log.append(event)
        event_type = type(event)
        handlers = self._handlers.get(event_type, [])
        dispatched = 0
        for handler in handlers:
            try:
                handler(event)
                dispatched += 1
            except Exception as exc:
                print(f"    [EventBus] Handler error: {exc}")
        return dispatched

    @property
    def history(self):
        return list(self._event_log)


# --- Domain Events ---

@dataclass
class UserRegistered:
    user_id: int
    name: str
    email: str

@dataclass
class OrderPlaced:
    order_id: str
    user_id: int
    total: float

@dataclass
class PaymentReceived:
    order_id: str
    amount: float


# --- Event Handlers (separate modules) ---

class EmailModule:
    def __init__(self, bus: EventBus):
        bus.register(UserRegistered, self.on_user_registered)
        bus.register(OrderPlaced, self.on_order_placed)

    def on_user_registered(self, event: UserRegistered):
        print(f"    [Email] Welcome email → {event.email}")

    def on_order_placed(self, event: OrderPlaced):
        print(f"    [Email] Order confirmation → user#{event.user_id}")


class AnalyticsModule:
    def __init__(self, bus: EventBus):
        bus.register(UserRegistered, self.on_user_registered)
        bus.register(OrderPlaced, self.on_order_placed)
        bus.register(PaymentReceived, self.on_payment)

    def on_user_registered(self, event: UserRegistered):
        print(f"    [Analytics] Track signup: user#{event.user_id}")

    def on_order_placed(self, event: OrderPlaced):
        print(f"    [Analytics] Track order: {event.order_id} (${event.total:.2f})")

    def on_payment(self, event: PaymentReceived):
        print(f"    [Analytics] Track payment: {event.order_id} (${event.amount:.2f})")


class InventoryModule:
    def __init__(self, bus: EventBus):
        bus.register(OrderPlaced, self.on_order_placed)

    def on_order_placed(self, event: OrderPlaced):
        print(f"    [Inventory] Reserve stock for {event.order_id}")


# --- Demo ---
bus = EventBus()

# Modules register themselves (no module knows about the others)
email = EmailModule(bus)
analytics = AnalyticsModule(bus)
inventory = InventoryModule(bus)

print("=== User Registration ===")
bus.emit(UserRegistered(user_id=1, name="Alice", email="alice@example.com"))

print()
print("=== Order Placed ===")
bus.emit(OrderPlaced(order_id="ORD-001", user_id=1, total=49.99))

print()
print("=== Payment Received ===")
bus.emit(PaymentReceived(order_id="ORD-001", amount=49.99))

print()
print(f"=== Event history: {len(bus.history)} events ===")
for e in bus.history:
    print(f"  {type(e).__name__}: {e}")
```

**Output:**
```
=== User Registration ===
    [Email] Welcome email → alice@example.com
    [Analytics] Track signup: user#1

=== Order Placed ===
    [Email] Order confirmation → user#1
    [Analytics] Track order: ORD-001 ($49.99)
    [Inventory] Reserve stock for ORD-001

=== Payment Received ===
    [Analytics] Track payment: ORD-001 ($49.99)

=== Event history: 3 events ===
  UserRegistered: UserRegistered(user_id=1, name='Alice', email='alice@example.com')
  OrderPlaced: OrderPlaced(order_id='ORD-001', user_id=1, total=49.99)
  PaymentReceived: PaymentReceived(order_id='ORD-001', amount=49.99)
```

## Go Implementation

```go
package main

import (
	"fmt"
	"reflect"
	"sync"
)

type EventBus struct {
	mu       sync.RWMutex
	handlers map[reflect.Type][]func(interface{})
}

func NewEventBus() *EventBus {
	return &EventBus{handlers: make(map[reflect.Type][]func(interface{}))}
}

func (eb *EventBus) Register(eventType interface{}, handler func(interface{})) {
	eb.mu.Lock()
	defer eb.mu.Unlock()
	t := reflect.TypeOf(eventType)
	eb.handlers[t] = append(eb.handlers[t], handler)
}

func (eb *EventBus) Emit(event interface{}) int {
	eb.mu.RLock()
	handlers := eb.handlers[reflect.TypeOf(event)]
	eb.mu.RUnlock()

	for _, h := range handlers {
		h(event)
	}
	return len(handlers)
}

// Events
type UserRegistered struct {
	UserID int
	Name   string
	Email  string
}

type OrderPlaced struct {
	OrderID string
	UserID  int
	Total   float64
}

func main() {
	bus := NewEventBus()

	// Email module
	bus.Register(UserRegistered{}, func(e interface{}) {
		ev := e.(UserRegistered)
		fmt.Printf("    [Email] Welcome → %s\n", ev.Email)
	})
	bus.Register(OrderPlaced{}, func(e interface{}) {
		ev := e.(OrderPlaced)
		fmt.Printf("    [Email] Confirmation → user#%d\n", ev.UserID)
	})

	// Analytics module
	bus.Register(UserRegistered{}, func(e interface{}) {
		ev := e.(UserRegistered)
		fmt.Printf("    [Analytics] Signup: user#%d\n", ev.UserID)
	})
	bus.Register(OrderPlaced{}, func(e interface{}) {
		ev := e.(OrderPlaced)
		fmt.Printf("    [Analytics] Order: %s ($%.2f)\n", ev.OrderID, ev.Total)
	})

	fmt.Println("=== User Registration ===")
	bus.Emit(UserRegistered{UserID: 1, Name: "Alice", Email: "alice@example.com"})

	fmt.Println("\n=== Order Placed ===")
	bus.Emit(OrderPlaced{OrderID: "ORD-001", UserID: 1, Total: 49.99})
}
```

## JavaScript Implementation

```javascript
class EventBus {
  constructor() {
    this._handlers = new Map();
    this._history = [];
  }

  register(eventType, handler) {
    if (!this._handlers.has(eventType)) {
      this._handlers.set(eventType, []);
    }
    this._handlers.get(eventType).push(handler);
  }

  emit(eventType, payload) {
    this._history.push({ type: eventType, payload, timestamp: Date.now() });
    const handlers = this._handlers.get(eventType) || [];
    for (const handler of handlers) {
      try { handler(payload); } catch (e) { console.log(`  [EventBus] Error: ${e.message}`); }
    }
    return handlers.length;
  }

  get history() { return [...this._history]; }
}

// --- Demo ---
const bus = new EventBus();

// Email module
bus.register("user.registered", (e) => console.log(`    [Email] Welcome → ${e.email}`));
bus.register("order.placed", (e) => console.log(`    [Email] Confirmation → user#${e.userId}`));

// Analytics module
bus.register("user.registered", (e) => console.log(`    [Analytics] Signup: user#${e.userId}`));
bus.register("order.placed", (e) => console.log(`    [Analytics] Order: ${e.orderId} ($${e.total})`));

// Inventory module
bus.register("order.placed", (e) => console.log(`    [Inventory] Reserve stock for ${e.orderId}`));

console.log("=== User Registration ===");
bus.emit("user.registered", { userId: 1, name: "Alice", email: "alice@example.com" });

console.log("\n=== Order Placed ===");
bus.emit("order.placed", { orderId: "ORD-001", userId: 1, total: 49.99 });

console.log(`\nEvent history: ${bus.history.length} events`);
bus.history.forEach((e) => console.log(`  ${e.type}: ${JSON.stringify(e.payload)}`));
```

## Related Patterns

- **Observer** — an event bus is a centralised implementation of the observer pattern; the bus replaces direct observer registration.
- **Mediator** — the event bus acts as a mediator: publishers and subscribers never reference each other directly.
- **Publish-Subscribe** — a distributed message broker (Kafka, RabbitMQ) is the network-level version of an in-process event bus.
