계층형(Layered) 아키텍처
스프링 프로젝트를 처음 만들면 자연스럽게 Controller, Service, Repository로 코드를 나누게 됩니다. 이렇게 책임에 따라 코드를 나누는 구조를 계층형 아키텍처라고 합니다. 이번 글에서는 계층형 아키텍처가 무엇인지, 어떤 장단점이 있고 언제 선택해야 하는지 정리해 보겠습니다.
계층형 아키텍처
계층형 아키텍처는 소프트웨어를 책임에 따라 여러 논리적 계층(Layer)으로 나누는 아키텍처 패턴이며, 오래전부터 널리 사용된 구조입니다.
책임지는 영역을 기준으로 코드를 묶어 수평적 계층으로 구조화하고, 각 계층이 서로 상호작용하며 데이터를 처리하고 비즈니스 로직을 수행합니다.
아키텍처 구조
계층형 아키텍처는 일반적으로 표현 계층, 비즈니스 계층, 영속성 계층으로 구성합니다. 아래 그림처럼 데이터베이스를 별도 계층으로 그리기도 하지만, 코드로 구현하는 계층은 앞의 세 가지이고 데이터베이스는 영속성 계층이 접근하는 저장소입니다.
여기서 계층은 책임과 의존 방향을 기준으로 나눈 논리적 구분(Layer)이며, 별도 서버나 프로세스로 분리된 물리적 배포 단위(Tier)와는 다릅니다. 여러 계층을 한 Tier에 함께 배포할 수도 있고(스프링 부트 애플리케이션 하나가 대표적입니다), 계층마다 별도 Tier로 나눌 수도 있습니다. N-Tier는 이 논리적 계층과 물리적 Tier를 함께 다루는 용어입니다.

각 계층이 의미하는 것을 살펴보겠습니다.
표현 계층 (Presentation Layer)
웹 페이지, 모바일 앱 인터페이스, GUI 등 사용자와의 상호작용을 처리합니다. 데이터를 어떻게 보여주고 사용자와 시스템이 어떻게 상호작용하는지를 정의하는 책임을 가집니다.
비즈니스 계층 (Business Layer)
데이터 검증, 계산, 서비스의 핵심 알고리즘 등 애플리케이션의 비즈니스 규칙과 로직을 처리합니다. 말 그대로 비즈니스 로직과 규칙에 대한 책임을 가집니다.
영속성 계층 (Persistence Layer)
데이터베이스 쿼리, 캐싱 등 데이터 접근을 관리하고 데이터베이스와의 상호작용을 처리합니다.
닫힌 계층과 열린 계층
계층 간 접근을 제한하는 방식에 따라 닫힌 계층과 열린 계층으로 나뉩니다.
닫힌 계층은 상위 계층이 하위 계층에 접근할 때 중간 계층을 반드시 거치도록 합니다.
열린 계층은 특정 계층을 건너뛰고 직접 접근하는 것을 허용합니다. 예를 들어 표현 계층에서 영속성 계층에 바로 접근할 수 있게 하는 것입니다.
열린 계층은 불필요한 계층 통과를 줄일 수 있습니다. 하지만 여러 계층이 같은 하위 계층에 의존하게 되어 결합도가 높아질 수 있으므로, 명확한 이유가 없다면 닫힌 계층을 우선적으로 고려해야 합니다.
장점과 단점
장점
관심사의 분리
각 계층의 구성 요소는 특정한 관심사만 다룹니다. 따라서 개발자는 한 번에 하나의 계층에만 집중할 수 있고, 각 계층을 독립적으로 개발하고 유지보수할 수 있습니다.
개발 용이성
오랫동안 표준처럼 사용된 구조라서 각 계층의 역할이 명확하고 개발자 사이의 이해가 일치합니다. 그래서 온보딩 비용이 낮고 빠르게 개발할 수 있습니다.
유지보수성
각 계층이 모듈화되어 있어 애플리케이션을 유지보수하고 업데이트하기 쉽습니다. 예를 들어 인터페이스 계약이 명확하다면 JPA에서 JOOQ로 전환하더라도 비즈니스 계층의 로직을 변경할 필요가 없습니다.
테스트 용이성
각 계층 간 통신에 인터페이스를 두면 의존성을 모킹(mock)하여 각 계층을 독립적으로 테스트할 수 있습니다.
재사용 가능성
계층 안의 구성 요소는 다른 프로젝트나 애플리케이션에서도 재사용할 수 있습니다. 예를 들어 비즈니스 계층의 고객 관리 모듈을 웹 앱과 모바일 앱이 함께 사용할 수 있고, 데이터 검증이나 로깅 같은 공통 로직도 여러 애플리케이션이 공유할 수 있습니다.
단점
성능 오버헤드
계층 간 호출 규칙이 엄격하면 일부 계층에서 처리할 일이 없어도 요청이 여러 계층을 순차적으로 거쳐야 합니다. 같은 프로세스 안이라면 계층 간 호출은 일반적인 메서드 호출이므로 비용은 작지만, 불필요한 호출과 코드가 늘어납니다. 반면 계층이 물리적으로 분리된 Tier라면 네트워크 통신이 추가되어 지연이 커질 수 있습니다. 예를 들어 고객 목록을 조회하는 단순한 요청도 표현, 비즈니스, 영속성 계층을 모두 통과합니다.
싱크홀 안티패턴
요청이 각 계층에서 의미 있는 처리 없이 그대로 통과만 하는 현상을 싱크홀 안티패턴이라고 합니다. 계층이 가치를 더하지 못하고 단순한 전달자가 되면 구조의 효과는 떨어지고 복잡도만 늘어납니다. 예를 들어 고객 데이터를 조회하는 요청이 중간 계층의 로직이나 변환 없이 표현 계층에서 데이터베이스까지 그대로 전달되는 경우입니다.
대규모 시스템에서의 결합도 증가
계층형 아키텍처는 느슨한 결합을 목표로 하지만, 암묵적인 의존성이 생기고 변경이 잦아지면 시간이 지나면서 계층이 강하게 결합될 수 있습니다. 그러면 계층을 교체하거나 업그레이드하기 어렵고, 한 계층의 수정이 다른 계층으로 연쇄적으로 번질 위험이 커집니다. 예를 들어 데이터베이스 스키마 변경으로 영속성 계층을 수정했는데 의도치 않게 비즈니스 계층까지 영향을 받을 수 있습니다.
기능 변경이 여러 계층에 걸침
기술(UI, 비즈니스 로직, 데이터베이스)을 기준으로 코드를 나누기 때문에 하나의 기능을 바꾸려면 여러 계층을 함께 수정해야 할 수 있습니다. 예를 들어 스트리밍 앱에서 영화에 만료일을 추가하려면 데이터베이스, 영속성, 비즈니스, 표현 계층을 모두 수정해야 합니다. 팀이 계층 기준으로 나뉘어 있다면 조율 비용도 늘어납니다.
어떤 경우에 채택할까
장단점을 바탕으로 계층형 아키텍처가 어울리는 경우와 그렇지 않은 경우를 정리해 보았습니다.
예산이나 시간의 제약
구현이 단순하고 대부분의 개발자가 이해하고 있으며 분산 시스템의 복잡성이 없습니다. 따라서 예산이 한정적이거나 일정이 촉박한 프로젝트에 알맞습니다. 반대로 기능별로 독립적인 배포와 확장이 중요하다면 추가적인 구조 고려가 필요합니다. 계층형으로 만든 모놀리식 애플리케이션도 인스턴스를 복제해 수평 확장할 수 있습니다. 하지만 일부 기능만 따로 배포하거나 확장할 수 없고, 하나의 프로세스로 실행되면 한 계층의 장애가 애플리케이션 전체로 번질 수 있습니다. 계층을 별도 Tier로 분리하면 확장성과 복원력은 좋아지지만 네트워크 통신으로 지연과 운영 복잡도가 늘어납니다.
변경의 범위
UI 개편이나 데이터베이스 마이그레이션처럼 대부분의 변경이 한 계층 안에서 끝난다면 다른 계층으로 미치는 영향을 줄일 수 있어 알맞습니다. 반대로 기능을 추가할 때마다 데이터베이스, 영속성, 비즈니스, UI 계층을 동시에 수정해야 한다면 구조가 오히려 부담이 됩니다.
팀 구조
프론트엔드, 백엔드, 데이터베이스 팀처럼 기술 전문성을 기준으로 팀이 나뉜 경우에 잘 맞습니다. 콘웨이의 법칙에 따라 팀 구조와 소프트웨어 구조가 닮는 경향이 있으므로, 팀 경계와 계층 경계가 일치해 협업 경로가 단순해집니다.
반대로 고객 관리나 상품 카탈로그처럼 도메인을 기준으로 팀을 구성했다면 기술 기준의 분할은 팀 구조와 어긋납니다. 하나의 기능을 추가할 때마다 여러 계층을 가로질러 수정해야 하므로 팀 간 조율 비용이 늘어납니다.
콘웨이의 법칙
조직 내부 구조와 의사소통 경로는 그 조직에서 개발되는 소프트웨어의 모듈 구조와 유사한 형태를 가지게 되는 경향이 있다는 법칙입니다.
설계의 방향
애플리케이션에 가장 적합한 아키텍처가 불분명할 때는 범용적이고 널리 이해된 구조이므로 출발점으로 삼을 수 있습니다. 전통적인 계층형은 비즈니스 계층이 데이터 접근 계층에 의존하는데, 계층 사이에 인터페이스를 두어 이 의존 방향을 뒤집으면 도메인을 중심에 두는 구조(Clean Architecture, 헥사고날 아키텍처)로 발전시킬 수 있습니다.
요청의 처리 방식
각 계층에서 의미 있는 처리가 이루어진다면 문제가 없습니다. 하지만 대부분의 요청이 처리 없이 계층을 통과만 한다면 싱크홀 안티패턴으로 불필요한 복잡도만 늘어나므로, 계층형 아키텍처가 해당 용도에 적합하지 않다는 신호로 볼 수 있습니다.
MVC 패턴과 계층형 아키텍처
계층형 아키텍처를 공부하다 보면 MVC 패턴과 헷갈리기 쉽습니다. 둘의 관계를 정리해 보겠습니다.
MVC(Model-View-Controller) 패턴은 사용자 상호작용과 UI 구조를 어떻게 구성할 것인가에 초점을 맞춥니다.
- Model: 애플리케이션의 데이터 관리
- View: 모델의 데이터를 표현
- Controller: 사용자 입력 처리
반면 계층형 아키텍처는 시스템의 책임과 의존 관계에 따라 기능을 묶고, 이를 통해 계층 간 결합도와 변경 영향을 관리하는 데 초점을 맞춥니다.
스프링 부트 기준으로 본다면 표현, 비즈니스, 영속성 계층을 각각 @Controller, @Service, @Repository 등의 어노테이션으로 명시해 나눌 수 있습니다.
이러한 책임에 따라 패키지를 presentation, application, infrastructure처럼 구성하면(비즈니스 계층은 application, 영속성 계층은 infrastructure에 대응합니다) 계층형 아키텍처로 볼 수 있습니다.
그리고 해당 시스템이 Thymeleaf를 통해 사용자와 상호작용하는 서버 사이드 애플리케이션이라면, MVC 패턴의 관점에서 View는 Thymeleaf, Controller는 @Controller라고 볼 수 있습니다.
Model은 구분이 필요합니다. org.springframework.ui.Model은 뷰에 전달할 데이터를 담는 객체이고, MVC에서 말하는 Model은 UI를 모르는 도메인 객체를 뜻합니다(Martin Fowler의 GUI Architectures 참고). 따라서 MVC의 Model은 표현 계층 안이 아니라 도메인(비즈니스) 계층과 연결될 수 있습니다.
즉, MVC는 사용자 인터페이스와 관련된 책임을 분리하는 패턴으로 표현 계층에서 활용할 수 있고, 계층형 아키텍처는 시스템 전체의 책임을 나누는 방식이므로 이 둘은 배타적인 관계가 아니며 함께 사용할 수 있습니다. 다만 두 패턴의 구성 요소가 일대일로 대응하지는 않습니다.
계층형 아키텍처는 구조가 단순하고 널리 이해되고 있어서, 어떤 아키텍처가 적합한지 아직 불분명할 때 출발점으로 삼기 좋은 구조입니다. 다만 대부분의 요청이 계층을 의미 없이 통과만 하거나, 기능 하나를 추가할 때마다 여러 계층을 가로질러 수정해야 한다면 구조를 다시 살펴봐야 한다는 신호로 볼 수 있습니다.
댓글남기기