ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링] 안티패턴: 완화된 레이어드 아키텍처
    Spring 2025. 12. 19. 15:59

    백엔드 개발자라면 코드를 작성하다가 한 번쯤 이런 '유혹'에 빠져본 적이 있을 것입니다. 아주 단순한 조회 로직이나, 별다른 비즈니스 처리가 필요 없는 데이터를 다룰 때 다음과 같은 의문이 듭니다.

    "이걸 굳이 서비스(Service) 계층을 거쳐야 하나?"

    오늘 이야기할 주제인 '완화된 레이어드 아키텍처(Relaxed Layered Architecture)'를 본격적으로 소개하기에 앞서, 여러분께 질문을 하나 던져보겠습니다.

    Q. 컨트롤러(Presentation)가 리포지토리(Infrastructure)를 직접 사용하는 것은 괜찮을까요?

    우리는 보통 Controller -> Service -> Repository로 이어지는 견고한 흐름에 익숙해져 있습니다. 하지만 종종 아래와 같은 구조로 코드를 작성하고 싶은 충동, 혹은 그렇게 작성된 레거시 코드를 마주하게 됩니다.

    즉, Presentation Layer가 Business Layer(Service)를 건너뛰고 곧바로 Infrastructure Layer(Persistence)에 접근하는 형태입니다.

    누군가는 이를 "불필요한 보일러플레이트 코드를 줄이는 실용적인 방법"이라고 말하고, 누군가는 "계층의 책임을 무너뜨리는 위험한 안티패턴"이라고 경고합니다. 과연 정답은 무엇일까요? 그리고 이러한 구조는 우리 시스템에 어떤 나비효과를 불러올까요?

    지금부터 '완화된 레이어드 아키텍처'의 개념과 이것이 안티패턴으로 불릴 수 있는 이유에 대해 깊이 파고들어 보겠습니다.

     

    1. 결론부터 말하자면 "좋지 않습니다"

    앞서 던진 질문, "컨트롤러가 리포지토리를 직접 사용해도 되는가?"에 대한 답을 먼저 드리자면, 제 대답은 "아니오, 좋지 않습니다"입니다.

    실무에서 편의를 위해 종종 사용되긴 하지만, 아키텍처 관점에서 보았을 때 2개 이상의 레이어를 건너뛰어 통신하는 구조(Layer Skipping)는 일반적으로 안티패턴(Anti-pattern)으로 분류되기 때문입니다.

    2. 완화된 레이어드 아키텍처 (Relaxed Layered Architecture)란?

    우리가 흔히 '안티패턴'이라고 부르는 이 구조에도 정식 명칭은 있습니다. 바로 '완화된 레이어드 아키텍처(Relaxed Layered Architecture)'입니다. 상위 레이어가 자신의 바로 아래뿐만 아니라, 그보다 더 하위의 모든 레이어에 접근할 수 있는 권한을 가진 구조를 의미합니다.

    이름에서 알 수 있듯이 '완화됐다(Relaxed)'는 표현은 레이어드 아키텍처의 기본 원칙을 유지하되, 특정 제약을 느슨하게 풀어주었다는 뜻을 담고 있습니다.

    그렇다면 여기서 말하는 '제약'이란 무엇일까요?

    3. 완화된 제약 "인접한 레이어와만 대화하라"

    일반적인 레이어드 아키텍처(Layered Architecture)의 가장 핵심적인 규칙은 "레이어 간의 통신은 오직 인접한 하위 레이어에서만 이루어져야 한다"는 것입니다.

    • 일반적인 규칙: Presentation -> Service -> Infrastructure (O)
    • 완화된 규칙: Presentation -> Infrastructure (O? 허용함)

    엄격한 아키텍처에서는 '폐쇄형 레이어(Closed Layer)' 개념을 사용하여, 요청이 상위에서 하위로 순차적으로 흘러가도록 강제합니다. 하지만 완화된 아키텍처는 이를 '개방형 레이어(Open Layer)'로 변경하여, 컨트롤러가 중간의 서비스 계층을 무시하고 리포지토리에 접근할 수 있도록 한 것입니다.

     

    4. 코드로 보는 완화된 레이어드 아키텍처

    백문이 불여일견입니다. 앞서 설명한 개념이 실제 코드에서는 어떤 형태로 나타나는지 살펴보겠습니다. 가장 흔한 예시인 '회원 목록 조회' 기능을 구현한다고 가정해 봅시다.

    보통의 경우라면 컨트롤러는 서비스를 의존하고, 서비스가 리포지토리를 의존하는 형태겠지만, 완화된 레이어드 아키텍처에서는 다음과 같이 컨트롤러가 리포지토리를 직접 의존하게 됩니다.

    1. Repository (Infrastructure Layer)

    먼저, 데이터베이스 접근을 담당하는 리포지토리입니다.

     
    @Repository
    public interface MemberRepository extends JpaRepository<Member, Long> {
        // 기본적인 CRUD 기능을 제공하는 JPA 리포지토리
    }
    

    2. Controller (Presentation Layer)

    문제의 핵심이 되는 컨트롤러 코드입니다.

     
    @RestController
    @RequestMapping("/api/members")
    @RequiredArgsConstructor
    public class MemberController {
    
        // [핵심] Service가 아닌 Repository를 직접 주입받습니다.
        private final MemberRepository memberRepository;
    
        @GetMapping
        public ResponseEntity<List<Member>> getAllMembers() {
            // [핵심] 중간 비즈니스 로직 없이, 컨트롤러가 리포지토리의 메서드를 직접 호출합니다.
            List<Member> members = memberRepository.findAll();
            
            return ResponseEntity.ok(members);
        }
    }
    

    위 코드를 보면 MemberService라는 중간 계층이 아예 존재하지 않거나, 적어도 이 조회 로직에서는 배제되어 있습니다.

    Presentation Layer인 MemberController가 Infrastructure Layer인 MemberRepository의 findAll() 메서드를 직접 호출하여 데이터를 가져옵니다. 코드가 매우 간결해 보이고, "단순 조회인데 굳이 서비스 클래스를 만들어야 해?"라는 질문에 대한 실용적인 대답처럼 보이기도 합니다.

    하지만 이 짧은 코드가 프로젝트가 커짐에 따라 어떤 문제를 야기할 수 있을까요?

     

    프레젠테이션 레이어(UI나 Controller)가 비즈니스 로직까지 처리하며 지나치게 똑똑해지는 스마트 UI 현상이 나옵니다. 더 심각한 문제는 코드의 위치에 대한 기준이 사라진다는 점입니다.

    "컨트롤러가 리포지토리를 불러도 된다"는 허용은 곧 "비즈니스 로직을 어디에 짜도 상관없다"는 신호가 됩니다. 원칙적으로 비즈니스 로직은 비즈니스 레이어(Service, Domain)에 있어야 하지만, 이 규칙이 무의미해지는 순간 혼란이 시작됩니다.

    • 어떤 개발자는 컨트롤러에서 데이터를 필터링하고,
    • 어떤 날은 서비스에서 계산 로직을 수행하며,
    • 또 다른 날은 도메인 객체에 로직을 넣습니다.

    결국 비즈니스 로직은 역할과 책임에 따라 유의미한 객체 한 곳으로 모이는 것이 아니라, 개발자의 기분에 따라 그날그날 중구난방으로 위치하게 됩니다.

    기능 하나를 수정하려 해도 코드가 어디에 박혀 있는지 한눈에 파악하기 힘들어집니다. 이것이 바로 우리가 완화된 레이어드 아키텍처를 '안티패턴'이라고 부르는 이유입니다.

    만약, 제약이 엄격했다면 어땠을까요?

    그렇다면 반대로 가정해 봅시다. 만약 우리 프로젝트에 "프레젠테이션 레이어는 반드시 비즈니스 레이어에만 의존해야 한다"는 엄격한 제약이 있었다면 어땠을까요?

    즉, 완화된 아키텍처를 버리고 일반적인 레이어드 아키텍처(Layered Architecture)의 규칙을 강제했다면 상황은 달라졌을 것입니다.

    • 컨트롤러는 서비스를 의존: 컨트롤러는 리포지토리에 아예 접근할 수 없습니다. 컴파일 에러가 날 테니까요.
    • 강제된 위치 선정: 저장소에서 데이터를 불러오고 가공하는 모든 로직은 비즈니스 레이어(Service)에 작성될 수밖에 없습니다.

    컨트롤러는 API 요청을 받아 파라미터를 검증하고, 적절한 서비스를 호출한 뒤, 응답을 반환하는 본연의 역할에만 충실하게 됩니다. 복잡한 '스마트 UI' 코드는 자연스럽게 사라지고, 책임이 명확히 분리된 깔끔한 구조가 유지됩니다.

    5. 결론

    이것이 바로 레이어드 아키텍처에서 "레이어 간 통신은 인접한 레이어끼리만 이뤄져야 한다"는 제약을 두는 핵심적인 이유입니다.

    마지막으로 딱 하나만 강조하겠습니다.

    컨트롤러가 리포지토리를 직접 사용해서는 안 됩니다. 다시 말해, 프레젠테이션 레이어가 인프라스트럭처 레이어에 직접 의존하게 해서는 안 됩니다.

    이 원칙을 지키는 것만으로도, 여러분의 코드는 '기분에 따라 작성되는 코드'에서 '설계에 따라 작성되는 코드'로 변화할 것입니다.

Designed by MSJ.