Pub-Sub(발행-구독) 패턴은 소프트웨어 아키텍처에서 메시징을 통해 컴포넌트 간의 통신을 관리하는 디자인 패턴입니다. 이 패턴은 두 가지 주요 역할인 발행자(Publisher)와 구독자(Subscriber)로 구성됩니다.
Event Borker
이벤트 브로커는 주로 이벤트 기반 아키텍처에서 사용되며, 특정 이벤트가 발생했을 때 이를 감지하고 해당 이벤트를 구독한 컴포넌트에 전달합니다.
특징
사용 예시
Message Broker
메시지 브로커는 메시지를 전송하고 수신하는 역할을 하며, 발행자와 구독자 간의 데이터 전송을 중개합니다. 메시지의 큐잉 및 라우팅을 포함한 복잡한 메시징 패턴을 지원합니다.
특징
사용 예시
구분 | 이벤트 브로커 | 메시지 브로커 |
---|---|---|
주요 기능 | 이벤트 감지 및 전달 | 메시지 전송, 큐잉, 라우팅 |
상태 관리 | 상태 비저장 | 상태 저장 |
비동기 처리 | 이벤트 발생 시 즉시 반응 | 메시지 큐에 저장 후 수신 처리 가능 |
사용 예 | UI 이벤트 처리, 상태 변화 알림 | 시스템 간 데이터 전송, 비즈니스 통합 |
1 ) 발행(Publish)
발행자는 특정 이벤트가 발생하면, 그 이벤트와 관련된 데이터를 메시지로 만들어 브로커에게 전달합니다. 이 메시지에는 이벤트의 타입이나 메시지의 내용이 포함됩니다.
2 ) 구독(Subscribe)
구독자는 자신이 관심 있는 특정 이벤트나 메시지 타입을 브로커에게 알려주고, 이를 구독합니다. 구독자는 특정 이벤트가 발행될 때 이를 수신할 준비를 합니다.
3 ) 중개(Distribute)
브로커는 발행자로부터 메시지를 수신하면, 그 메시지를 해당 이벤트를 구독한 모든 구독자에게 전달합니다. 이 과정에서 브로커는 메시지를 필터링하거나, 특정 조건에 맞는 구독자에게만 메시지를 전달할 수 있습니다.
4 )수신(Receive)
구독자는 브로커로부터 메시지를 수신하고, 이 메시지에 따라 필요한 동작을 수행합니다. 예를 들어, UI를 업데이트하거나, 데이터베이스를 수정하는 등의 작업을 할 수 있습니다.
EventBroker.ts
type EventCallback = (data: any) => void;
export class EventBroker {
// 이벤트 저장소: 이벤트 이름을 키로 하고, 해당 이벤트의 콜백 함수 배열을 값으로 가집니다.
private events: { [key: string]: EventCallback[] } = {};
// 구독: 지정된 이벤트에 대해 콜백 함수를 등록합니다.
subscribe(event: string, callback: EventCallback): void {
// 이벤트가 아직 등록되지 않은 경우, 빈 배열로 초기화
if (!this.events[event]) {
this.events[event] = [];
}
// 콜백 함수를 해당 이벤트의 콜백 배열에 추가
this.events[event].push(callback);
}
// 구독 해제: 지정된 이벤트에서 특정 콜백 함수를 제거합니다.
unsubscribe(event: string, callback: EventCallback): void {
// 해당 이벤트가 등록되어 있지 않은 경우, 아무 것도 하지 않음
if (!this.events[event]) return;
// 콜백 배열에서 특정 콜백 함수를 필터링하여 제거
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
// 발행: 지정된 이벤트에 대해 모든 등록된 콜백 함수를 호출합니다.
publish(event: string, data: any): void {
// 해당 이벤트에 등록된 콜백이 없는 경우, 아무 것도 하지 않음
if (!this.events[event]) return;
// 모든 콜백 함수에 데이터를 전달하여 실행
this.events[event].forEach(callback => callback(data));
}
}
Publisher.ts
import { EventBroker } from './EventBroker';
export class Publisher {
// EventBroker 인스턴스를 받아 초기화합니다.
constructor(private eventBroker: EventBroker) {}
// 이벤트를 발행합니다.
publish(event: string, data: any): void {
// EventBroker의 publish 메서드를 호출하여 이벤트를 발행
this.eventBroker.publish(event, data);
}
}
Subscriber.ts
import { EventBroker } from './EventBroker';
export class Subscriber {
// EventBroker 인스턴스를 받아 초기화합니다.
constructor(private eventBroker: EventBroker) {}
// 지정된 이벤트를 구독합니다.
subscribeToEvent(event: string, callback: (data: any) => void): void {
// EventBroker의 subscribe 메서드를 호출하여 이벤트를 구독
this.eventBroker.subscribe(event, callback);
}
// 지정된 이벤트의 구독을 해제합니다.
unsubscribeFromEvent(event: string, callback: (data: any) => void): void {
// EventBroker의 unsubscribe 메서드를 호출하여 이벤트의 구독 해제
this.eventBroker.unsubscribe(event, callback);
}
}
index.ts
import { EventBroker } from './src/EventBroker';
import { Publisher } from './src/Publisher';
import { Subscriber } from './src/Subscriber';
const eventBroker = new EventBroker();
const publisher = new Publisher(eventBroker);
const subscriber1 = new Subscriber(eventBroker);
const subscriber2 = new Subscriber(eventBroker);
const onMessageReceived1 = (data: any) => {
console.log(`Subscriber 1 received: ${data}`);
};
const onMessageReceived2 = (data: any) => {
console.log(`Subscriber 2 received: ${data}`);
};
// 구독자 1과 2가 "message" 이벤트에 구독
subscriber1.subscribeToEvent("message", onMessageReceived1);
subscriber2.subscribeToEvent("message", onMessageReceived2);
// Publisher가 메시지 발행
publisher.publish("message", "Hello, Subscribers!");
// 구독자 1이 구독 해제
subscriber1.unsubscribeFromEvent("message", onMessageReceived1);
// 다시 메시지 발행
publisher.publish("message", "Hello again!");
TodoList.ts
import { EventBroker } from "./EventBroker";
import { Publisher } from "./Publisher";
// Publiser 역할
// TodoList 클래스는 작업(Task)을 관리하고, 작업의 추가 및 제거 시 이벤트를 발행합니다.
export class TodoList extends Publisher {
tasks: string[];
constructor(eventBroker: EventBroker) {
// 부모 클래스인 Publisher를 초기화합니다.
super(eventBroker);
this.tasks = [];
}
addTask(task: string) {
this.tasks.push(task);
// 작업 추가 후 현재 작업 목록을 발행
this.publish("task", this.tasks);
}
removeTask(task: string) {
this.tasks = this.tasks.filter((t) => t !== task);
// 작업 제거 후 현재 작업 목록을 발행
this.publish("task", this.tasks);
}
}
RenderUI.ts
import { EventBroker } from "./EventBroker";
import { Subscriber } from "./Subscriber";
import { TodoList } from "./TodoList";
// Subscriber의 역할
// RenderUI 클래스는 TodoList의 작업 목록을 UI에 렌더링하는 역할을 합니다.
export class RenderUI extends Subscriber {
todoList: TodoList;
constructor(eventBroker: EventBroker, todoList: TodoList) {
// 부모 클래스인 Subscriber를 초기화합니다.
super(eventBroker);
this.todoList = todoList;
// "task" 이벤트를 구독하고, 해당 이벤트 발생 시 render 메서드를 호출합니다.
this.subscribeToEvent("task", (taskList) => this.render(taskList));
}
// TodoList를 UI에 렌더링합니다.
render(taskList: string[]) {
const taskListElement = document.getElementById("taskList");
taskListElement!.innerHTML = "";
taskList.forEach((task) => {
const li = document.createElement("li");
li.textContent = task;
const removeButton = document.createElement("button");
removeButton.textContent = "제거";
removeButton.addEventListener("click", () => {
this.todoList.removeTask(task);
});
li.appendChild(removeButton);
taskListElement!.appendChild(li);
});
const taskCountElement = document.getElementById("taskCount");
taskCountElement!.textContent = taskList.length.toString();
}
}
index.ts
import { EventBroker } from "./src/EventBroker";
import { RenderUI } from "./src/RenderUI";
import { TodoList } from "./src/TodoList";
import "./styles.css";
const eventBroker = new EventBroker();
const todoList = new TodoList(eventBroker);
new RenderUI(eventBroker, todoList);
document.getElementById("addTaskButton")!.addEventListener("click", () => {
const taskInput = document.getElementById("taskInput") as HTMLInputElement;
const task = taskInput.value.trim();
if (task) {
todoList.addTask(task);
taskInput.value = "";
}
});
1 ) 이벤트 브로커 생성
EventBroker
클래스는 이벤트를 관리합니다. 이벤트를 구독하고 발행하는 기능을 제공합니다.2 ) TodoList 생성
TodoList
클래스는 작업의 추가 및 제거를 담당합니다. 이 클래스는 Publisher
클래스를 상속받아, 작업이 추가되거나 제거될 때마다 해당 이벤트를 발행합니다.addTask(task: string)
메서드는 새로운 작업을 추가하고, 작업 목록을 발행합니다.removeTask(task: string)
메서드는 작업을 제거하고, 업데이트된 작업 목록을 발행합니다.3 ) RenderUI 생성
RenderUI
클래스는 Subscriber
클래스를 상속받아, 이벤트를 구독하여 UI를 업데이트합니다.TodoList
인스턴스를 받아, "task" 이벤트를 구독합니다. 이 이벤트가 발생하면 render
메서드를 호출하여 UI를 업데이트합니다.4 ) UI 렌더링
render(taskList: string[])
메서드는 UI를 업데이트합니다. 작업 목록을 HTML 요소에 렌더링하고, 각 작업마다 "제거" 버튼을 생성합니다.TodoList
에서 제거됩니다.5 ) 이벤트 흐름
TodoList
의 addTask
메서드가 호출됩니다. 이 메서드는 작업을 추가하고, 현재 작업 목록을 "task" 이벤트로 발행합니다.RenderUI
는 "task" 이벤트를 구독하고 있으므로, 이 이벤트가 발생하면 render
메서드가 호출되어 UI가 업데이트됩니다.TodoList
의 removeTask
메서드가 호출되고, 작업 목록이 업데이트된 후 다시 "task" 이벤트가 발행되어 UI가 다시 렌더링됩니다.정리
EventBroker: 이벤트를 관리하고, 구독 및 발행 기능 제공합니다.
Publisher: 이벤트를 발행하는 기능을 가진 클래스, TodoList
에서 사용됩니다.
Subscriber: 이벤트를 구독하는 기능을 가진 클래스, RenderUI
에서 사용됩니다.
TodoList: 작업을 관리하고, 추가 및 제거 시 이벤트를 발행합니다.
RenderUI: UI를 업데이트하며, TodoList
의 작업 목록 변화에 반응합니다.
아래의 이미지와 같은 형태로 동작한다고 볼 수 있습니다.
Pub-Sub 패턴은 소프트웨어 디자인 패턴 중 하나로, 객체 간의 느슨한 결합을 통해 통신을 관리하는 방법입니다. 이 패턴에서는 Publisher
가 이벤트를 발행하고, SubScriber
가 특정 이벤트를 구독하여 해당 이벤트가 발생할 때 알림을 받습니다. Publisher와 Subscriber 간의 직접적인 연결이 없기 때문에, 시스템의 유연성과 확장성이 높아지고, 새로운 기능을 추가하거나 수정할 때 다른 구성 요소에 미치는 영향을 줄일 수 있습니다. 이 패턴은 주로 이벤트 기반 아키텍처나 메시지 전송 시스템에서 사용됩니다.