프론트엔드 개발자도 한 번쯤은 계층형 아키텍처(Layered Architecture)라는 이름을 들어봤을 겁니다. 표현 계층, 비즈니스 계층처럼 낯선 용어로 설명되는 경우가 많지만, 관심사를 나누고 의존 방향을 통제한다는 원리 자체는 어떤 코드에도 똑같이 적용됩니다.
이 글에서는 계층형 아키텍처의 개념, 토폴로지, 표준 계층과 그 역할, 계층 격리의 개방/폐쇄, 핵심 특징, 싱크홀 안티패턴, 아키텍처 특성 평가, 장단점, 적합한 상황까지 순서대로 정리합니다.
1. 계층형 아키텍처란
계층형 아키텍처는 시스템을 기술적 관심사에 따라 여러 계층으로 나누는, 가장 널리 쓰이는 아키텍처 스타일입니다. 마이크로서비스나 이벤트 기반 아키텍처처럼 정교한 설계가 필요 없는 대부분의 서비스에서 기본값으로 선택됩니다.
계층형 아키텍처는 화면, 업무 규칙, 데이터 접근처럼 서로 다른 기술적 역할을 계층 단위로 분리하고, 각 계층이 무엇을 책임지고 어디까지 관여할 수 있는지를 정합니다. 계층 사이의 호출은 정해진 방향으로만 흐릅니다.
2. 기술적 관심사에 따른 분할
계층형 아키텍처는 시스템을 주문, 회원, 결제 같은 비즈니스 기능보다 기술적인 역할을 기준으로 나눕니다.
계층의 수와 유형에 제약은 없지만 보통 4개의 계층으로 구성되어 다음의 역할을 맡습니다.
- 표현 계층: ui 및 브라우저 통신 로직 처리(프론트에서 페이지·컴포넌트·폼 같은 UI 코드가 이 역할)
- 비즈니스 계층: 요청과 관련된 비즈니스 규칙을 실행합니다.(커스텀 훅이 비슷한 역할을 합니다.)
- 영속성 계층: 비즈니스 계층의 요청을 받아 데이터베이스의 데이터를 조회·저장·수정·삭제합니다.(
fetch, Axios 같은 API 클라이언트나localStorage접근 코드가 여기에 대응합니다.) - 데이터베이스 계층: 실제 데이터를 저장하고 쿼리와 트랜잭션을 처리합니다.(프론트에선 이 계층에 직접 관여하지 않고, 외부 데이터 접근 계층 너머의 백엔드 API가 이 역할을 대신합니다.)
토폴로지
(* 계층이 배치되고 서로 연결되는 방식을 토폴로지라고 부릅니다.)
표현 계층 (UI)
↓
비즈니스 계층 (커스텀 훅)
↓
영속성 계층 (API 클라이언트)
↓
데이터베이스 계층 (백엔드 API)각 계층은 수행하는 작업을 추상화하여 관심사가 분리됩니다.
이러한 관심사의 분리 덕분에 효과적인 역할 및 책임 모델을 구축하기가 쉽지만, 전체 시스템이 변화에 빠르게 대응이 어렵습니다. (‘고객’이라는 비즈니스 도메인이 모든 계층에 걸쳐 분산되다보니, 고객 도메인의 변경 사항을 적용하기가 어렵습니다.)
3. 아키텍처 세부사항
계층은 열려 있을 수도, 닫혀 있을 수도 있습니다.
닫힌 계층: 요청이 반드시 바로 아래 계층으로만 통과
표현 → 비즈니스 → 영속성 → 데이터베이스- 계층을 건너뛰고 접근할 수 없음
- 상위 계층이 하위 계층의 내부 구현을 알지 못함
- 인터페이스를 통해서만 통신하고, 계층의 변경 사항이 다른 계층에 영향을 주지 않음.
닫힌 계층은 계층 간 격리를 촉진하고 변경 사항을 격리하는데 도움이 되지만 열린 계층을 사용하는 것이 합리적인 경우도 있습니다.
안티패턴 : 아키텍처 싱크홀
예를 들어 표현 계층이 사용자 요청에 응답해 기본 고객 데이터(이름, 주소)를 검색하는 경우를 생각해보겠습니다.
표현 계층 → 비즈니스 계층 → 영속성 계층
(요청) (그대로 전달) (SQL 쿼리 실행)비즈니스 계층은 이 요청을 검증하거나 다른 데이터와 조합하지 않고, 그대로 영속성 계층에 전달해 SQL 쿼리만 실행시킵니다. 이렇게 별다른 처리나 로직 없이 요청을 그대로 통과시키기만 하는 계층을 아키텍처 싱크홀(Sinkhole)이라고 부릅니다.
이런 요청이 20% 이하라면 괜찮지만, 전체 요청의 80%를 넘어설 정도로 반복된다면 열린 계층을 허용하거나, 계층형 아키텍처가 적절하지 않다는 지표일 수 있습니다.
4. 주요 강점과 단점
4.1 장점: 단순함
구현과 이해가 쉽다
요청이 위에서 아래로만 흐르기 때문에, 코드를 처음 보는 사람도 흐름을 예측할 수 있습니다. 버그가 생기면 OrderForm(UI) → useCreateOrder(애플리케이션 로직) → orderPolicy(도메인 로직) → orderApi(외부 데이터 접근) 순서로 따라가기만 하면 됩니다.
계층을 건너뛰거나 역방향으로 호출하는 경로가 없어서, “이 값이 왜 이렇게 됐는지”를 추적하는 경로가 하나로 정해져 있습니다.
초기 도입 비용이 낮다
모듈 페더레이션(독립적으로 배포된 앱끼리 런타임에 서로의 모듈을 가져다 쓰는 기술)이나 마이크로 프론트엔드용 빌드 파이프라인을 갖추지 않아도, components/, hooks/, lib/, api/ 같은 폴더 구조만으로 바로 적용할 수 있습니다. 사이드 프로젝트나 팀 규모가 작을 때 특히 부담이 적습니다.
협업과 온보딩이 쉽다
계층별로 역할이 정해져 있어서 “이 코드가 어디에 있어야 하는가”를 예측할 수 있습니다. 새로 합류한 팀원도 orderApi.ts, useCreateOrder.ts, orderPolicy.ts, OrderForm.tsx 같은 파일 이름과 폴더 위치만 보고 API 호출, 상태 관리, 업무 규칙, 화면 로직이 각각 어디 있는지 짐작할 수 있습니다. eslint-plugin-boundaries 같은 도구로 “컴포넌트에서 API 함수를 직접 호출하지 않는다” 같은 규칙을 정적으로 강제할 수 있다는 점도 코드 리뷰 부담을 줄여줍니다.
다만 이 장점들은 시스템 규모가 커질수록 상쇄됩니다. 계층이 늘어나고 계층 사이를 오가는 코드가 많아지면, 단순함이 주던 이점이 사라집니다.
4.2 단점: 낮은 탄력성·확장성·내결함성
탄력성이 낮은 이유
순간적으로 몰리는 트래픽에 맞춰 자원을 빠르게 늘렸다 줄이는 능력을 탄력성이라고 합니다. 계층형 아키텍처는 독립적으로 배포 가능한 단위가 1이기 때문에 이 능력이 떨어집니다. 검색, 대시보드, 결제가 모두 하나의 SPA 번들로 묶여 있는 경우를 예로 들어보면,
[ SPA 번들 (퀀텀 1) ]
├─ 검색
├─ 대시보드
└─ 결제- 대시보드 차트 연산이 무겁다면 Web Worker(다중 스레딩)로 연산을 위임하고
postMessage(내부 메시징)로 결과를 받을 수 있습니다. - 하지만 이건 같은 번들, 같은 배포 단위 안에서 일어나는 일입니다.
- 대시보드 트래픽만 늘었다고 그 부분만 따로 배포하거나 서버를 늘릴 수는 없습니다. 번들 전체가 하나의 퀀텀이라, 확장도 배포도 항상 전체 단위로만 가능합니다.
이 한계를 넘으려면 검색·대시보드·결제를 마이크로 프론트엔드처럼 각각 독립 배포 단위로 쪼개야 합니다. 그러면 퀀텀이 여러 개로 늘어나 대시보드만 따로 확장·배포할 수 있지만, 이는 계층형 아키텍처 자체를 벗어나는 선택입니다.
확장성이 낮은 이유
새로운 기능이나 모듈을 추가하기 쉬운 정도를 확장성이라고 합니다. 계층형 아키텍처는 기능이 아니라 기술 역할을 기준으로 계층을 나눴기 때문에 이 부분이 약합니다. “장바구니에 쿠폰을 적용한다”는 기능 하나를 추가한다고 해도, 다음처럼 계층마다 코드를 손대야 합니다.
- UI 계층: 장바구니 컴포넌트에 쿠폰 입력창 추가
- 애플리케이션 로직 계층:
useCart훅에 쿠폰 적용 흐름 추가 - 도메인 로직 계층: 쿠폰 할인 계산 정책 추가
- 외부 데이터 접근 계층: 쿠폰 검증 API 호출 추가
“쿠폰”이라는 기능 하나가 파일 하나에 모여 있지 않고 계층별로 흩어져 있다 보니, 기능 단위로 코드를 찾고 수정하기가 번거롭습니다.
내결함성이 낮은 이유
일부 장애가 전체로 번지지 않도록 막는 능력을 내결함성이라고 합니다. 모든 계층이 같은 번들, 같은 런타임 안에서 함께 실행되다 보니 계층형 아키텍처는 이 부분도 취약합니다.
결제 로직에서 처리되지 않은 예외가 발생하면, React 기준으로 에러 바운더리가 없는 구간까지 렌더링이 함께 멈출 수 있습니다. 결제와 전혀 관계없는 검색이나 대시보드까지 같이 영향을 받게 됩니다. 에러 바운더리로 완화할 수 있지만, 계층이나 기능을 완전히 격리하지는 못합니다.