Easy Understanding

웹 백엔드 관점으로 본 디자인 패턴 정리(8) - 파사드 패턴(Facade Pattern) 본문

Study

웹 백엔드 관점으로 본 디자인 패턴 정리(8) - 파사드 패턴(Facade Pattern)

appleg1226 2022. 5. 7. 15:31

 

파사드 패턴

- 필요 개념: 상속이나 컴포지션과는 상관 없긴 함
- 활용도:가끔
- 난이도: 간단
- 패턴이 필요한 상황: 클라이언트 코드가 너무 길고 복잡해질 때

 

바로 스프링의 예시로 들어가보자.

스프링에서 컨트롤러를 작성할 때 여러 구현 철학들이 있지만 난 이런 식의 구현을 별로 좋아하지 않는다.

그래서 좀 정리를 해보고자 한다.

@RestController
@RequiredArgsConstructor
public class AggregationController {
  private final UserService userService;
  private final PaymentService paymentService;
  private final OrderService orderService;
  private final EventService eventService;

  @GetMapping("/users/{loginId}")
  public ResponseEntity<String> user(@PathVariable String loginId){
    User user = userService.getUser(loginId);
    List<Payment> payments = paymentService.getUsersPayments(user.getId());
    List<Order> orders = orderService.getUsersOrders(user.getId());
    List<Event> events = eventService.getUserParticipatedEvents(orders);
    return new ResponseEntity<>(toJsonResponse(payments, orders, events), HttpStatus.OK);
  }
}

API 설명을 하자면  

1. 로그인 아이디를 받아서 유저의 객체를 DB에서 가져오고

2. 유저의 아이디로 구매 내역들을 받아오고

3. 유저의 아이디로 주문 내역들을 받아오고

4. 유저의 주문 내역들로 참여한 이벤트 목록을 받아오고

5. 이것들을 모두 집계하여 하나의 response로 내려주는 API이다.

 

이 로직 자체는 솔직히 전혀 문제가 없지만, 그렇다고 이걸 나누기에도 고민거리가 좀 있다.

우선 서비스 로직이 컨트롤러에 너무 많이 침투해있다.

이건 예시라서 그렇지 실제로는 저 코드 이상의 엄청난 길이의 코드가 엄청나게 늘어나게 될 것이다.

 

저걸 조금이라도 줄여보고자 시도해볼 수 있는 게 몇 가지가 있다.

우선 든 생각은 User와 관련된 정보니까 'UserService 안에 저 모든 걸 몰아버리자!' 라는 생각이 든다. 

그럼 저 의존성들을 고스란히 UserService로 모아보자.

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
  private final PaymentService paymentService;
  private final OrderService orderService;
  private final EventService eventService;

  public String getUsersInfoJson(String loginId){
    ...
  }
  
  ...
}

 

위처럼 하면 이렇게 깔끔하게 코드를 정리할 수가 있게 된다.

@RestController
@RequiredArgsConstructor
public class AggregationController {
  private final UserService userService;

  @GetMapping("/users/{loginId}")
  public ResponseEntity<String> user(@PathVariable String loginId){
    return new ResponseEntity<>(userService.getUsersInfoJson(loginId), HttpStatus.OK);
  }
}

 

그런데 이 컨트롤러에 다른 API가 추가 되어야 한다고 한다.

이번엔 UserService가 아니라 다른 Service를 주입해야 하는데, 

하필이면 아까전에 사용했던 EventService를 컨트롤러에 주입해야할 일이 생긴다.

 

이건 오케이.

 

그런데 어쩌다보니 EventService에서도 내부에 UserService를 주입받아야 하는 로직이 발생해 버렸다.

이렇게 되면 문제가 하나 생기는데 서로가 서로를 주입해버리는 문제(순환참조)가 생겨 버린다.

 

아래 사진을 보면 알겠지만 UserService와 EventService가 서로를 참조하고 있는 형태가 되어버린다. 

 

 

이럴 때는 해결법이 하나 더 있는데,

아예 중간에 레이어 하나를 더 두는 방법이다.

이렇게 해놓고 이전에 각 서비스를 조합하던 로직들을 Facade 레이어에서 관리하도록 하는 것이다.

이렇게하면 Controller의 로직도 깔끔해지고, 의존성이 관리하기 귀찮아지는 것도 방지가 된다.

 

@RestController
@RequiredArgsConstructor
public class AggregationController {
  private final AggregationFacade aggregationFacade;

  @GetMapping("/users/{loginId}")
  public ResponseEntity<String> user(@PathVariable String loginId){
    return new ResponseEntity<>(aggregationFacade.getUsersInfoJson(loginId), HttpStatus.OK);
  }
}

@Service
@RequiredArgsConstructor
public class AggregationFacade{
  private final UserService userService;
  private final PaymentService paymentService;
  private final OrderService orderService;
  private final EventService eventService;

  public String getUsersInfoJson(String loginId){
    ...
  }
  
  ...
}

 

설명이 길었지만 길다란 로직들을 그냥 한 클래스에 모아놓는 패턴,

그리고 클라이언트가 그걸 몰라도 되도록 하는 것.

이게 Facade 패턴이다.

 

다양한 예시가 있지만 백엔드 어플리케이션의 경우는

이렇게 각 레이어를 구성할 때 의존성이 복잡해지고 로직이 모이는 상황이라면

파사드 패턴을 도입을 해서 정리해 볼만하다고 생각이 든다.