--- id: wiki-2026-0508-event-mediator title: Event Mediator category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Mediator Pattern, Event Bus] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [architecture, design-pattern, event-driven, decoupling] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: nodejs --- # Event Mediator ## 매 한 줄 > **"매 N×N component coupling 의 N×1 mediator hub 의 collapse"**. Event Mediator 매 multiple components 의 direct coupling 의 elimination — 매 components 매 mediator 를 publish/subscribe, 매 mediator 매 routing/orchestration 의 own. GoF Mediator pattern 의 event-driven evolution. ## 매 핵심 ### 매 Mediator vs Observer - **Observer**: 매 1 subject → N observers, 매 unidirectional broadcast. - **Mediator**: 매 N senders ↔ N receivers, 매 hub 의 bidirectional routing. - **Event Bus**: 매 Mediator 의 generic implementation — 매 string-keyed event types. ### 매 동기 vs 비동기 - **Sync mediator**: 매 in-process method dispatch — 매 EventEmitter, MediatR. - **Async mediator**: 매 message queue 의 backed — 매 RabbitMQ, Kafka, Redis Streams. - **Hybrid**: 매 in-process sync + cross-service async (e.g., NestJS CQRS). ### 매 응용 1. UI components decoupling — chat rooms, form fields. 2. Microservices orchestration — saga coordinator. 3. CQRS command/query bus — MediatR. 4. Game event systems — unit death, achievement triggers. ## 💻 패턴 ### Typed Event Bus (TypeScript) ```typescript type EventMap = { 'user.signup': { userId: string; email: string }; 'order.placed': { orderId: string; total: number }; 'cache.invalidate': { key: string }; }; class EventMediator { private handlers = new Map void | Promise>>(); on(event: K, handler: (payload: M[K]) => void | Promise) { if (!this.handlers.has(event)) this.handlers.set(event, new Set()); this.handlers.get(event)!.add(handler); return () => this.handlers.get(event)!.delete(handler); } async emit(event: K, payload: M[K]) { const hs = this.handlers.get(event); if (!hs) return; await Promise.all([...hs].map(h => h(payload))); } } const bus = new EventMediator(); bus.on('user.signup', async ({ userId, email }) => { await sendWelcomeEmail(email); }); ``` ### Mediator with Request/Response (MediatR-style) ```typescript interface IRequest { __response?: TResponse } interface IHandler, TRes> { handle(req: TReq): Promise; } class Mediator { private handlers = new Map>(); register, R>(reqCtor: new (...a: any[]) => T, h: IHandler) { this.handlers.set(reqCtor, h); } async send(req: IRequest): Promise { const h = this.handlers.get(req.constructor); if (!h) throw new Error(`No handler for ${req.constructor.name}`); return h.handle(req); } } class GetUserQuery implements IRequest { constructor(public id: string) {} } class GetUserHandler implements IHandler { async handle(q: GetUserQuery) { return db.users.findById(q.id); } } ``` ### Saga Mediator (orchestrated) ```typescript class OrderSaga { constructor(private bus: EventMediator) { bus.on('order.created', this.onCreated.bind(this)); bus.on('payment.failed', this.onPaymentFailed.bind(this)); } async onCreated({ orderId }: { orderId: string }) { await this.bus.emit('payment.requested', { orderId }); } async onPaymentFailed({ orderId, reason }: any) { await this.bus.emit('order.cancelled', { orderId, reason }); await this.bus.emit('inventory.released', { orderId }); } } ``` ### Middleware Pipeline ```typescript type Middleware = (event: E, next: () => Promise) => Promise; class PipelineMediator { private middlewares: Middleware[] = []; use(m: Middleware) { this.middlewares.push(m); } async dispatch(event: E) { let i = -1; const run = async (idx: number): Promise => { if (idx <= i) throw new Error('next() called twice'); i = idx; const fn = this.middlewares[idx]; if (fn) await fn(event, () => run(idx + 1)); }; await run(0); } } // usage: logging, validation, retry, dead-letter ``` ### Cross-Service Mediator (Kafka) ```typescript import { Kafka } from 'kafkajs'; const kafka = new Kafka({ brokers: ['localhost:9092'] }); const producer = kafka.producer(); const consumer = kafka.consumer({ groupId: 'order-svc' }); await consumer.subscribe({ topic: 'orders', fromBeginning: false }); await consumer.run({ eachMessage: async ({ message }) => { const evt = JSON.parse(message.value!.toString()); await handler[evt.type]?.(evt.payload); }, }); await producer.send({ topic: 'orders', messages: [{ key: orderId, value: JSON.stringify({ type: 'order.placed', payload }) }], }); ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | 2-3 components 의 direct call | 매 mediator skip — 매 over-engineering | | 5+ components, fan-out broadcast | 매 in-process EventEmitter | | CQRS / clean architecture | 매 MediatR-style request bus | | Cross-service, durability | 매 Kafka / RabbitMQ | | Long-running workflow | 매 saga orchestrator (Temporal, Inngest) | **기본값**: 매 typed in-process EventEmitter (Node) 또는 MediatR (.NET) — 매 cross-service 의 Kafka. ## 🔗 Graph - 부모: [[Design-Patterns]] · [[Event-Driven-Architecture]] - 변형: [[Observer-Pattern]] · [[Pub-Sub]] - 응용: [[CQRS]] · [[Microservices]] - Adjacent: [[Event-Sourcing]] ## 🤖 LLM 활용 **언제**: 매 N×N coupling 의 emerge — 매 5+ components 매 mutual callbacks. CQRS / saga implementation. **언제 X**: 매 simple 2-component dependency — 매 direct injection 매 sufficient. Hot-path latency-critical (mediator dispatch overhead). ## ❌ 안티패턴 - **God Mediator**: 매 mediator 매 business logic 의 absorb — 매 anemic handlers, 매 mediator 의 1000+ lines. Mediator 의 routing only. - **Event soup**: 매 untyped string events — 매 'user_signup' vs 'userSignup' typo 의 silent failure. Typed map 의 use. - **Sync chain pretending async**: 매 emit() 매 await chain 의 200ms latency — 매 async queue 의 use. - **Lost events**: 매 in-memory bus 매 crash 의 lose — 매 durability 매 required 의 persistent queue. ## 🧪 검증 / 중복 - Verified (GoF Design Patterns; MediatR docs; NestJS CQRS). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — Event Mediator pattern 의 typed/saga/Kafka 의 5 patterns |