DDD 기본기: 도메인 언어와 경계 설계
DDD는 비즈니스 언어와 코드 모델의 거리를 줄이고, Bounded Context로 도메인 경계를 나누는 설계 방식이다. 전략적 설계와 전술적 설계를 통해 모듈러 모놀리스에서 MSA로 진화할 수 있는 근거를 만든다.
Script Companion
오디오와 함께 스크립트 보기
- 01
DDD, Domain-Driven Design의 출발점은 코드와 비즈니스 언어 사이의 거리다. 도메인 복잡도가 커지면 기획자는 주문을 확정한다고 말하는데, 코드는 단순히 상태 값을 바꾸는 식으로 흘러가기 쉽다. 이 거리감이 쌓이면 작은 변경도 계속 번역 비용을 만든다. DDD는 도메인 언어와 코드 모델을 맞춰서, 비즈니스에서 말하는 개념이 코드 안에서도 같은 모양으로 드러나게 하려는 접근이다.
- 02
핵심 비유는 회사 부서별 언어다. 같은 회사에서도 회계팀의 고객은 세금계산서를 발행해야 하는 법인이나 개인이고, 마케팅팀의 고객은 캠페인 타겟이며, 배송팀의 고객은 배송지가 있는 수령인이다. 모두 고객이라는 단어를 쓰지만 중요하게 보는 정보가 다르다. 이 세 팀이 하나의 고객 테이블을 공유하면 필요한 필드가 계속 붙고, 결국 서로의 변경이 충돌하는 구조가 된다. DDD는 이 문제를 Bounded Context로 다룬다.
- 03
전략적 설계, Strategic Design은 큰 그림에서 도메인을 어떻게 나눌지 결정하는 일이다. Bounded Context는 각 팀이나 도메인이 자기만의 모델을 가지는 경계이며, 그 경계 안에서는 모델의 의미가 일관되어야 한다. 같은 Order라도 주문 처리 맥락과 다른 맥락에서 필요한 속성이 다를 수 있다. Context Map은 이렇게 나뉜 여러 Bounded Context 사이의 관계를 정의한다. 즉, 어디까지가 같은 언어이고 어디부터 번역이 필요한지 선을 긋는 작업이다.
- 04
Ubiquitous Language는 개발자와 도메인 전문가가 같은 용어를 쓰게 만드는 원칙이다. 비즈니스에서 주문을 확정한다고 말한다면, 코드에서도 상태 문자열을 직접 바꾸는 표현보다 주문 확정이라는 동작이 드러나야 한다. 이 원칙은 단순히 메서드 이름을 예쁘게 붙이는 문제가 아니다. 회의에서 쓰는 말, 정책을 설명하는 말, 테스트에서 확인하는 말, 코드 모델이 가리키는 말이 어긋나지 않게 만드는 장치다.
- 05
Bounded Context가 없을 때의 대표적인 실패는 공유 모델의 함정이다. 하나의 Customer 모델을 모든 팀이 함께 쓰면, 각 팀이 필요한 필드를 계속 추가하면서 신 객체가 생긴다. 문서에서는 필드가 50개 넘는 Customer 테이블을 예로 든다. 이렇게 되면 한 팀의 스키마 변경이 다른 팀 전체를 깨뜨릴 수 있다. DDD의 경계 분리는 중복을 무조건 없애려는 설계가 아니라, 서로 다른 의미를 가진 모델을 억지로 하나로 합치지 않기 위한 설계다.
- 06
전술적 설계, Tactical Design은 경계 안의 코드를 어떻게 구조화할지 다룬다. Entity는 고유한 ID로 식별되는 객체라서 속성이 바뀌어도 같은 객체로 본다. Value Object는 ID가 아니라 값으로 동일성을 판단하고 불변이다. Aggregate는 연관 객체의 묶음이며 외부에서는 Root를 통해서만 접근한다. Repository는 Aggregate의 영속성을 관리하는 인터페이스이고, Domain Event는 OrderConfirmed나 PaymentFailed처럼 도메인에서 발생한 중요한 사건을 표현한다. Domain Service는 특정 엔티티에 넣기 어려운 비즈니스 로직을 담는다.
- 07
Aggregate Root의 핵심 역할은 불변식, Invariant를 보호하는 것이다. 불변식은 어떤 상황에서도 반드시 참이어야 하는 비즈니스 규칙이다. 예를 들어 주문 총 금액은 절대 음수일 수 없고, 확정된 주문에는 상품을 추가할 수 없다는 규칙이 있다. 외부에서 OrderItem을 직접 수정하면 이런 규칙이 깨질 수 있으므로, 상태 변경은 반드시 Root인 Order를 통해야 한다. 이 구조의 장점은 트랜잭션 일관성이다. Aggregate 내부 변경은 하나의 트랜잭션으로 저장되어 부분 성공과 부분 실패를 피한다.
- 08
실무 진화 경로에서 DDD는 모듈러 모놀리스에서 MSA로 넘어가는 설계 근거가 된다. 처음부터 MSA로 시작하면 도메인 경계가 아직 불명확해서 나중에 서비스를 다시 합쳐야 하는 역설이 생길 수 있다. 모듈러 모놀리스는 각 모듈이 자체 DB 테이블을 소유하고, 다른 모듈의 DB를 직접 조회하지 않는 방식으로 경계를 연습한다. 모듈 간 통신도 인터페이스를 통해서만 하므로, 나중에 HTTP나 메시지큐로 바꾸기 쉽다. 동시에 하나의 프로세스로 배포되기 때문에 MSA의 분산 시스템 복잡도는 아직 떠안지 않는다.
- 09
DDD는 도메인 복잡도가 있는 곳에서 힘을 발휘하지만, 잘못 쓰면 시스템을 더 복잡하게 만든다. Aggregate가 너무 커지면 Lock 경쟁이 커질 수 있고, Context 사이의 동기 호출이 많아지면 분산 모놀리스에 가까워진다. Shared Kernel이 비대해지면 Context의 독립성도 약해진다. 정리하면, Bounded Context는 공유 모델이 신 객체가 되는 흐름을 막고, Ubiquitous Language는 비즈니스 언어와 코드 언어를 맞춘다. Aggregate Root는 불변식을 지키며, 이 경계가 트랜잭션 경계가 된다.
같은 레이어
L9에서 이어 듣기
- 설계 원칙을 운영 가능한 코드로 잇기 길이 미정
- Clean Architecture의 의존성 규칙 길이 미정
- Twelve-Factor App 운영 원칙 길이 미정
- CAP과 일관성으로 보는 분산 시스템 선택 길이 미정
- MSA 패턴, 분리의 이득과 운영 비용 길이 미정
- Saga Pattern: 로컬 커밋과 역순 보상 길이 미정
- CQRS와 이벤트 소싱의 운영 경계 길이 미정
- TDD와 테스트 피라미드로 설계하는 테스트 전략 길이 미정
- 대규모 웹 크롤러의 큐, 정중함, 중복 제거 길이 미정
- API 계약으로 안전하게 서비스 경계를 진화시키기 길이 미정
- URL Shortener와 Rate Limiter로 보는 시스템 디자인 길이 미정
- 오디오 파일
- /podcasts/l9-ddd-basics.mp3