Step 07. 도메인이 중심에 서다 — DDD라는 렌즈

만화로 보는 요약 — 먼저 읽어보세요
DDD는 같은 단어의 다른 뜻을 도메인 언어와 바운디드 컨텍스트로 분리하고, 애그리거트로 핵심 규칙을 지킨다.
면접 실전 질문: ① 유비쿼터스 언어란 무엇인가? ② 바운디드 컨텍스트는 왜 필요한가? ③ 애그리거트는 무엇을 보호하는가?
이 장의 질문: 앞 장의 병 — 스키마가 도메인을 정의하고, 규칙이 어디에나 스며 있는 — 에 대한 첫 처방은 왜 새로운 구조가 아니라 관점의 교정이었는가? 그리고 DDD를 아키텍처 스타일 목록에 넣으면 왜 범주 오류인가?
1. 범주 주의 — 이것은 스타일이 아니라 렌즈다
시작하기 전에 이 장의 위상부터 분명히 하자. 레이어드(앞 장)와 헥사고날(다음 장)은 아키텍처 스타일이다 — “박스와 화살표를 이렇게 배치하라”는 구조적 답. 그 사이에 낀 이 장의 DDD(Domain-Driven Design)는 그 목록의 일원이 아니다. DDD는 설계 방법론이다 — “무엇을 중심에 놓고, 어떻게 발견하고, 어디에 경계를 그을지”를 다루는 사고의 렌즈.
층위가 다르다는 것은 배타적이지 않다는 뜻이기도 하다. DDD는 레이어드 위에서도, 헥사고날 위에서도, 모놀리스에서도 마이크로서비스에서도 실천할 수 있다. 거꾸로 말하면 — 헥사고날 구조를 완벽히 갖추고도 DDD 없이(도메인에 대한 아무 통찰 없이) 빈 껍데기를 만들 수 있다. 이 장이 스타일들의 행렬 한가운데 있는 이유는, DDD가 이후 모든 스타일에게 “무엇을 지킬 것인가”라는 목적어를 공급하기 때문이다. 헥사고날이 벽을 세우는 기술이라면, DDD는 그 벽이 무엇을 지켜야 하는지 알려주는 렌즈다.
2. 갈림길 — 트랜잭션 스크립트 vs 도메인 모델
DDD로 가기 전에, 2002년 Fowler의 PoEAA가 정리해둔 갈림길 하나를 통과해야 한다. 업무 로직을 조직하는 두 가지 기본 패턴이다.
- 트랜잭션 스크립트(Transaction Script): 요청 하나당 절차 하나. “주문 취소”라는 요청이 오면, 검증하고 → 상태 바꾸고 → 저장하는 절차를 위에서 아래로 쓴다. 앞 장의 빈혈 모델 + 서비스 조합이 정확히 이것이다.
- 도메인 모델(Domain Model): 업무 개념 하나당 객체 하나. 규칙과 데이터가 개념 단위로 동거하고, 절차는 객체들의 협력으로 표현된다.
Fowler의 분석에서 중요한 것은 어느 쪽이 옳은가가 아니라 곡선의 교차점이다. 도메인 로직의 복잡도가 낮을 때는 트랜잭션 스크립트가 압도적으로 싸다 — 읽기 쉽고, 곧바로 쓰고, 배울 것이 없다. 그러나 복잡도가 올라가면 절차들 사이에 규칙이 중복되기 시작하고(앞 장에서 본 “불변식을 지킬 단일 장소의 부재”), 어느 지점을 지나면 도메인 모델의 초기 투자가 역전한다. 문제는 그 교차점을 지나고 있다는 사실을 지나고 나서야 알게 된다는 것 — 그리고 그때는 이미 규칙이 수십 개의 스크립트에 흩어져 있다는 것이다.
2003년, 이 갈림길에서 “복잡한 도메인이라면 도메인 모델 쪽 길”을 선언하고 그 길의 지도를 그린 책이 나온다.
3. Evans의 선언 — 복잡성은 심장에 있다
Eric Evans의 『Domain-Driven Design』(2003)의 부제를 천천히 읽을 필요가 있다: Tackling Complexity in the Heart of Software — 소프트웨어의 심장에 있는 복잡성과 싸우기.
선언의 내용은 이렇다. 소프트웨어의 진짜 어려움은 프레임워크도 DB도 분산도 아니라 도메인 그 자체 — 업무의 개념, 규칙, 예외, 관행 — 에 있다(Brooks의 본질적 복잡성이 들리면 정확히 들은 것이다). 따라서 팀의 가장 좋은 지성은 기술이 아니라 도메인 모델링에 투입되어야 하고, 코드의 중심에는 기술이 아니라 도메인이 앉아야 한다. 앞 장의 병 — 스키마가 도메인을 정의하는 전도(顚倒) — 에 대한 정확한 반명제다.
이 선언을 실행하는 첫 번째 도구가 **유비쿼터스 언어(Ubiquitous Language)**다. 현업은 “정산 마감”이라 말하는데 코드에는 processBatchType3()이 있다면, 그 사이의 번역마다 오해가 산다. 유비쿼터스 언어는 도메인 전문가와 개발자가 하나의 언어를 합의하고, 그 언어가 대화에도, 문서에도, 클래스와 메서드 이름에도 그대로 나타나게 하라는 규율이다. SettlementClosing.close()가 현업의 문장과 일치할 때, 코드 리뷰가 도메인 리뷰가 된다. 모델과 코드가 분리된 산출물이 아니라 같은 것이 되는 것 — Evans는 이것을 모델 주도 설계(model-driven design)라 불렀다.
4. 전략적 설계 — 하나의 모델이라는 환상을 버려라
DDD의 절반은 개념 모델링 기법이지만, 후대에 더 중요해진 것은 나머지 절반 — **전략적 설계(strategic design)**다. 출발점은 뼈아픈 관찰이다: 여러 팀과 여러 하위 도메인이 얽히는 규모에서, 전사(全社) 통일 모델은 유지 비용이 폭발하며 붕괴한다.
“주문”이라는 단어를 생각하라. 판매팀에게 주문은 가격·프로모션·결제수단의 덩어리다. 물류팀에게 주문은 배송지·박스 크기·운송장이다. 정산팀에게 주문은 수수료율과 정산 주기가 붙은 금액 항목이다. 셋을 하나의 Order 클래스로 통일하려는 순간, 세 팀의 요구가 한 클래스에서 충돌하고 필드 40개짜리 괴물이 태어난다 — 모두를 만족시키려는 모델은 아무도 만족시키지 못한다.
Evans의 답이 **바운디드 컨텍스트(Bounded Context)**다: 하나의 모델(그리고 하나의 유비쿼터스 언어)이 유효한 경계를 명시하라. 판매 컨텍스트의 주문과 정산 컨텍스트의 주문은 같은 단어를 쓰는 다른 개념이며, 각자의 경계 안에서 각자 정합적이면 된다. 통일하지 말고 분리하고, 관계를 명시하라.
관계의 명시가 **컨텍스트 맵(Context Map)**이다. 두 컨텍스트가 모델을 일부 공유하는가(Shared Kernel), 한쪽이 다른 쪽의 모델을 그대로 따르는가(Conformist), 아니면 — 가장 중요한 패턴 — 상대의 모델이 내 경계 안으로 새어 들어오지 못하도록 번역 계층을 세우는가(Anticorruption Layer, ACL). ACL이 낯익다면 맞다: 앞에서 본 “양탄자 밑으로(진흙의 격리)“의 정제된 후예이고, 레거시나 외부 시스템과 접할 때 도메인을 지키는 표준 도구다.
눈치챘겠지만 바운디드 컨텍스트는 Step 03의 언어로 완벽히 번역된다 — 모델의 의미가 갈라지는 곳에 그은 경계이며, 변경의 이유가 갈리는 곳에 선을 그으라던 Parnas의 기준을 도메인 의미론 차원으로 끌어올린 것이다. 그리고 예고 하나: 십여 년 뒤 마이크로서비스 진영이 “서비스를 어디서 잘라야 하는가”라는 난제에 부딪혔을 때, 꺼내 든 답이 바로 이 개념이다.
5. 전술적 패턴 — 애그리거트, 불변식의 요새
DDD의 나머지 절반인 **전술적 패턴(tactical patterns)**은 경계 안쪽에서 모델을 코드로 세우는 재료들이다. 엔티티(식별자로 추적되는 개념), 값 객체(값으로만 비교되는 불변 개념 — Money, Address), 리포지토리, 도메인 서비스, 도메인 이벤트. 금융 도메인이라면 값 객체 하나는 특별히 눈여겨보라 — 금액을 원시 BigDecimal이 아니라 Money로 세우는 순간, 통화와 반올림 규칙이 코드 곳곳에 흩어지는 대신 한 개념에 모인다. 암묵적 개념을 명시적으로 만드는 것, 그것이 값 객체의 핵심이다. 다만 목록의 암기보다 하나의 패턴을 깊이 이해하는 것이 낫다. 심장은 **애그리거트(Aggregate)**다.
애그리거트는 “함께 변경되어야 하는 객체들의 묶음”이며, 정확히는 불변식의 경계이자 트랜잭션의 경계다. 금융 예제가 가장 선명하다. “주문의 항목 합계는 주문 총액과 일치해야 한다”는 불변식이 있다면, Order와 OrderLine들은 한 애그리거트다 — 항목 하나를 바꾸면서 총액을 안 바꾸는 중간 상태가 외부에 관측되어서는 안 되므로, 한 트랜잭션 안에서 함께 저장되어야 한다. 외부는 오직 루트(Order)를 통해서만 내부에 접근한다. 앞 장의 “불변식을 지킬 단일 장소가 없다”는 병에 대한 구조적 처방이 이것이다: 애그리거트 루트가 그 단일 장소다.
뒤집으면 설계 지침이 나온다. 같은 트랜잭션에서 지킬 필요가 없는 불변식이라면 같은 애그리거트에 넣지 마라. 애그리거트가 크면 클수록 한 번에 잠기는 범위가 커지고(동시성 병목), 로딩이 무거워진다. “주문과 회원을 한 덩어리로”가 아니라, 주문은 회원의 ID만 참조하고 회원 관련 규칙은 회원 애그리거트가 지킨다. 애그리거트 사이의 정합성은 트랜잭션이 아니라 결과적으로(이벤트로) 맞춘다 — 이 문장이 훗날 분산 시스템 장에서 Saga라는 이름으로 되돌아온다.
마지막으로 리포지토리. Evans의 리포지토리는 DAO의 다른 이름이 아니라 “애그리거트를 컬렉션처럼 다루게 해주는 도메인 측의 추상”이다 — 인터페이스는 도메인이 소유하고 구현은 바깥에 있다. Step 03에서 본 추상의 소유권 역전, 그 문장 그대로다.
6. DDD와 아키텍처의 관계 — 렌즈는 벽이 아니다
역사적 사실 하나가 이 장과 다음 장을 잇는다. Evans의 2003년 책은 레이어드 아키텍처를 전제했다. 책의 구조 논의는 UI / 애플리케이션 / 도메인 / 인프라스트럭처의 4층이고, 이는 당시의 표준 어휘였다.
그런데 여기에 미묘한 긴장이 있다. 분명히 하자 — Evans가 그 긴장을 몰랐던 것은 아니다. 5절에서 봤듯 리포지토리는 도메인이 인터페이스를 소유하는 역전이고, Evans는 인프라 층이 상위 층의 인터페이스를 구현한다고 명시했다. 즉 영속 의존은 이미 국소적으로 뒤집혀 있었다. 그러나 그 역전은 레이어드라는 어휘 안에 사는 국소 기법이었지 구조 전체의 기본값은 아니었다 — UI는 여전히 특권적 “위”였고, 그림의 기본 화살표는 여전히 아래를 향했다. 필요한 것은 이 국소 역전을 전 구조의 원리로 일반화하고, UI와 DB를 대칭적인 “바깥”으로 만드는 그림이었다. 그것이 다음 장의 헥사고날이다. 실제로 Vernon의 『Implementing DDD』(2013)에 이르면 DDD의 기본 짝은 헥사고날로 바뀌어 있다. DDD가 요구를 만들었고, 헥사고날이 구조를 공급했다 — 렌즈와 벽의 분업이다.
7. 흔한 오해 바로잡기
오해 1: “DDD = 엔티티·리포지토리 잘 나누기.” 가장 흔하고 가장 값비싼 오해다. 전술 패턴만 가져다 쓰는 것(업계 은어로 DDD-lite)은 유비쿼터스 언어도 바운디드 컨텍스트도 없이 클래스 분류법만 남긴 것이다. Vernon을 비롯한 실천가들이 반복해서 강조하는 방향은 반대다 — 가치의 대부분은 전략적 설계에 있다. 전술 없이 전략만 있어도 DDD지만, 전략 없는 전술은 스타일 흉내다.
오해 2: “DDD를 하려면 마이크로서비스여야 한다.” 인과가 거꾸로다. DDD(2003)는 마이크로서비스(2014)보다 11년 먼저다. 바운디드 컨텍스트는 모놀리스 안의 모듈 경계로도 완벽히 실천되고 — 오히려 그쪽이 시작점으로 안전하다. 경계를 프로세스로 찢는 것은 별도의 (비싼) 결정이다.
오해 3: “바운디드 컨텍스트 = 마이크로서비스, 1:1.” 컨텍스트는 모델과 언어의 경계이고, 서비스는 배포의 경계다. 한 서비스가 여러 컨텍스트를 품을 수도, 한 컨텍스트가 여러 서비스로 구현될 수도 있다. 1:1은 흔한 출발점일 뿐 정의가 아니다.
오해 4: “모든 프로젝트에 DDD를 적용해야 한다.” Evans 자신의 전제를 기억하라 — DDD는 복잡한 도메인을 위한 투자다. 2절의 곡선 교차점 앞이라면(단순 CRUD, 규칙 없는 도메인) 트랜잭션 스크립트가 옳고, 앞 장의 변호가 그대로 적용된다. 렌즈는 필요할 때 쓰는 것이다.
8. 요약, 그리고 다음 장으로
- DDD는 아키텍처 스타일이 아니라 **렌즈(설계 방법론)**다. 이후 스타일들에게 “무엇을 지킬 것인가”라는 목적어를 공급한다.
- 갈림길: 단순한 도메인은 트랜잭션 스크립트, 복잡한 도메인은 도메인 모델 — 교차점은 지나고 나서야 보인다(PoEAA).
- Evans의 선언(2003): 복잡성은 **심장(도메인)**에 있다. 유비쿼터스 언어로 모델과 코드를 일치시켜라.
- 전략적 설계: 규모가 커지면 통일 모델은 환상이다 — 바운디드 컨텍스트로 모델의 유효 경계를 명시하고, 컨텍스트 맵(특히 ACL)으로 관계를 관리하라. Parnas의 기준을 도메인 의미론으로 끌어올린 것.
- 전술적 패턴: 심장은 애그리거트 — 불변식의 경계 = 트랜잭션의 경계. 애그리거트 사이는 결과적 정합(이벤트)으로.
- 역사의 다리: Evans는 레이어드를 전제하되, 리포지토리로 영속 의존을 이미 국소적으로 뒤집어 두었다. 그 국소 역전을 전 구조의 원리로 일반화하는 일이 다음 장을 부른다.
다음 장 예고: 2005년, Alistair Cockburn이 그 불일치를 끝낼 그림을 내놓는다. UI도 DB도 똑같은 “바깥”이며, 바깥의 모든 화살표가 안(도메인)을 향하는 대칭 구조 — 헥사고날 아키텍처. Step 03에서 예습한 문장이 마침내 제 이름을 얻는다: 애플리케이션이 소유한 계약이 포트, 바깥에서 그것을 맞춰주는 구현이 어댑터다.
원전
| 연도 | 문헌 | 이 장에서의 역할 |
|---|---|---|
| 2002 | Fowler, Patterns of Enterprise Application Architecture | 트랜잭션 스크립트 vs 도메인 모델, 복잡도 곡선 |
| 2003 | Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software | 이 장의 주 텍스트 — 유비쿼터스 언어, 바운디드 컨텍스트, 애그리거트 |
| 2013 | Vernon, Implementing Domain-Driven Design | 전략적 설계 우선론, DDD와 헥사고날의 결합 |
| 2014 | Fowler & Lewis, Microservices | 바운디드 컨텍스트의 서비스 경계 재사용 (예고) |