Facade Pattern
복잡한 서브시스템 앞에 단순한 진입점을 둬 클라이언트의 결합도를 낮추는 패턴. Adapter는 모양 변환, Facade는 단순화가 목적.
난이도 중급 · 선행 Adapter
한 줄 요약
여러 모듈을 호출해야 하는 복잡한 절차를 한 객체의 단순한 메서드 뒤로 숨긴다. 클라이언트는 그 한 메서드만 알면 된다.
어떤 문제를 푸는가
쇼핑몰에서 주문 한 건을 처리하려면 여러 서비스를 차례로 호출해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void placeOrder(const Cart& cart, const User& user) {
if (!inventory.reserve(cart.items)) throw OutOfStock();
if (!payment.charge(user, cart.total())) {
inventory.release(cart.items); // 결제 실패 시 재고 복구
throw PaymentFailed();
}
try {
shipping.schedule(cart, user.address);
} catch (...) {
payment.refund(user, cart.total());
inventory.release(cart.items);
throw;
}
notification.send(user, "주문 완료");
analytics.record("order", cart);
}
문제:
- 클라이언트가 5개 서비스를 모두 알고, 호출 순서·실패 시 보상까지 책임진다.
- 같은 주문 처리를 다른 곳(모바일 API, 관리자 콘솔)에서도 호출하면 그곳에도 이 코드를 복제하게 된다.
- 서브시스템 하나의 시그니처가 바뀌면 모든 호출 사이트가 깨진다.
패턴 적용 후
복잡한 절차를 OrderService(Facade) 한 곳에 모아 단순한 진입점만 노출한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
#include <stdexcept>
#include <string>
class InventoryService {
public:
bool reserve(const std::string& item) { std::cout << item << " 재고 예약\n"; return true; }
void release(const std::string& item) { std::cout << item << " 재고 복구\n"; }
};
class PaymentService {
public:
bool charge(const std::string& user, int amount) {
std::cout << user << "에게 " << amount << "원 결제\n"; return true;
}
void refund(const std::string& user, int amount) {
std::cout << user << "에게 " << amount << "원 환불\n";
}
};
class ShippingService {
public:
void schedule(const std::string& item, const std::string& addr) {
std::cout << item << " → " << addr << " 배송 예약\n";
}
};
class NotificationService {
public:
void send(const std::string& user, const std::string& msg) {
std::cout << "[알림→" << user << "] " << msg << "\n";
}
};
// Facade: 복잡한 협력을 한 메서드로 노출
class OrderService {
InventoryService inventory;
PaymentService payment;
ShippingService shipping;
NotificationService notification;
public:
void placeOrder(const std::string& user, const std::string& item,
int amount, const std::string& addr) {
if (!inventory.reserve(item)) throw std::runtime_error("재고 없음");
if (!payment.charge(user, amount)) {
inventory.release(item);
throw std::runtime_error("결제 실패");
}
try {
shipping.schedule(item, addr);
} catch (...) {
payment.refund(user, amount);
inventory.release(item);
throw;
}
notification.send(user, "주문 완료");
}
};
int main() {
OrderService service;
service.placeOrder("alice", "노트북", 1500000, "서울시 종로구");
}
달라진 점:
- 클라이언트는
OrderService::placeOrder하나만 안다. - 호출 순서·보상 로직이 한 곳에 모인다.
- 서브시스템이 바뀌어도 Facade만 수정. 클라이언트 무손상.
구조
1
2
3
4
5
Client ──▶ Facade
│
├──▶ SubsystemA
├──▶ SubsystemB
└──▶ SubsystemC
- Facade: 단순한 진입점 (
OrderService) - Subsystem: 실제 일을 하는 모듈들 (
InventoryService,PaymentService, …)
핵심은 Facade가 서브시스템의 한 가지 사용 방식을 노출한다는 점이다. 서브시스템을 직접 쓰는 것도 여전히 가능하다 — Facade는 길을 막는 게 아니라 지름길을 제공한다.
실전 사례
- jQuery
$(selector): DOM API 복잡성(브라우저 호환성 포함)을 단순한 한 함수 뒤로 숨김. - Spring
JdbcTemplate: JDBC의 5단계(Connection → Statement → ResultSet → 닫기 → 예외 변환)를 한 메서드로. - HikariCP
getConnection(): 커넥션 풀의 복잡한 자원 관리를 단순한 호출로. - Compiler 프론트엔드:
compile(source)한 함수 뒤에 lexer/parser/typecheck/codegen이 숨음. - payment SDK: 토스/스트라이프의 단일
Charge.create(...)가 내부적으로 카드 검증·3DS·라우팅을 다 처리.
Adapter / Mediator와의 차이
| Facade | Adapter | Mediator | |
|---|---|---|---|
| 의도 | 복잡한 서브시스템 단순화 | 인터페이스 모양 변환 | 객체 간 통신 중재 |
| 새 인터페이스인가 | 그렇다 (새 진입점) | 아니다 (기존 Target 인터페이스에 맞춤) | 그렇다 (객체들이 Mediator에만 의존) |
| 관계 방향 | 외부 → 시스템 (단방향) | 외부 → 시스템 | 시스템 내부 객체끼리 |
Facade는 “여러 모듈 → 단순화”이고 Adapter는 “한 모듈 → 모양 변환”. 헷갈리면 의도를 보면 된다.
안티패턴 / 주의
- Facade가 서브시스템 메서드를 단순 forwarding만 한다: 가치 없음. 단순화·정책 결정·보상 로직처럼 의미 있는 일을 해야 Facade.
- Facade가 비대해진다 (God Object): 단일 Facade가 50개 메서드를 갖는다면 책임 분리가 잘못된 신호. 도메인별로 쪼개라 (
OrderService,RefundService, …). - Facade로 서브시스템 직접 접근을 막지 마라: 특수한 경우엔 서브시스템을 직접 써야 한다. 보호 프록시가 아니다.
- 순환 의존: Facade가 서브시스템을 호출하는데, 서브시스템이 다시 Facade를 호출하면 위험. 단방향을 유지하라.
스스로 점검
1. Facade가 서브시스템 메서드를 단순 forwarding만 하면 어떤 점이 문제?
답
가치가 없다. 단순화·정책 결정·보상 로직(트랜잭션 롤백 같은) 같은 의미 있는 일을 해야 Facade. 메서드 1:1 매핑이면 한 단계 더 들어가는 비용만 생긴다.
2. OrderService Facade가 메서드 50개로 부풀었다면?
답
God Object 신호. 도메인 단위로 쪼개야 한다 (RefundService, ShippingService, InventoryService 등). 단일 Facade가 모든 걸 떠안으면 그 자체가 결합점.
3. Facade를 두면 서브시스템 직접 호출을 막아야 하나?
답
아니다. Facade는 “지름길”을 제공할 뿐, “길을 막는 보안 장치”가 아니다. 특수한 경우 서브시스템 직접 호출도 가능해야 한다. 그건 보호 프록시의 역할.