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 langsung memanggil observer. Subject tahu siapa observer-nya.
- Pub/Sub — publisher kirim pesan ke channel. Subscriber dengarkan channel. Mereka tidak saling tahu.
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:
- Redux — dispatch action ke store, reducers merespons
- WebSocket — server publish events, client subscribe
- Browser postMessage — komunikasi antar iframe/window
- Service Worker — komunikasi antara main thread dan worker
- Kafka, RabbitMQ, Redis Pub/Sub — server-side message queues
Kapan pakai Pub/Sub:
- Komunikasi antar module/component yang tidak boleh tightly coupled
- Saat publisher tidak perlu tahu siapa yang memproses event
- Real-time updates, cross-module communication, plugin systems