Pub/Sub Pattern — Design Patterns

Pub/Sub (Publish/Subscribe) adalah variasi Observer Pattern dimana publisher dan subscriber tidak saling kenal. Komunikasi terjadi melalui perantara (message…

Pub/Sub (Publish/Subscribe) adalah variasi Observer Pattern dimana publisher dan subscriber tidak saling kenal. Komunikasi terjadi melalui perantara (message broker/event bus).

Bedanya dengan Observer:

Observer:   Subject ──→ Observer1, Observer2
Pub/Sub:    Publisher ──→ [Event Bus] ──→ Subscriber1, Subscriber2

Implementasi Event Bus:

class EventBus {
  constructor() {
    this.channels = {};
  }

  subscribe(channel, callback) {
    if (!this.channels[channel]) {
      this.channels[channel] = [];
    }
    this.channels[channel].push(callback);

    // Return unsubscribe function
    return () => {
      this.channels[channel] = this.channels[channel]
        .filter(cb => cb !== callback);
    };
  }

  publish(channel, data) {
    const subscribers = this.channels[channel] || [];
    subscribers.forEach(cb => cb(data));
  }
}

const bus = new EventBus();

// Subscriber 1 — UI component
bus.subscribe("cart:updated", (cart) => {
  console.log(`Cart badge: ${cart.items.length} items`);
});

// Subscriber 2 — Analytics
bus.subscribe("cart:updated", (cart) => {
  console.log(`Track: cart has ${cart.total} total`);
});

// Publisher — Cart service (tidak tahu siapa yang subscribe)
bus.publish("cart:updated", {
  items: ["Laptop", "Mouse"],
  total: 15250000
});
// "Cart badge: 2 items"
// "Track: cart has 15250000 total"

Contoh: Micro-frontend communication

// Event Bus global untuk komunikasi antar module
const appBus = new EventBus();

// Module Auth — publish saat login/logout
function authModule() {
  function login(user) {
    appBus.publish("auth:login", user);
  }
  function logout() {
    appBus.publish("auth:logout", null);
  }
  return { login, logout };
}

// Module Header — subscribe ke auth events
function headerModule() {
  appBus.subscribe("auth:login", (user) => {
    console.log(`Header: Show ${user.name}`);
  });
  appBus.subscribe("auth:logout", () => {
    console.log("Header: Show login button");
  });
}

// Module Sidebar — subscribe ke auth events
function sidebarModule() {
  appBus.subscribe("auth:login", (user) => {
    console.log(`Sidebar: Load ${user.name} menu`);
  });
}

// Modules tidak saling import — loose coupling!

Typed Events dengan TypeScript:

// Tanpa TypeScript, gunakan naming convention:
// "domain:action" → "user:login", "cart:add", "notification:show"

const events = {
  USER_LOGIN: "user:login",
  USER_LOGOUT: "user:logout",
  CART_ADD: "cart:add",
  CART_REMOVE: "cart:remove",
};

bus.subscribe(events.USER_LOGIN, handleLogin);
bus.publish(events.CART_ADD, { productId: 123 });

Pub/Sub di dunia nyata:

Kapan pakai Pub/Sub: