Spring MVC에 Facade 패턴 도입기 - @Gateway 어노테이션으로 레이어드 아키텍처 개선

전통적인 Controller-Service-Repository 구조에 커스텀 @Gateway 어노테이션과 Facade 레이어를 도입하여 도메인별 단일 책임 원칙을 확보하고 팀 생산성을 높인 과정을 공유합니다.

배경

프로젝트 초기에는 전통적인 Spring MVC 패턴인 Controller → Service → Repository 구조를 사용하고 있었다.

서비스 규모가 작을 때는 문제가 없었지만, 결제·인증·알림·구독 등 도메인이 늘어나고 비즈니스 로직이 복잡해지면서 구조적인 문제가 하나둘 드러나기 시작했다.

기존 구조의 문제점 Service 레이어의 비대화

하나의 Service에 비즈니스 로직, 다른 도메인 호출, 트랜잭션 관리가 모두 섞여 있었다. 예를 들어 PaymentService에서 CouponService, UserService, SubscriptionService를 직접 호출하면서 결제 서비스가 수백 줄로 비대해졌다. 도메인 간 강한 결합

Service가 다른 도메인의 Service를 직접 참조하다 보니, 순환 참조 문제가 발생하거나 변경 시 영향 범위를 예측하기 어려웠다. 코드 중복

비슷한 비즈니스 흐름이 여러 Service에 흩어져 있었다. 대표적인 예가 "유저 조회 + 권한 검증" 로직이다.

이런 식으로 유저 검증 로직이 5~6곳에 복사되어 있었다. 유저 상태 체크 조건이 하나 추가되면 모든 곳을 찾아서 동일하게 수정해야 했고, 하나라도 빠뜨리면 버그가 됐다. 각 Service가 UserRepository를 직접 들고 있으니, 도메인 경계를 넘는 접근이 자연스럽게 퍼져나갔다. 테스트의 어려움

Service 하나를 테스트하려면 의존하는 다른 Service를 전부 모킹해야 했다. 도메인 로직을 검증하고 싶은 건데, 의존성 설정에 더 많은 코드가 들어갔다.

Facade 패턴 도입 - Gateway 레이어

이 문제를 해결하기 위해 Controller와 Service 사이에 Gateway 레이어를 도입했다. 디자인 패턴으로는 Facade 패턴에 해당하지만, 우리 팀에서는 여러 Service를 조합하여 API의 진입점 역할을 한다는 의미에서 Gateway라고 명명했다.

변경된 구조

Controller → Gateway → Service → Repository

각 레이어의 역할을 명확하게 분리했다.

| 레이어 | 역할 | 규칙 | |--||| | Controller | HTTP 요청/응답 처리, 파라미터 검증 | 비즈니스 로직 없음, Gateway만 호출 | | Gateway | 비즈니스 유스케이스 조합, 트랜잭션 관리 | 여러 Service를 조합하여 하나의 기능을 완성 | | Service | 단일 도메인 로직 | 자기 도메인의 Repository만 접근, 다른 Service 직접 호출 금지 | | Repository | 데이터 접근 | 순수 쿼리 |

@Gateway 커스텀 어노테이션

패턴을 코드 레벨에서 강제하기 위해 커스텀 어노테이션을 만들었다.

Spring의 @Component를 메타 어노테이션으로 사용하여, @Gateway를 붙이면 자동으로 Spring Bean으로 등록된다. @Service, @Repository처럼 레이어의 역할을 어노테이션으로 명시하는 것이 목적이다.

왜 @Component를 직접 쓰지 않고 커스텀 어노테이션을 만들었을까? Spring 개발자라면 @Service, @Repository, @Controller를 이미 자연스럽게 사용하고 있다. @Gateway도 같은 맥락이다. 새로운 개념을 학습하는 게 아니라, 이미 익숙한 Spring의 관례를 그대로 확장하는 것이기 때문에 팀원들이 거부감 없이 빠르게 받아들일 수 있었다. 클래스에 @Gateway가 붙어 있으면 "여러 Service를 조합하는 레이어구나"를 별도 설명 없이 바로 이해할 수 있다.

패키지 구조

도메인별로 gateway 패키지를 두어 구조를 통일했다.

현재 17개의 Gateway 클래스가 각 도메인에 걸쳐 운영되고 있다.

적용 예시

인증 - AuthGateway

인증은 OAuth 클라이언트, JWT 발급, 리프레시 토큰 관리, 유저 정보 조회 등 여러 도메인이 얽히는 대표적인 복합 유스케이스다.

기존에는 이 흐름이 AuthService 하나에 뭉쳐 있었고, OAuth 클라이언트 로직과 JWT 로직과 유저 조회 로직이 한 클래스에 섞여 있었다. Gateway를 도입하면서 각 Service는 자기 역할만 담당하고, Gateway가 흐름을 조합한다.

유저 - UserGateway

유저 관련 API는 수업, 결제, 티켓, 알림 등 거의 모든 도메인과 연관된다.

UserGateway는 8개 이상의 Service를 조합한다. 만약 이 의존성이 전부 UserService에 들어 있었다면, UserService는 수천 줄의 "God Class"가 됐을 것이다.

결제 - PaymentGateway