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에 산다면, OrderBatchService와 AdminOrderController에도 살 수 있고, 반드시 살게 된다. 불변식(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