icon

메티의 블로그

디자인패턴: Mediator 패턴 & Middleware 패턴
디자인패턴: Mediator 패턴 & Middleware 패턴

디자인패턴: Mediator 패턴 & Middleware 패턴

summary
이 글에서는 Mediator 패턴과 Middleware 패턴에 대해 설명하고 있습니다. Mediator 패턴은 객체 간의 직접 통신을 제한하고 중재자를 통해 상호작용을 관리하는 방식이며, Middleware 패턴은 express.js에서 요청과 응답 사이에 여러 동작을 추가하는 역할을 합니다.
Tags
Design Pattern
view_count
날짜
Aug 13, 2025
상태
공개

Mediator 패턴

Mediator 패턴이란?

Mediator 패턴(중재자 패턴)은 GoF 책에서도 소개된 행동 디자인 패턴으로, 상호작용이 여러 객체에서 일어날 수 있는 경우 중앙의 중재자가 여러 상호작용들을 관리하도록 하는 패턴입니다. 이때, 객체끼리의 직접 통신을 제한하고 중재자 객체를 통해서만 협력하도록 하는 것이 핵심 아이디어 입니다.

왜 사용하나요?

중재자가 없는 경우의 각 상호작용
중재자가 없는 경우의 각 상호작용
중재자가 상호작용을 관리후 각 컴포넌트에 전달
중재자가 상호작용을 관리후 각 컴포넌트에 전달
중재자 패턴은 여러 객체가 여러 상호작용을 하는 유저 인터페이스 구현에서 많이 사용하며, 각 객체들 끼리의 결합도를 낮추어 각 객체들의 재사용성과 상태 관리의 복잡성을 완화 할 수 있습니다.

활용

interface Mediator { sendMessage(sender: User, message: string): void; } class ChatRoom implements Mediator { private users: User[] = []; register(user: User) { this.users.push(user); } sendMessage(sender: User, message: string) { this.users.forEach(user => { if (user !== sender) { user.receive(message, sender.name); } }); } } class User { constructor(public name: string, private mediator: Mediator) {} send(message: string) { console.log(`[${this.name}]: ${message}`); this.mediator.sendMessage(this, message); } receive(message: string, from: string) { console.log(`[${from} -> ${this.name}]: ${message}`); } } // 사용 const chatRoom = new ChatRoom(); const alice = new User("Alice", chatRoom); const bob = new User("Bob", chatRoom); chatRoom.register(alice); chatRoom.register(bob); alice.send("안녕, Bob!"); bob.send("안녕, Alice!");
모든 유저는 서로를 참조하지 않고, 중재자 객체인 chatRoom 을 통해서만 채팅을 합니다.

단점

중재자 객체가 God Object 가 될 수 있습니다.

신 객체, 전지적 객체 라고도 부르는 이 객체는 단일 책임 원칙에 반대되는 방식으로 하나의 객체가 많은 문제를 책임지고 있습니다. 그러므로, 중재자 객체 자체의 테스트나 디버깅이 힘들어 질 수 있습니다. 그리고, 중재자에서 성능 병목이 생길 수도 있습니다.


Middleware 패턴(Chain of Responsibility 패턴)

여는 글

디자인패턴 웹 사이트에서 같이 언급된 미들웨어 패턴은 디자인 패턴이라고 하기 보단 express.js 의 핵심 개념 중 하나로 보는게 맞는 것 같습니다. express.js 에서는 요청과 응답의 중간에 middleware 라는 중간자 역할을 하는 함수가 여러 동작을 부가적으로 실행합니다. 이 패턴은 GoF 에서 소개된 행위 패턴인 책임 연쇄 패턴에서 왔다 볼 수 있습니다. 그래서 다음과 같이 하나의 요청이 여러 검사 함수들(핸들러)을 거쳐 새로운 객체를 리턴하는 방식으로 구현합니다.

왜 사용하나요?

책임 연쇄 패턴은 하나의 기능이 여러 필터를 통해 응답이 달라져야하는 경우에 사용합니다. 예를 들어 어떤 요청이 서비스에 인증이 되어있고, 권한이 있으며, 데이터가 검증된 상태가 있어야하는 상황이라면, 이 모든 것을 한번에 한 요청에서 검사하는 것이 아닌 인증, 권한 부여, 검증 핸들러가 따로 책임을 나누어 관리하도록 합니다. 이를 통해 책임을 분리하고, 각 책임들을 재사용하기 용이하기 위해 만들어진 패턴 입니다.
그리고 express.js 에서 이를 middleware 라는 이름을 붙여 책임 연쇄 패턴을 구현한 것이라고 보면 좋을 것 같습니다. (약간의 특화된 변형이 있지만, 큰 동기는 책임 연쇄 패턴에서 파생된 것 같네요.)

예시

아래 첫번째 예시는 아주 간단한 미들웨어 사용 방식으로 next() 함수를 통해 다음 미들웨어로 넘어가게 하거나, next('/route') 등으로 라우팅을 할 수 있습니다.
import express from "express"; const app = express(); // 1️⃣ 모든 요청에 대해 로깅하는 미들웨어 app.use((req, res, next) => { console.log(`[LOG] ${req.method} ${req.url}`); next(); // 다음 미들웨어로 이동 }); // 2️⃣ 모든 요청에 대해 인증 여부를 확인하는 미들웨어 app.use((req, res, next) => { if (req.headers.authorization === "Bearer valid-token") { next(); // 인증 성공 → 다음 단계로 } else { res.status(401).send("Unauthorized"); // 인증 실패 → 흐름 종료 } }); // 3️⃣ 실제 라우트 핸들러 app.get("/", (req, res) => { res.send("Hello, Express Middleware!"); }); // 4️⃣ 서버 실행 app.listen(3000, () => { console.log("Server running at http://localhost:3000"); });
express.js 내 미들웨어 간단 예시
아래 예시는 미들웨어 로직을 재사용 가능하게 만들어 적용하는 예시입니다.
export function logger(req, res, next) { console.log(`[LOG] ${req.method} ${req.url}`); next(); } export function requireAuth(req, res, next) { if (req.headers.authorization === "Bearer valid-token") { next(); } else { res.status(401).json({ error: "Unauthorized" }); } } export function requireRole(role) { return (req, res, next) => { if (req.headers["x-role"] === role) { next(); } else { res.status(403).json({ error: "Forbidden" }); } }; }
import express from "express"; import { logger } from "../middlewares/logger.js"; import { requireAuth } from "../middlewares/auth.js"; import { requireRole } from "../middlewares/role.js"; const router = express.Router(); // 이 라우트 그룹에 필요한 미들웨어들을 조합 router.use(logger); router.use(requireAuth); router.use(requireRole("admin")); // GET /admin/dashboard router.get("/dashboard", (req, res) => { res.json({ message: "관리자 대시보드" }); }); // GET /admin/users router.get("/users", (req, res) => { res.json({ message: "관리자 사용자 목록" }); }); export default router;
import express from "express"; import adminRoutes from "./routes/admin.js"; const app = express(); app.use(express.json()); // /admin 하위 라우트는 전부 adminRoutes 미들웨어 체인 적용 app.use("/admin", adminRoutes); app.listen(3000, () => { console.log("Server running at http://localhost:3000"); });