← Posts

GoF · Structural · Facade

Facade

하나 하려고 여러 개를 순서대로 불러야 할 때. 복잡한 하위 시스템 앞에 단순한 창구 하나를 세우는 구조 패턴.

목차
  1. 하나 하려고 여러 개를 순서대로 불러야 한다
  2. 퍼사드 = 복잡한 안쪽에 단순한 창구 하나
  3. 클라이언트가 안쪽을 다 알아야 하는 게 문제
  4. 창구 하나를 세운다
  5. 안쪽은 그대로 두고 앞에만 세운다
  6. 결합이 줄어든다
  7. 어댑터와 무엇이 다른가
  8. 실무: 서비스 계층과 SDK 진입점
  9. 정리

하나 하려고 여러 개를 순서대로 불러야 한다

주문 하나를 처리하려면 여러 서비스를 거쳐야 한다. 재고를 잡고, 결제를 하고, 배송을 걸고, 알림을 보낸다.

java
if (inventory.reserve(order)) {   // 재고
    payment.charge(order);        // 결제
    shipping.ship(order);         // 배송
    notification.notify(order);   // 알림
}

주문을 부르는 쪽이 이 네 서비스를 다 알아야 하고, 순서와 조건까지 챙겨야 한다. 재고를 결제보다 먼저 해야 한다는 것도, 재고 실패 시 멈춰야 한다는 것도 클라이언트의 몫이 된다. 주문하는 코드가 여러 군데면, 이 조율이 그만큼 복붙된다.

퍼사드 = 복잡한 안쪽에 단순한 창구 하나

퍼사드 패턴이 이걸 푼다.

여러 클래스로 이뤄진 복잡한 하위 시스템 앞에, 단순한 창구 하나를 세운다. 클라이언트는 그 창구만 상대한다.

“주문해줘” 한마디면 되게 만드는 것이다. 재고·결제·배송·알림을 누가 어떤 순서로 부르는지는 창구 뒤로 숨긴다.

클라이언트가 안쪽을 다 알아야 하는 게 문제

원래 코드의 문제는 클라이언트가 하위 시스템의 속을 다 안다는 것이다.

어느 서비스가 있는지, 무슨 순서로 부르는지, 언제 멈추는지 - 이 지식이 클라이언트에 박혀 있다. 그래서 하위 시스템이 조금만 바뀌어도(서비스 하나 추가, 순서 변경) 클라이언트를 다 찾아 고쳐야 한다. 쓰는 쪽과 안쪽이 너무 촘촘히 붙어 있다.

창구 하나를 세운다

조율을 통째로 맡는 클래스를 하나 만든다.

java
public class OrderFacade {

    private final InventoryService inventory;
    private final PaymentService payment;
    private final ShippingService shipping;
    private final NotificationService notification;
    // 생성자 생략

    public void placeOrder(Order order) {
        if (!inventory.reserve(order)) {
            throw new OutOfStockException();
        }
        payment.charge(order);
        shipping.ship(order);
        notification.notify(order);
    }
}

이제 부르는 쪽은 이 한 줄이다.

java
orderFacade.placeOrder(order);
diagramdiagram

네 서비스와 순서·조건이 전부 OrderFacade 안으로 들어갔다. 클라이언트는 “주문해줘”만 안다.

안쪽은 그대로 두고 앞에만 세운다

퍼사드는 하위 시스템을 바꾸지도, 완전히 가리지도 않는다. 서비스들은 그대로 있고, 그 앞에 편한 진입점을 하나 더했을 뿐이다.

그래서 특별한 사정이 있으면 클라이언트가 서비스를 직접 부를 수도 있다. 퍼사드는 “쉬운 길”을 열어주는 것이지, 유일한 길로 강제하는 게 아니다. 대부분은 창구로 다니고, 예외적인 경우만 안쪽으로 직접 들어간다.

결합이 줄어든다

값어치는 결합에서 나온다. 클라이언트가 의존하는 건 이제 OrderFacade 하나다. 네 서비스가 아니다.

배송 방식이 바뀌거나 알림 서비스가 갈리거나 순서가 조정돼도, 그 변화는 OrderFacade 안에서 흡수된다. 클라이언트는 여전히 placeOrder(order)만 부른다. 안쪽이 아무리 출렁여도 창구의 모양이 그대로면 밖은 안전하다.

어댑터와 무엇이 다른가

둘 다 무언가를 감싸지만 목적이 다르다.

  • 어댑터는 이미 정해진 인터페이스에 안 맞는 것을 맞춘다. 모양을 변환하는 게 일이고, 보통 대상은 하나다.
  • 퍼사드는 맞춰야 할 인터페이스가 따로 없다. 복잡한 여럿을 위해 새 단순 인터페이스를 만든다.

어댑터는 “기존 모양에 끼워 맞추기”, 퍼사드는 “복잡한 것 앞에 쉬운 창구 새로 내기”다.

실무: 서비스 계층과 SDK 진입점

실무에서 퍼사드는 서비스 계층으로 흔히 나타난다. Spring의 @Service가 여러 리포지토리와 도메인 로직을 조율해 “회원가입”, “주문” 같은 한 동작으로 묶어주면, 컨트롤러 입장에선 그게 퍼사드다. 라이브러리·SDK가 복잡한 내부를 감추고 몇 개의 진입 클래스만 노출하는 것도 퍼사드다.

주의도 있다. 퍼사드가 너무 많은 걸 떠안으면 그 자체가 비대해진다. 창구 하나가 수십 가지 일을 다 하면, 이번엔 그 창구가 문제가 된다. 퍼사드도 “한 가지 흐름을 위한 창구” 단위로 나눠야 한다.

정리

퍼사드란복잡한 하위 시스템 앞에 단순한 창구 하나를 세운다
문제클라이언트가 여러 클래스와 순서·조건을 다 알아야 한다
고침조율을 창구로 몰고, 클라이언트는 창구 메서드 하나만 부른다
성질안쪽을 바꾸거나 막지 않는다 - 쉬운 길을 하나 더할 뿐
효과결합이 줄어 안쪽이 바뀌어도 밖은 안전하다
어댑터와어댑터=기존 모양에 맞춤, 퍼사드=복잡한 여럿에 새 단순 창구

복잡한 안쪽을 클라이언트가 다 짊어지지 않게, 앞에 쉬운 창구 하나를 세우는 것 - 그게 퍼사드다.

관련 글