Files
2nd/10_Wiki/Topics/Architecture/Event Mediator.md
T
koriweb d8a80f6272 chore(wiki): dangling 링크 canonical 정규화 (768파일/1200건)
이름만 다른(표기 변형) [[위키링크]]를 대상 문서의 canonical 제목으로 치환해
끊겼던 1,200개 링크를 연결. 제목/파일명 정규화 일치만 적용하고 별칭 매칭은
과병합 위험으로 제외(애매성 가드). 원본은 _link_reconcile_backup/ 에 백업.
도구: Datacollect/scripts/link_reconcile_apply.mjs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:24:15 +09:00

6.7 KiB
Raw Blame History

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-event-mediator Event Mediator 10_Wiki/Topics verified self
Mediator Pattern
Event Bus
none A 0.9 applied
architecture
design-pattern
event-driven
decoupling
2026-05-10 pending
language framework
typescript 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)

type EventMap = {
  'user.signup': { userId: string; email: string };
  'order.placed': { orderId: string; total: number };
  'cache.invalidate': { key: string };
};

class EventMediator<M> {
  private handlers = new Map<keyof M, Set<(p: any) => void | Promise<void>>>();

  on<K extends keyof M>(event: K, handler: (payload: M[K]) => void | Promise<void>) {
    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<K extends keyof M>(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<EventMap>();
bus.on('user.signup', async ({ userId, email }) => {
  await sendWelcomeEmail(email);
});

Mediator with Request/Response (MediatR-style)

interface IRequest<TResponse> { __response?: TResponse }
interface IHandler<TReq extends IRequest<TRes>, TRes> {
  handle(req: TReq): Promise<TRes>;
}

class Mediator {
  private handlers = new Map<Function, IHandler<any, any>>();
  register<T extends IRequest<R>, R>(reqCtor: new (...a: any[]) => T, h: IHandler<T, R>) {
    this.handlers.set(reqCtor, h);
  }
  async send<R>(req: IRequest<R>): Promise<R> {
    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<User> { constructor(public id: string) {} }
class GetUserHandler implements IHandler<GetUserQuery, User> {
  async handle(q: GetUserQuery) { return db.users.findById(q.id); }
}

Saga Mediator (orchestrated)

class OrderSaga {
  constructor(private bus: EventMediator<OrderEvents>) {
    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

type Middleware<E> = (event: E, next: () => Promise<void>) => Promise<void>;

class PipelineMediator<E> {
  private middlewares: Middleware<E>[] = [];
  use(m: Middleware<E>) { this.middlewares.push(m); }
  async dispatch(event: E) {
    let i = -1;
    const run = async (idx: number): Promise<void> => {
      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)

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

🤖 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