Skip to content

Facade 패턴에 대한 탐구

Cho jiwon edited this page Jul 14, 2025 · 5 revisions

Facade 패턴에 대한 탐구


개요

팀 맛집 관련 기능을 만드는 과정에서, TeamRestaurant 도메인과 Review Reviewphoto 도메인을 한번에 다루게 되었습니다. 여러 도메인을 다루다 보니, 불가피하게 각 도메인들의 서비스들을 호출 하게 되었습니다. 이렇게 서비스 간 강한 결합이 우려 되는 경우, 팀원들과 사전에 Facade 패턴을 적용해서 해결하기로 했습니다. 기본 적인 설명은 진주님께서 작성을 해주셨지만, 직접 개발을 하면서, 스스로 조금 더 구체적인 내용과 정리가 필요할 것 같다고 느껴, 해당 글을 작성하게 됐습니다.


기본 개념

Facade 패턴 이란, 복잡한 서브시스템(여러 클래스나 모듈)들을 간단하게 사용할 수 있도록 하나의 간편한 인터페이스를 제공하는 구조 패턴 입니다. 프로그램이 커질 수록, 내재된 클래스 및 로직들은 늘어나고, 복잡하지기 마련인데, 이때 Facade 가, 일종의 '창구' 역할을 함으로서 클라이언트가 기능을 편하게 사용을 할 수 있게 해줍니다. 클라이언트는, 복잡한 시스템을 알 필요없이 시스템의 외부에 대해서 단순한 인터페이스를 이용하기만 하면 되기 때문에, 동작의 목적과 같은 중요한 사항을 놓치는 실수를 줄일 수 있습니다.

참고

Facade 란, 건축물의 정면을 의미하며, 건축물의 정면은 보통 건축물의 이미지와 건축 의도를 나타냅니다. 이처럼 건축물 정면만 봐도 이 건물이 어떤 목적을 하는지 단번에 알수 있다는 특징을 차용하여 명명 지은 것입니다.


패턴 구조

image
항목 설명
Facade - 서브시스템의 복잡한 로직을 재정리하여 고수준 인터페이스 제공
- 여러 서브시스템과 상호작용하며, 단순한 진입 지점 역할 수행
- 클라이언트와 서브시스템 간의 강한 결합을 방지
Additional Facade - 퍼사드는 여러 개 존재할 수 있음
- 기능이 서로 연관되지 않은 경우 별도의 퍼사드로 분리 가능
- 메인 퍼사드에서 호출하거나 클라이언트가 직접 접근할 수도 있음
Subsystem (하위 시스템) - 실제로 동작을 수행하는 여러 라이브러리나 클래스 집합
- 퍼사드 내부에서 호출되며, 직접 사용은 복잡함
Client - 서브시스템에 직접 접근하지 않고 퍼사드를 통해 사용
- 퍼사드가 제공하는 고수준 메서드만 알면 됨

패턴 특징

패턴 적용시기

  1. 시스템을 사용하고 있는 외부와 결합도가 너무 높아, 의존성 낮추기 위할때
  2. 간단한 인터페이스를 통해 복잡한 시스템을 접근하도록 하고 싶을때

패턴 장점

  1. 중간 매개체 역할을 하는 Facade 가 있기 때문에, 실제 내부 로직이 변경 되도, 상관이 없어지므로, 의존성을 감소 시킬 수 있음
  2. 복잡한 코드를 감춤으로서, 클라이언트가 쉽게 사용 할 수 있음

패턴 단점

  1. Facade 가 모든 클래스가 결합된, 최상위 객체가 될 수 있음
  2. 추가적인 코드가 늘어나는 것이기 때문에, 유지 보수 측에 공수가 더 들 수 있음

Facade 적용 예시

TeamRestaurantController

   @GetMapping("/{teamId}/restaurants/{teamRestaurantId}/reviews/photos")
    @Operation(summary = "팀 맛집 리뷰 사진 조회", description = "팀 맛집 리뷰 사진을 조회합니다.")
    public ListResponse<PhotoPath> getTeamRestaurantPhotos(
            @PathVariable @Positive final Long teamId,
            @PathVariable @Positive final Long teamRestaurantId,
            @Valid final TeamRestaurantReviewPhotoRequest request) {
        return teamRestaurantReviewFacade.getTeamRestaurantReviewPhotos(
                teamId, teamRestaurantId, request);
    }

TeamRestaurantReviewFacade

 @Transactional(readOnly = true)
    public ListResponse<TeamRestaurantReviewResponse> getTeamRestaurantReviews(
            Long teamId, Long teamRestaurantId, TeamRestaurantReviewRequest request) {
        final TeamRestaurant teamRestaurant =
                teamRestaurantService.getValidatedTeamRestaurant(teamId, teamRestaurantId);
        final Page<ReviewWithUserProjection> reviews =
                reviewService.getReviewWithUserByTeamRestaurantId(
                        teamRestaurant.getId(), request.toPageableAndDateSorted());

        // 리뷰 Id 추출
        final List<Long> reviewIds =
                reviews.getContent().stream().map(ReviewWithUserProjection::id).toList();

        // 리뷰 별 리뷰 사진들 정보 가져오기
        final ReviewPhotoPaths reviewPhotoPaths = reviewPhotoService.getReviewPhotoPaths(reviewIds);

        final Page<TeamRestaurantReviewResponse> teamRestaurantReviewResponses =
                TeamRestaurantReviewResponse.from(reviews, reviewPhotoPaths);
        return ListResponse.from(teamRestaurantReviewResponses);
    }

Facade 패턴이 적용 되지 않았으면, 실제로 TeamRestaurantService,ReviewPhotoService,ReviewService 들을 모두 호출해야 되기 때문에, 매우 강한 의존성을 가지게 되고, 코드도 클린하지 않게 된다.

참고

Facade 는, 진입지점 역할을 하기 때문에, 중복된 인스턴스 생성이 불필요하기 때문에, SingleTone 으로 구성해주는게 좋다. TeamRestaurantReviewFacade 에서는 @Service 를 선언 함으로 서, SingleTone 이 적용 됐다.

TeamRestaurantController

@Service
@RequiredArgsConstructor
public class TeamRestaurantReviewFacade {

결론

이전에 개발 할 때는 서비스 끼리 너무 강하게 결합되어 있어, 기능 하나를 수정할 때 꽤 애먹은 경험도 있었는데,이번에, Facade 패턴을 적용해서 처음 개발을 진행해 보니, 확실히 코드가 조금 더 깔끔하고, 의존성이 많이 감소 한 것 같습니다. 디자인 패턴 적용 함으로서, 더 정갈한 코드를 짤 수 있게 된다는 것이 재밌기도 하고, 역시 배우고 공부해야 될 것이 참 많은 것 같다고 다시 한번 느꼈습니다. 더 파이팅 해야겠습니다..!! 🔥🔥🔥🔥🔥


참고 자료

퍼사드(Facade)패턴 완벽 마스터하기

Clone this wiki locally