Skip to Content

Step 06. 레이어드 아키텍처 — 최초의 정형화된 구조

만화로 보는 요약 — 먼저 읽어보세요

만화로 보는 요약 — 먼저 읽어보세요

레이어드는 함께 바뀌는 책임을 수평 층으로 나누고, 의존성 규칙을 지켜 변경의 파급을 한 층 안에 가두려는 첫 번째 정형 구조다.

면접 실전 질문: ① 레이어드는 어떤 기준으로 나누는가? ② 의존성 규칙을 어기면 어떤 일이 생기는가? ③ 레이어드가 진흙공으로 퇴화하는 이유는?


이 장의 질문: 인류가 진흙에 맞서 세운 최초의 질서는 무엇이었고, 무엇을 해결했는가? 그리고 그 질서가 반세기 뒤 “고통”의 대명사가 된 이유는 — 정말 레이어드가 잘못해서인가?


1. 최초의 질서 — 1968년, THE 시스템

레이어드를 “구식 아키텍처”라 부르는 사람은 그 뿌리가 얼마나 깊은지 모르고 하는 말이다. 계층화는 아키텍처라는 단어가 소프트웨어에 정착하기도 전에 있었다.

1968년 — 가르미슈 회의와 같은 해다 — Dijkstra는 「The Structure of ‘THE’-Multiprogramming System」에서 운영체제 하나를 여섯 개의 **수준(level)**으로 쌓아 올린 설계를 발표한다. 맨 아래에 프로세서 스케줄링, 그 위로 메모리 관리, 콘솔, 입출력, 사용자 프로그램, 그리고 맨 위의 운영자(operator)까지 여섯 수준. 규칙은 단 하나였다: 각 수준은 자기보다 아래 수준의 서비스만 사용한다.

동기가 무엇이었는지가 중요하다. 성능도 재사용도 아니고 검증 가능성이었다. 수준 0이 올바름을 증명하면, 수준 1은 “수준 0이 올바르다”는 전제 위에서 증명할 수 있다. 시스템 전체를 한 번에 이해하는 대신, 한 번에 한 층씩 정복한다 — Step 02에서 본 관심사의 분리를, 위계라는 형태로 구현한 것이다. 이 아이디어는 강력했고, OSI 7계층 모델(네트워크를 물리 계층부터 응용 계층까지 쌓은 국제 표준)으로, 그리고 1996년 POSA(『Pattern-Oriented Software Architecture』)의 Layers 패턴으로 정식화되며 소프트웨어 설계의 기본 어휘가 됐다.

핵심 통찰을 기록해두자. 계층화의 본질은 추상화의 사다리다 — 각 층은 아래층의 복잡성을 감추고 위층에 더 단순한 어휘를 제공한다. TCP 위에서 HTTP를 쓸 때 패킷 재전송을 생각하지 않는 것, 그것이 잘 작동하는 계층이다.

2. 엔터프라이즈의 표준이 되다

2000년대에 이 아이디어는 업무 시스템의 사실상 표준 형태로 자리잡는다. 우리 모두가 아는 그 그림이다:

┌──────────────────────┐ │ Presentation (표현) │ ← 화면, API 엔드포인트 ├──────────────────────┤ │ Business (업무 로직) │ ← 서비스, 도메인 규칙 ├──────────────────────┤ │ Persistence (영속) │ ← 리포지토리, DAO, ORM ├──────────────────────┤ │ Database │ ← 스키마, 그 자체 └──────────────────────┘ ↓ 모든 화살표가 아래를 향한다

Fowler의 PoEAA(2002)가 표현-도메인-데이터소스의 3분할을 정리했고, 스프링 같은 프레임워크가 @Controller/@Service/@Repository라는 애노테이션으로 이 구조를 아예 언어처럼 만들었다.

왜 이렇게 성공했는가? 세 가지가 맞물렸다. 이해하기 쉽다 — 요청이 위에서 아래로 흐르는 그림은 설명이 필요 없다. 기술과 정렬된다 — UI 기술, 비즈니스 언어, DB 기술이 실제로 다르므로, 기술 경계로 자르는 것은 자연스럽다. 그리고 팀과 정렬된다 — 프론트엔드 팀, 백엔드 팀, DBA라는 당시(그리고 지금도 흔한) 조직 구조와 층이 일대일로 맞았다. 조직 구조가 아키텍처를 밀어내는 이 현상에는 이름이 있다 — Conway의 법칙, 뒤에서 정면으로 다룬다.

진흙공(앞 장)과 비교하면 레이어드가 가져다준 것은 분명하다. 최소한의 방향성(호출은 아래로), 최소한의 역할 분담(화면 코드와 SQL이 한 파일에 있지 않다), 그리고 새 개발자가 코드를 찾을 수 있는 지도. 이것은 진짜 진보였다. 문제는 이 진보가 감춘 두 개의 병이다.

3. 화살표를 보라 — 첫 번째 병

Step 03의 렌즈를 꺼내자. 위 그림에서 모든 화살표는 아래로, 최종적으로 데이터베이스를 향한다. 의존성 그래프에서 DB가 가장 안정된 자리(모두가 의존하고, 아무에게도 의존하지 않는 자리)에 앉아 있다.

그런데 안정 추상 원칙(SAP)을 기억하는가 — 안정된 자리에는 추상적인 것이 앉아야 한다. 레이어드는 정반대다. 시스템에서 가장 구체적인 코드 — 스키마를 그대로 베낀 엔티티와 영속 계층, 그 뒤의 벤더별 SQL — 가 모두가 의존하는 가장 안정된 자리에 앉아 있다. 그 결과:

  • 스키마가 도메인을 정의한다. 새 기능의 설계가 “어떤 개념이 필요한가”가 아니라 “어떤 테이블을 만들까”로 시작된다. 업무 개념이 정규화 규칙에 맞춰 절단되고, 도메인 모델은 테이블의 거울상이 된다.
  • 업무 규칙이 세부사항의 인질이 된다. “주문 한도 검증”을 테스트하려면 DB를 띄워야 한다 — 정책(가장 순수해야 할 것)이 세부사항(가장 잘 바뀌는 것)에 소스 수준에서 묶여 있기 때문이다.
  • 가장 비싼 결정이 가장 먼저 내려진다. Step 02의 기준(좋은 아키텍처 = 바뀔 결정을 미룰 수 있는 구조)으로 보면, 레이어드는 DB 선택과 스키마라는 되돌리기 비싼 결정을 프로젝트 첫 주에, 도메인을 가장 모르는 시점에 강제한다.

요컨대 레이어드는 화살표에 방향을 주는 데는 성공했지만, 그 방향이 틀렸다. 이 문장이 이후 헥사고날과 클린이 태어나는 자궁이다.

4. 두 번째 병 — 로직은 어디에나 있고 어디에도 없다

두 번째 병은 층 사이가 아니라 층 에서 자란다. 업무 규칙이 어느 층에도 확실히 살지 않고 스며드는 현상 — 비즈니스 로직 누수다. 임상 증상은 셋이다.

증상 1: 빈혈 도메인 모델(Anemic Domain Model). Fowler가 2003년에 명명한 안티패턴. 코드로 보는 게 빠르다.

// 빈혈: 데이터 자루. 규칙이 없다. public class Order { private Long id; private OrderStatus status; private BigDecimal amount; // getter, setter 뿐... } public class OrderService { public void cancel(Long orderId) { Order order = orderRepository.findById(orderId); if (order.getStatus() == SHIPPED) // 규칙이 서비스에 산다 throw new IllegalStateException(...); order.setStatus(CANCELLED); // 객체는 시키는 대로 할 뿐 orderRepository.save(order); } }
// 대조: 규칙이 개념과 함께 산다. public class Order { public void cancel() { if (this.status == SHIPPED) throw new CannotCancelShippedOrder(this.id); this.status = CANCELLED; } }

빈혈 모델의 문제는 미학이 아니다. Order의 상태를 바꾸는 규칙이 OrderService에 산다면, OrderBatchServiceAdminOrderController에도 살 수 있다 — 그리고 반드시 살게 된다. 불변식(invariant)을 지킬 단일 장소가 없다. setter가 열려 있는 객체는 어떤 규칙도 강제할 수 없다. 객체지향의 옷을 입은 절차적 코드다 — Fowler는 이것이 객체지향의 기본 이념에 반하며, “도메인 모델의 비용은 다 내면서 그 이득은 하나도 가져가지 않는” 구조라고 썼다.

증상 2: 검증의 삼중 분산. 같은 규칙(“주문 금액은 양수”)이 컨트롤러의 요청 검증에, 서비스의 방어 코드에, DB의 CHECK 제약에 세 번 적힌다. 세 곳이 미묘하게 어긋나는 순간부터가 진짜 문제다 — 규칙의 진실이 어디 있는지 아무도 모르게 된다.

증상 3: 싱크홀. 요청이 컨트롤러 → 서비스 → 리포지토리를 통과하는데 어느 층도 실질적인 일을 하지 않는 현상(Richards가 architecture sinkhole이라 부른 것). 층이 가치가 아니라 통행세만 걷는다. 시스템 요청의 대다수가 싱크홀이라면, 그 층 구조는 관성으로만 존재하는 것이다. Richards의 완화책은 층을 닫힘(반드시 통과)과 열림(우회 허용)으로 구분하는 것이다(layers of isolation) — 다만 이 전술은 통행세를 줄일 뿐, 다음 절의 근본 문제까지 건드리지는 못한다.

5. 진짜 병인 — 전단층의 오배치

두 병을 하나로 꿰는 진단이 앞 장에 이미 있다. 전단층: 변경 속도가 같은 것끼리 묶어라.

레이어드는 층을 기술로 나눴다 — 표현 기술, 업무 로직, 영속 기술. 그런데 실제 변경은 어떻게 오는가? “주문에 부분 취소 기능을 추가해주세요” — 기능(도메인) 단위로, 수직으로 온다. 이 요구사항 하나가 컨트롤러, DTO, 서비스, 엔티티, 리포지토리, 스키마를 전부 관통한다. 층은 수평인데 변경은 수직이다. 전단층이 변경의 결과 직각으로 놓인 것이다.

이것이 “레이어드의 고통”의 기하학적 정체다. 관심사는 분리됐지만 — 기술이라는 관심사가 분리됐을 뿐, 변경이라는 힘의 결은 따르지 않았다. Parnas가 1972년에 순서도 기준 분해를 반박하며 한 말이 그대로 재현된다: 자연스러워 보이는 절단선이 변경의 절단선과 다르면, 모든 변경이 모든 조각을 건드린다.

(공정한 각주 하나. 이 병은 “레이어드 vs 수직 분할 중 택일”의 문제가 아니다 — 수직으로 자른 조각 에서 다시 수평 층이 나타나는 게 보통이고, 실제 논점은 어느 절단이 1차인가다. 도메인이 1차이고 기술이 2차인 구조, 그것이 다음 장들의 방향이다.)

6. 변호 — 그럼에도 레이어드가 이기는 곳

여기까지 읽으면 레이어드를 장례 치를 분위기지만, 목차에서 예고한 대로 이 책은 그 결론을 거부한다. 대다수 시스템에서 레이어드는 여전히 최적해다. 품질 속성의 언어로 변호하면:

  • 도메인 복잡도가 낮다면 — 규칙이 거의 없는 CRUD 시스템에서 빈혈 모델은 병이 아니라 정직함이다. 지킬 불변식이 없는데 리치 도메인 모델을 세우는 것이야말로 과잉 구조(앞 장의 묘지행)다.
  • 단순성이라는 속성 — 레이어드는 설명이 필요 없고, 채용 시장의 모두가 알고, 프레임워크가 기본으로 지원한다. 인지 비용이 가장 싼 구조다.
  • 팀이 작고 수명이 짧다면 — 화살표가 틀렸다는 비용은 시스템과 팀이 커질 때 복리로 청구된다. 그 복리가 시작되기 전이라면, 지불하지 않은 이자를 걱정할 이유가 없다.

Step 04의 제1법칙을 상기하라 — 모든 것은 트레이드오프다. 레이어드는 “단순성·친숙함·빠른 출발”을 얻고 “변경용이성(도메인 축)·테스트가능성”을 지불한 거래다. 그 거래가 남는 장사인 상황은, 업계 전체로 보면 오히려 다수다. 레이어드가 문제가 되는 것은 레이어드를 골라서가 아니라, 거래 조건이 바뀌었는데(도메인이 복잡해졌는데) 계약을 갱신하지 않아서다.


7. 흔한 오해 바로잡기

오해 1: “레이어드는 구식이다.” 계층화 원리는 1968년생이지만 OSI, OS 커널, 그리고 당신이 오늘 쓴 스프링 애플리케이션 안에서 여전히 작동한다. 구식인 것은 레이어드가 아니라 “모든 시스템에 같은 구조”라는 사고다.

오해 2: “패키지를 controller/service/repository로 나눴으니 관심사 분리는 끝났다.” 디렉토리는 구조가 아니다. Step 03의 언어로 — 문제는 폴더가 아니라 화살표다. 서비스가 엔티티(=테이블 거울상)를 직접 주무르고 규칙이 세 층에 스며 있다면, 폴더가 몇 개든 관심사는 분리되지 않았다.

오해 3: “빈혈 도메인 모델은 항상 안티패턴이다.” Fowler의 명명은 “도메인 모델이라 주장하면서 빈혈인 것”을 겨눈다. 규칙이 없는 도메인에서 데이터 자루 + 트랜잭션 스크립트는 정당한 선택이고, Fowler 자신(PoEAA)이 트랜잭션 스크립트를 패턴으로 수록했다. 죄는 빈혈이 아니라 복잡한 불변식을 가진 도메인의 빈혈이다.

오해 4: “층이 많을수록 잘 설계된 것이다.” 싱크홀을 기억하라. 가치를 더하지 않는 층은 통행세다. 층 하나를 추가할 때의 질문은 “표준 구조에 있으니까”가 아니라 “이 층이 감추는 복잡성이 무엇인가”여야 한다 — 추상화의 사다리(1절)라는 본질로 돌아가는 질문이다.


8. 요약, 그리고 다음 장으로

  • 계층화는 구식이 아니라 최초의 정형화된 구조다(Dijkstra THE, 1968). 본질은 추상화의 사다리이고, 동기는 검증 가능성이었다.
  • 엔터프라이즈 3계층의 성공 비결: 이해 용이성, 기술 정렬, 팀 정렬. 진흙공 대비 방향성과 지도를 줬다.
  • 병 1 — 화살표가 틀렸다: 가장 구체적인 것(DB)이 가장 안정된 자리에 앉아, 스키마가 도메인을 정의하고 정책이 세부사항의 인질이 된다(SAP 위반).
  • 병 2 — 로직 누수: 빈혈 도메인 모델, 검증의 분산, 싱크홀. 불변식을 지킬 단일 장소가 없다.
  • 진짜 병인: 전단층의 오배치 — 층은 기술 따라 수평인데, 변경은 도메인 따라 수직으로 온다.
  • 그럼에도 낮은 도메인 복잡도·작은 팀·짧은 수명에서 레이어드는 여전히 최적해다. 문제는 선택이 아니라 갱신하지 않은 계약이다.

다음 장 예고: 병의 진단이 나왔으니 처방의 역사가 시작된다. 첫 처방은 구조가 아니라 관점의 교정이었다 — “소프트웨어의 심장은 기술이 아니라 도메인이다.” 2003년, Eric Evans의 DDD. 주의할 것: DDD는 아키텍처 스타일이 아니다. 그것은 렌즈다.


원전

연도문헌이 장에서의 역할
1968Dijkstra, The Structure of the ‘THE’-Multiprogramming System (CACM)계층화의 원전 — 검증 가능성을 위한 수준 분리
1984ISO 7498, OSI Reference Model계층화의 표준화 사례
1996Buschmann 외, Pattern-Oriented Software Architecture Vol.1Layers 패턴의 정식 기술
2002Fowler, Patterns of Enterprise Application Architecture표현-도메인-데이터소스 계층, 트랜잭션 스크립트/도메인 모델
2003Fowler, AnemicDomainModel (bliki)빈혈 도메인 모델 명명
2020Ford & Richards, Fundamentals of Software Architecture싱크홀 안티패턴, 레이어드의 현대적 평가
Last updated on