Spring 기초/Spring 기초 지식

가볍게 알아보는 디자인 패턴 - 퍼사드 패턴(Facade Pattern)

Stark97 2023. 12. 11. 10:32
반응형
 
 

퍼사드 패턴에 대해 가볍게 알아보자

 

1. Facade Pattern이란?

1-1. 퍼사드 패턴이란

  • 퍼사드(Facade) 패턴은 복잡한 시스템을 쉽게 사용할 수 있도록 도와주는 디자인 패턴이다. 예를 들어, 컴퓨터를 켜면서 하는 일들을 생각해 보자. 전원 버튼을 누르는 것만으로 운영체제가 시작되고, 여러 프로그램이 실행되고, 네트워크에 연결되는 일들이 일어난다. 이 모든 복잡한 과정들을 단 한 번의 버튼 클릭으로 간단하게 처리할 수 있는 것처럼, 퍼사드 패턴은 프로그래밍에서도 비슷한 역할을 한다.

 

1-2. 퍼사드 패턴의 역할

  • 퍼사드 패턴은 복잡한 서브시스템의 기능들을 간단한 인터페이스로 제공함으로써 클라이언트 측 개발자가 내부 로직의 복잡성을 몰라도 되게 한다. 이 패턴은 여러 복잡한 클래스들과의 직접적인 상호작용을 줄이고, 한 개의 통합된 인터페이스를 통해 서브시스템과 상호작용하도록 도와준다.

  • 예를 들어, 자동차 엔진 시스템이 복잡한데, 운전자는 엔진의 모든 세부 사항을 알 필요 없이 단지 가속 페달과 브레이크 페달을 사용하면 된다. 여기서 페달들은 운전자에게 복잡한 엔진 시스템의 간단한 인터페이스를 제공하는 퍼사드와 같은 역할을 한다. 개발자 입장에서 퍼사드 패턴은 복잡한 시스템을 쉽게 사용할 수 있도록 해줘서, 개발 과정을 단순화하고 오류 가능성을 줄여주는 장점이 있다.

 

1-3. 퍼사드 패턴의 예

  • 예를 들어, 온라인 쇼핑몰 시스템을 생각해 보자. 이 시스템에는 사용자 인증, 제품 검색, 주문 처리, 결제 처리 등 여러 서브시스템이 있을 것이다. 만약 이 모든 서브시스템을 직접 다루려면 굉장히 복잡하다. 그래서 여기서 Facade pattern (퍼사드 패턴)을 사용하면, 이 모든 복잡한 과정들을 간단한 인터페이스 뒤에 숨길 수 있다. 예를 들어 '주문하기'라는 하나의 메서드로 사용자 인증부터 결제까지 모든 과정을 간단하게 처리할 수 있게 되는 것이다.

 

2. 실생활 예시: 홈 시어터(집에 구축한 영화관) 시스템

2-1. 홈 시어터 시스템 (Facade 실생활 예시)

  • 실생활 예시로는 홈 시어터 시스템을 생각해 볼 수 있다. 이 시스템은 일반적으로 여러 구성 요소로 이루어져 있다. (스크린, 프로젝터, 오디오 시스템, DVD 플레이어 등)

홈 시어터
홈 시어터

  • 각 구성 요소는 별도로 작동할 수 있고, 복잡한 설정과 조작이 필요할 수 있다. 하지만 홈 시어터를 쉽게 사용하기 위해, '시작' 버튼 하나로 모든 장비를 켜고 영화를 재생할 수 있는 간단한 리모컨을 제공할 수 있다.
  • 이 '시작' 버튼이 Facade pattern의 역할을 한다. 사용자(개발자)는 복잡한 각 장비의 작동 방식을 알 필요 없이, 하나의 버튼으로 전체 시스템을 제어할 수 있게 된다.
이 예시에서, 리모컨의 '시작' 버튼은 복잡한 시스템을 간소화한 인터페이스를 제공하며, 사용자는 내부의 복잡한 세부 사항을 알 필요 없이 리모컨을 통해 쉽게 영화를 즐길 수 있게 되는 것이다.

 

3. Facade Pattern의 장단점

3-1. 장점

  1. 시스템의 복잡성 숨김
  • 퍼사드 패턴을 사용하면, 개발자는 복잡한 서브시스템의 내부 로직을 몰라도 되고, 단순화된 인터페이스를 통해서만 시스템과 상호작용할 수 있다. 이렇게 되면 개발 과정이 단순해지고, 오류 가능성도 줄어든다.
  2. 의존성 감소
  • 클라이언트 코드는 서브시스템의 구체적인 구현에 의존하지 않고, 단지 퍼사드 인터페이스에만 의존하게 된다. 이런 방식은 시스템의 결합도를 낮추어 유지보수와 확장성을 높이는 데 도움을 준다.
  3. 유지보수 및 확장 용이
  • 서브시스템의 변경이 있어도 퍼사드 인터페이스는 그대로 유지될 수 있다. 이것은 시스템을 업그레이드하거나 수정할 때, 클라이언트 코드에 미치는 영향을 최소화해주는 장점이 있다.

 

3-2. 단점

  1. 퍼사드의 과도한 책임
  • 퍼사드가 서브시스템의 모든 요소를 추상화하려고 하면, 너무 많은 책임과 복잡성을 가지게 될 수 있다. 이는 퍼사드 자체가 복잡해지고 관리하기 어려워지는 원인이 될 수 있다.
  2. 서브시스템에 대한 의존성
  • 퍼사드는 서브시스템에 대해 어느 정도 의존성을 가질 수밖에 없다. 만약 서브시스템이 크게 변경된다면, 퍼사드 역시 이에 맞춰 수정되어야 하며, 이는 유지보수의 복잡성을 증가시킬 수 있다.
  3. 유지보수의 복잡성
  • 퍼사드가 제공하는 추가적인 추상화 레이어는 코드 베이스에 추가적인 계층을 만든다. 이렇게 되면 간단한 작업조차도 퍼사드를 통해 이루어져야 하고, 이는 유지보수를 더 복잡하게 만들 수 있다. 특히, 시스템이 커지고 복잡해질수록 이러한 문제는 더 심각해질 수 있다.

 

3-3. 정리

  • 퍼사드 패턴은 시스템의 복잡성을 감추고 간결한 인터페이스를 제공하는 장점이 있지만, 동시에 퍼사드 자체가 복잡해지거나 서브시스템에 대한 의존성이 증가하는 단점도 가지고 있다. 이 패턴을 적절히 사용하면 시스템의 유지보수와 확장성을 크게 향상시킬 수 있지만, 과도하게 의존하게 되면 유지보수의 복잡성이 증가할 수 있으니 주의해야 한다.

 

4. 예제: Facade Pattern 적용 X: 온라인 주문 시스템

4-1. 퍼사드 패턴을 적용하지 않은 시스템 예시

  • 퍼사드 패턴을 사용하지 않는 경우, 컨트롤러에서 직접 각 서비스 클래스(재고, 결제, 배송)를 호출하고, 각 서비스의 결과에 따라 다음 단계를 진행하는 방식으로 코드를 작성할 수 있다. 이 경우, 컨트롤러는 각 서비스의 복잡한 로직과 상호작용에 대해 더 많이 알고 있어야 하고, 코드가 더 복잡해질 수 있다.

Facade 적용하지 않은 구조
Facade 적용하지 않은 구조

 

4-2. 주문 요청(Request DTO)

  • 클라이언트로부터 주문 정보를 받는 데 사용되는 데이터 전송 객체(DTO).
  • 여기서는 사용자가 주문할 때 필요한 정보를 담고 있다. itemId는 주문할 상품, paymentDetails는 결제 정보, address는 배송지 주소를 의미해. 이 정보들을 통해 주문을 진행할 수 있다.
@Setter
@Getter
public class OrderRequest {
    private String itemId;
    private String paymentDetails;
    private String address;
}

 

4-3. 컨트롤러(controller)

  • 이 코드에서 컨트롤러는 각 서비스의 세부 로직을 직접 관리하며, 각 단계별로 성공 여부를 확인하고 그에 따라 적절한 HTTP 응답을 반환한다. 이 방식은 퍼사드 패턴을 사용하지 않았을 때 컨트롤러가 서비스 로직과 강하게 결합되어 있다는 것을 보여준다.
@RequiredArgsConstructor
@RestController
@RequestMapping("/order")
public class OrderController {

    private final InventoryService inventoryService;
    private final PaymentService paymentService;
    private final ShippingService shippingService;

    @PostMapping
    public ResponseEntity<String> placeOrder(@RequestBody OrderRequest orderRequest) {
        // 재고 확인
        if (!inventoryService.checkStock(orderRequest.getItemId())) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("재고 없음");
        }

        // 결제 처리
        if (!paymentService.processPayment(orderRequest.getPaymentDetails())) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("결제 실패");
        }

        // 배송 준비
        if (!shippingService.prepareShipping(orderRequest.getAddress())) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("배송 준비 실패");
        }

        return ResponseEntity.ok("주문 성공");
    }
}

 

4-4. 서비스 (Service)

@Service
public class InventoryService {
    // 재고 관리 메서드
}

@Service
public class PaymentService {
    // 결제 처리 메서드
}

@Service
public class ShippingService {
    // 배송 준비 메서드
}

 

 

5. 예제: Facade Pattern 적용 O: 온라인 주문 시스템

5-1. 퍼사드 패턴 적용한 시스템

  • 온라인 쇼핑몰에서 주문을 처리하는 시스템을 구현한다고 가정해 보자. 이 예시에서는 컨트롤러(Controller), 서비스(Service), 그리고 퍼사드(Facade)를 사용해 복잡한 비즈니스 로직을 처리하도록 구성할 것이다.

퍼사드 패턴 적용
퍼사드 패턴 적용

 

5-2. 주문 요청(Request DTO)

  • 클라이언트로부터 주문 정보를 받는 데 사용되는 데이터 전송 객체(DTO).
  • 여기서는 사용자가 주문할 때 필요한 정보를 담고 있다. itemId는 주문할 상품, paymentDetails는 결제 정보, address는 배송지 주소를 의미해. 이 정보들을 통해 주문을 진행할 수 있다.
@Setter
@Getter
public class OrderRequest {
  private String itemId;
  private String paymentDetails;
  private String address;
}

 

5-3. 컨트롤러(Controller)

  • HTTP 요청을 받아 OrderFacade 클래스로 요청을 전달하고, 주문 결과에 따라 적절한 HTTP 응답을 반환한다.
  • 사용자의 주문 요청을 받아들이는 '문지기' 역할이다. 주문 요청이 들어오면 OrderFacade를 통해 주문 처리 과정을 시작한다. 주문 결과에 따라 성공 메시지나 실패 메시지를 사용자에게 보내준다.
@RestController
@RequestMapping("/order")
public class OrderController {

    private final OrderFacade orderFacade;

    public OrderController(OrderFacade orderFacade) {
        this.orderFacade = orderFacade;
    }

    @PostMapping
    public ResponseEntity<String> placeOrder(@RequestBody OrderRequest orderRequest) {
        boolean result = orderFacade.placeOrder(orderRequest);
        return result ? ResponseEntity.ok("주문 성공") : ResponseEntity.status(HttpStatus.BAD_REQUEST).body("주문 실패");
    }
}

 

5-4. 서비스(Service)

  • 각 서비스는 주문 과정에서 필요한 특정 작업을 담당한다. InventoryService는 상품이 충분한지 확인하고, PaymentService는 결제가 성공적으로 이루어졌는지, ShippingService는 상품 배송 준비를 확인한다.
@Service
public class InventoryService {
    // 재고 관리 메서드
}

@Service
public class PaymentService {
    // 결제 처리 메서드
}

@Service
public class ShippingService {
    // 배송 준비 메서드
}

 

 

 

이전과 다른 점은 퍼사드 클래스를 생성한다는 것이다.


 

5-5. 퍼사드(Facade)

  • 여기서는 주문 과정의 '지휘자' 역할을 한다. 사용자로부터 받은 주문 요청에 따라 서비스들을 적절히 조율하여 주문 과정을 진행한다. 각 단계가 순조롭게 흘러가면 최종적으로 주문 성공을 알려준다.
  • 이 구조에서 추상화는 각 단계별 복잡한 로직을 숨기고 사용자에게는 간단한 인터페이스를 제공하는 것을 의미한다. 사용자는 복잡한 내부 과정을 몰라도 주문을 쉽게 할 수 있다.
@RequiredArgsConstructor
@Component
public class OrderFacade {

    private final InventoryService inventoryService;
    private final PaymentService paymentService;
    private final ShippingService shippingService;

    public boolean placeOrder(OrderRequest orderRequest) {
        // 재고 확인
        if (!inventoryService.checkStock(orderRequest.getItemId())) {
            return false; // 재고 없음
        }

        // 결제 처리
        if (!paymentService.processPayment(orderRequest.getPaymentDetails())) {
            return false; // 결제 실패
        }

        // 배송 준비
        if (!shippingService.prepareShipping(orderRequest.getAddress())) {
            return false; // 배송 준비 실패
        }

        return true; // 모든 과정 성공, 주문 성공
    }
}

 

5-6. 결론

  • 이 예시에서 퍼사드 패턴을 사용한 이유는 복잡한 주문 처리 로직을 단순화하고 사용자 인터페이스를 깔끔하게 유지하기 위해서다. 각 서비스(재고, 결제, 배송)는 별도의 복잡한 로직을 가지고 있지만, 퍼사드 패턴을 적용함으로써 개발자는 복잡한 서비스 로직을 각각 이해하고 사용할 필요 없이, 단순화된 인터페이스를 통해 전체 주문 처리 과정을 쉽게 구현할 수 있다.

 

 

 

퍼사드는 이렇게 내부 로직을 숨기고, 사용자에게는 간단하고 명확한 인터페이스만 제공하는 역할을 한다. 이로 인해 시스템의 유지보수와 확장성이 향상되며, 사용자 경험이 개선된다.




6. 마무리

6-1. 퍼사드 패턴과 MVC

  • 퍼사드 패턴은 MVC (Model-View-Controller) 아키텍처에서 특별한 명칭이나 역할을 가지지 않는다. MVC는 주로 웹 애플리케이션의 사용자 인터페이스 계층을 구조화하는 데 사용되는 디자인 패턴이다. 반면, 퍼사드 패턴은 시스템의 복잡한 서브시스템을 단순화하고 통합하는 역할을 한다.
  • MVC 내에서 퍼사드는 서비스 계층을 간소화하는 데 사용될 수 있으며, 이를 통해 컨트롤러가 다루기 쉬운 인터페이스를 통해 복잡한 로직을 관리할 수 있다. 그러나 퍼사드를 사용하는 것 자체가 별도의 아키텍처 패턴이나 명칭으로 분류되지는 않는다.

 

6-2. 퍼사드 패턴의 의존성 방향

  • 퍼사드 패턴을 사용하는 경우, 의존성의 방향이 일반적으로 Controller -> Facade -> Service -> Repository가 된다. 퍼사드 패턴의 목적은 컨트롤러와 서비스 계층 사이의 복잡성을 감소시키는 것이기 때문에, 컨트롤러는 퍼사드를 통해 서비스 계층과 상호작용하게 된다. 이는 서비스 계층의 내부 로직이나 변경사항이 컨트롤러에 미치는 영향을 최소화하기 위함이다.
  • 반면, 퍼사드 패턴을 적용하지 않은 기존 방식은 Controller -> Service -> Repository로, 컨트롤러가 서비스 계층과 직접적으로 상호작용한다. 서비스 계층에서는 퍼사드에 의존하지 않고 자체적인 비즈니스 로직을 처리한다.

 

6-3. 결론

  • MVC 패턴에서 퍼사드 패턴을 적용하면 컨트롤러가 직접 서비스를 호출하는 대신 퍼사드를 통해 서비스에 접근한다. 이 구조에서는 컨트롤러가 퍼사드에 요청을 보내고, 퍼사드가 내부적으로 여러 서비스(예: 재고, 결제, 배송 서비스 등)와 상호작용하여 필요한 작업을 수행한다. 따라서, 흐름은 Controller -> Facade -> Service로 변경되며, 이를 통해 컨트롤러의 복잡성을 줄이고, 코드의 유지보수성과 가독성을 향상시킬 수 있다.

퍼사드 패턴 적용

  • 퍼사드 패턴을 사용하면, 컨트롤러의 코드는 그대로 유지되고, 퍼사드 내부에서 필요한 서비스들을 조합하고 관리한다. 이 방식은 컨트롤러가 복잡한 비즈니스 로직을 직접 처리하지 않아도 되므로, 컨트롤러의 코드는 간결하고 명확하게 유지된다. 필요한 변경이 있을 때는 퍼사드의 내부 구현을 수정하면 되며, 이는 컨트롤러의 추가적인 수정 없이도 시스템의 기능을 유연하게 변경할 수 있게 해 준다.

 

 

 

이렇게 퍼사드 패턴에 대해서 간단히 알아봤다.
퍼사드 패턴은 우리가 신경 쓰지 않지만 자연스럽게 적용해서 사용하고 있는 패턴이라는 생각이 든다.


 

 

 

스프링의 팩토리 메서드 패턴도 가볍게 알아보자! 👇🏻👇🏻

 

 

가볍게 알아보는 디자인 패턴 - 팩토리 메서드 패턴(Factory Method Pattern)

팩토리 메서드 패턴에 대해서 알아보자 1. 팩토리 메서드 패턴이란? 팩토리 메서드 패턴은 객체 생성을 추상화하고 캡슐화하는 디자인 패턴이다. 스프링에서 이 패턴은 BeanFactory와 ApplicationContext

curiousjinan.tistory.com

 

스프링의 싱글톤 패턴도 가볍게 알아보자! 👇🏻👇🏻

 

가볍게 알아보는 디자인 패턴 - 싱글톤 패턴(Singleton Pattern)

스프링에 적용된 싱글톤 패턴에 대해서 간단히 알아보자 1. 싱글톤 패턴의 구조 이해하기 1-1. 스프링 컨테이너 이해하기 스프링 컨테이너는 ApplicationContext를 통해 정의된다. 이 컨테이너는 IoC(Inv

curiousjinan.tistory.com

 

반응형