Facade 패턴: 여러 서브시스템을 하나의 진입점으로 묶는 법

Facade 패턴이 인증·재고·결제 같은 서브시스템 호출 순서와 예외 처리를 단일 인터페이스로 캡슐화하는 원리를 정리한다.

2026-08-13 · 최초 발행 2025-10-14

서비스가 늘어날수록 호출 순서를 아는 사람이 줄어든다

주문 하나를 처리하려면 인증하고, 재고를 확인하고, 결제를 진행해야 한다. 이 순서와 각 단계의 실패 처리를 클라이언트마다 따로 알고 있어야 한다면, 서브시스템이 하나만 바뀌어도 호출하는 쪽 전부를 고쳐야 하는 상황이 온다. Facade 패턴은 이런 복잡한 서브시스템 집합에 대해 통일된 단일 인터페이스를 제공하는 구조적 패턴이다. 클라이언트는 퍼사드(Facade)에만 요청하고, 퍼사드가 내부 모듈 호출 순서와 데이터 변환, 예외 처리 로직을 캡슐화한다.

비슷한 패턴들과 목적이 다르다. Adapter가 인터페이스 호환을 위한 것이고 Proxy가 접근 제어나 지연 로딩을 위한 것이라면, Facade는 단순화와 오케스트레이션에 집중한다.

퍼사드가 떠맡는 책임들

퍼사드는 서브시스템에 대한 대표 인터페이스 역할을 하며, 입력·출력 스키마와 사전·사후 조건을 계약으로 명문화한다. 이 경계를 통해 외부 의존성과 내부 변경의 파급 효과를 차단하는 것이 첫 번째 역할이다.

두 번째는 오케스트레이션과 시퀀싱이다. 여러 모듈을 호출하는 순서, 재시도 정책, 타임아웃, 모듈 간 의존 관계를 퍼사드 내부에서 일관되게 관리하고, 공통 검증·변환·캐싱·트랜잭션 경계 설정을 한곳에 모아 재사용성과 일관성을 높인다.

세 번째는 예외를 표준 도메인 오류로 번역하는 역할이다. 서로 다른 서브시스템이 각자의 예외를 던지더라도 퍼사드를 거치면 클라이언트가 보는 오류는 일관된 형태가 되고, 부분 실패가 나면 보상 로직(compensation)이나 폴백 전략으로 회복력을 확보할 수 있다.

마지막으로 퍼사드 인터페이스를 기준으로 DIP를 적용하면 서브시스템을 교체하기 쉬워지고, 퍼사드 단위로 모킹·스텁 테스트를 할 수 있어 상위 계층 테스트도 단순해진다.

요청부터 보상 처리까지의 흐름

클라이언트 요청, 도메인 명령, 컨텍스트 파라미터가 입력으로 들어오면 전처리 검증 → 다중 서브시스템 호출 오케스트레이션 → 예외 번역·보상 처리 → 결과 집계 순으로 처리되고, 표준화된 응답 DTO와 오류 코드·메시지, 코릴레이션 ID 같은 관측 가능성 메타데이터가 출력된다.

Payment ServiceInventory ServiceAuth ServiceFacadeClientPayment ServiceInventory ServiceAuth ServiceFacadeClient보상 처리alt[결제 실패][결제 성공]alt[재고 부족][재고 확보]alt[인증 실패][인증 성공]placeOrder(userId, sku, qty)authenticate(userId)AuthErrorError{code=AUTH_FAILED}AuthOKreserve(sku, qty)OutOfStockError{code=OUT_OF_STOCK}Reservedpay(userId, amount)PaymentErrorrelease(sku, qty)Error{code=PAYMENT_FAILED}PaidOrderResult{success=true}

결제가 실패하면 이미 예약해둔 재고를 되돌리는 release 호출이 보상 처리에 해당한다 — 퍼사드가 이 흐름을 알고 있기 때문에 클라이언트는 실패 케이스마다 무엇을 되돌려야 하는지 신경 쓸 필요가 없다.

주문 처리로 보는 퍼사드 오케스트레이션

Java 17, 표준 JDK만으로 단일 파일 컴파일·실행이 가능하다.

// 파일명: Main.java (Java 17)
import java.util.*;

public class Main {
    // 도메인 결과
    static class OrderResult {
        final boolean success;
        final String message;
        OrderResult(boolean success, String message) { this.success = success; this.message = message; }
        @Override public String toString() { return "OrderResult{success=" + success + ", message='" + message + "'}"; }
    }

    // 서브시스템 인터페이스
    interface AuthService { boolean authenticate(String userId); }
    interface InventoryService { boolean reserve(String sku, int qty); void release(String sku, int qty); }
    interface PaymentService { boolean pay(String userId, int amount); }

    // 서브시스템 구현 (데모용)
    static class SimpleAuth implements AuthService {
        public boolean authenticate(String userId) { return userId != null && !userId.isBlank(); }
    }
    static class SimpleInventory implements InventoryService {
        private final Map<String,Integer> stock = new HashMap<>();
        SimpleInventory() { stock.put("SKU-1", 10); stock.put("SKU-2", 0); }
        public boolean reserve(String sku, int qty) {
            int remain = stock.getOrDefault(sku, 0);
            if (remain >= qty) { stock.put(sku, remain - qty); return true; }
            return false;
        }
        public void release(String sku, int qty) { stock.put(sku, stock.getOrDefault(sku, 0) + qty); }
    }
    static class SimplePayment implements PaymentService {
        public boolean pay(String userId, int amount) { return amount <= 100_000; } // 금액 한도 체크
    }

    // Facade
    static class OrderFacade {
        private final AuthService auth;
        private final InventoryService inventory;
        private final PaymentService payment;

        OrderFacade(AuthService auth, InventoryService inventory, PaymentService payment) {
            this.auth = auth; this.inventory = inventory; this.payment = payment;
        }

        public OrderResult placeOrder(String userId, String sku, int qty, int unitPrice) {
            if (!auth.authenticate(userId)) return new OrderResult(false, "AUTH_FAILED");
            if (!inventory.reserve(sku, qty)) return new OrderResult(false, "OUT_OF_STOCK");
            int amount = qty * unitPrice;
            if (!payment.pay(userId, amount)) {
                inventory.release(sku, qty); // 보상 처리
                return new OrderResult(false, "PAYMENT_FAILED");
            }
            return new OrderResult(true, "ORDER_ACCEPTED");
        }
    }

    public static void main(String[] args) {
        OrderFacade facade = new OrderFacade(new SimpleAuth(), new SimpleInventory(), new SimplePayment());
        System.out.println(facade.placeOrder("alice", "SKU-1", 2, 30000)); // 성공 케이스
        System.out.println(facade.placeOrder("", "SKU-1", 2, 30000));      // 인증 실패
        System.out.println(facade.placeOrder("bob", "SKU-2", 1, 10000));   // 재고 부족
        System.out.println(facade.placeOrder("carol", "SKU-1", 5, 30000)); // 결제 실패 + 보상
    }
}

퍼사드를 두는 위치들

레거시나 외부 시스템을 통합할 때는 서로 다른 프로토콜과 데이터 포맷을 단일 API로 감싸 업스트림 변경의 영향을 줄이고, 마이그레이션 기간 동안 호환성 경계로 활용할 수 있다. 마이크로서비스 경계에서는 여러 서비스 호출을 BFF나 컴포지트 엔드포인트로 모아 타임아웃·재시도·서킷브레이커 정책을 한곳에서 적용한다. 주문·정산·가입 같은 고레벨 유스케이스는 퍼사드로 모델링해 트랜잭션 경계와 보상 트랜잭션 흐름을 일관되게 관리할 수 있고, 플랫폼 SDK나 클라이언트 라이브러리는 내부 복잡성을 퍼사드로 감싸 파트너나 프런트엔드 코드를 단순화하면서 버전 관리·폐기 계획을 체계화할 수 있다.

직접 통합과 비교하면

구분 성능 확장성 일관성 안정성 운영 편의
직접 통합 호출 분산으로 네트워크 왕복 증가 가능성, 최적화 부재 호출 패턴 중복으로 병목 파악 곤란 각 호출처별 검증/변환 불일치 발생 예외 처리 편차로 장애 전파 확대 호출자마다 설정 관리로 운영 복잡도 증가
Facade 적용 캐싱/배치/집계로 왕복 수 감소, 공통 최적화 용이 스로틀링/백프레셔를 경계에서 통제 표준 계약/스키마로 결과 일관성 확보 예외 번역/보상 처리로 실패 격리 관측/로깅/버전 관리 일원화로 운영 단순화

직접 통합 대비 클라이언트 호출 코드량은 2040% 줄어들 수 있고, 집계·캐싱을 전제로 하면 네트워크 왕복 횟수도 1530% 줄어들 수 있다. 예외 번역과 격리를 적용하면 장애 전파율이 30% 이상 낮아질 것으로 기대되고, 테스트 케이스 수도 20% 내외로 단순해진다. 정량적인 수치 외에도 변경이 격리되고 팀 간 계약이 명확해지면서 개발 속도와 협업 효율이 올라가고, 관측 가능성이 좋아지면서 문제를 진단하는 시간이 줄어드는 효과가 있다.

얇게 유지하기 위한 원칙

퍼사드 인터페이스를 중심에 두고 안정적인 버전 정책을 세우며, 입력·출력 DTO는 불변으로 유지하고 스키마 호환성을 관리하는 것이 기본이다. 타임아웃·재시도·서킷브레이커·레이트리밋·코릴레이션 ID 로깅 같은 정책은 한곳으로 모으고, 얇은 퍼사드 원칙을 지키면서 도메인 유스케이스 단위로 분리하는 편이 좋다. 성공·실패율, 지연, 다운스트림별 오류 같은 관측 지표는 기본으로 내장해두는 게 낫다.

다만 기능이 계속 붙으면 퍼사드가 비대해져 갓 오브젝트가 될 위험이 있다 — 이럴 땐 모듈을 분리하고 하위 퍼사드로 위임하는 구조를 검토해야 한다. 레이어가 하나 늘어나는 만큼 초기 지연과 복잡도가 늘 수 있으니 핫패스 경로에는 캐싱·비동기화·배치 처리로 상쇄하는 편이 좋고, 내부를 세밀하게 제어해야 하는 고급 클라이언트를 위해서는 고급 API와 퍼사드 API를 병행 제공하는 전략도 고려할 만하다.

디자인 패턴구조 패턴Facade 패턴오케스트레이션소프트웨어 아키텍처