DDD를 공부하다 보면 좋은 설계를 모든 곳에 적용하고 싶어진다. Entity를 만들고, Value Object를 만들고, Aggregate를 나누고, Repository와 Domain Service를 세운다. 도메인 이벤트도 발행하고, 계층도 깔끔하게 분리한다.
그런데 실제 프로젝트에서 이런 방식으로 접근하면 금방 피로해진다. 단순한 관리자 화면 하나를 만드는 데도 수많은 클래스가 생긴다. 공지사항을 등록하는 기능에도 Aggregate가 필요해 보이고, 배너 순서를 바꾸는 기능에도 Domain Event가 필요해 보인다. 시간이 지나면 팀 안에서 이런 말이 나온다.
“DDD는 너무 복잡하다.”
하지만 DDD가 정말 말하려는 것은 모든 코드를 복잡하게 만들라는 것이 아니다. 오히려 반대에 가깝다. DDD는 복잡한 도메인을 정직하게 다루되, 모든 영역에 같은 수준의 설계 에너지를 쓰지 말라고 말한다.
이때 필요한 개념이 Core Domain이다.
모든 도메인이 같은 가치를 갖지는 않는다
소프트웨어 안에는 여러 종류의 도메인이 섞여 있다. 어떤 영역은 비즈니스의 경쟁력을 만든다. 어떤 영역은 반드시 필요하지만 경쟁력의 핵심은 아니다. 또 어떤 영역은 대부분의 회사가 비슷하게 해결하는 일반적인 문제다.
예를 들어 구독 서비스를 생각해보자.
이 서비스의 핵심 경쟁력이 “사용자의 이용 패턴에 따라 가장 적절한 요금제를 추천하고, 갱신과 변경을 유연하게 처리하는 것”이라면, 구독과 요금제 정책은 매우 중요한 도메인이다. 무료 체험을 언제 제공할지, 월간 구독에서 연간 구독으로 바꿀 때 남은 기간을 어떻게 계산할지, 결제 실패 후 며칠 동안 서비스를 유지할지 같은 규칙은 서비스의 경험과 수익에 직접 영향을 준다.
반면 이메일 발송은 필요하지만 핵심 경쟁력은 아닐 수 있다. 인증도 중요하지만 많은 경우 이미 검증된 솔루션이나 공통 패턴을 사용할 수 있다. 관리자 공지사항 관리도 필요하지만 깊은 도메인 모델이 필요하지 않을 수 있다.
모든 기능이 중요하다는 말은 맞다. 하지만 모든 기능이 같은 방식으로 중요하지는 않다.
Core Domain, Supporting Subdomain, Generic Subdomain
DDD에서는 도메인을 크게 세 가지로 나누어 볼 수 있다.
Core Domain
Supporting Subdomain
Generic Subdomain
Core Domain은 비즈니스의 핵심 경쟁력이 되는 영역이다. 이곳은 가장 깊게 이해해야 하고, 가장 좋은 설계 에너지를 써야 한다. 도메인 전문가와 오래 대화해야 하며, 모델의 언어도 계속 다듬어야 한다.
Supporting Subdomain은 핵심 도메인을 돕는 영역이다. 비즈니스에 필요하지만 경쟁 우위의 중심은 아니다. 구독 서비스에서 알림, 쿠폰, 고객 문의 같은 영역이 여기에 해당할 수 있다. 이들도 아무렇게나 만들어도 된다는 뜻은 아니다. 다만 Core Domain만큼 깊고 정교한 모델링이 항상 필요한 것은 아니다.
Generic Subdomain은 여러 시스템에서 공통적으로 나타나는 일반적인 영역이다. 인증, 이메일 발송, 파일 업로드, 결제 PG 연동 같은 기능이 여기에 해당할 수 있다. 이 영역은 직접 만들기보다 검증된 라이브러리, 외부 서비스, 범용 솔루션을 사용하는 편이 더 나을 수 있다.
구독 서비스 예시로 정리하면 다음과 같다.
Core Domain
- 구독 상태 관리
- 요금제 변경 정책
- 갱신과 만료
- 이용 권한 계산
- 무료 체험과 전환 정책
Supporting Subdomain
- 쿠폰 발급과 사용
- 알림 발송 시나리오
- 고객 문의 처리
- 운영자 관리 기능
Generic Subdomain
- 회원 인증
- 이메일 발송
- 결제 PG 연동
- 파일 저장
이 구분은 고정된 답이 아니다. 어떤 회사에서는 결제 자체가 Core Domain일 수 있다. 어떤 회사에서는 알림이 핵심 경험일 수 있다. 중요한 것은 우리 비즈니스에서 무엇이 경쟁력의 중심인지 묻는 것이다.
DDD는 집중의 기술이다
DDD를 처음 적용하는 팀은 종종 모든 것을 도메인 모델로 만들려고 한다. 하지만 이것은 설계 에너지를 낭비하는 방식이다.
단순히 제목과 내용을 저장하는 공지사항 기능이 있다고 해보자. 이 기능에 복잡한 상태 변화도 없고, 중요한 비즈니스 정책도 없다면 다음 정도의 구조로 충분할 수 있다.
NoticeController
NoticeService
NoticeRepository
NoticeEntity
여기에 굳이 NoticeAggregate, NoticeCreatedEvent, NoticeFactory, NoticePolicy 같은 구조를 만들 필요는 없을 수 있다. 그렇게 만든 코드는 DDD가 아니라 DDD처럼 보이는 과한 구조가 된다.
반대로 구독 갱신 정책은 다르게 봐야 한다. 결제 실패 시 재시도 간격, 유예 기간, 권한 유지 여부, 만료 처리, 알림 발송, 요금제 변경과의 충돌 같은 규칙이 얽혀 있다면, 이 영역은 깊게 모델링해야 한다.
subscription.renew(paymentResult, renewalPolicy, now);
이 한 줄 안에는 단순한 상태 변경이 아니라 도메인 규칙이 들어 있다. 이 규칙이 서비스의 매출과 고객 경험을 좌우한다면, 여기에 좋은 설계를 쓰는 것은 비용이 아니라 투자다.
중요한 영역을 평범하게 만들지 않기
모든 곳에 DDD를 적용하는 것도 문제지만, 반대로 Core Domain을 단순 CRUD처럼 다루는 것도 문제다.
구독 상태를 단순히 ACTIVE, CANCELED, EXPIRED로만 관리한다고 해보자. 처음에는 충분해 보인다. 하지만 시간이 지나면서 이런 요구사항이 들어온다.
무료 체험 중인 사용자는 해지해도 만료일까지 사용할 수 있다.
결제 실패 후 3일 동안은 서비스 이용을 유지한다.
연간 구독으로 변경하면 남은 월간 구독 금액을 차감한다.
기업 요금제는 최소 좌석 수를 만족해야 갱신할 수 있다.
환불이 완료된 구독은 다시 활성화할 수 없다.
이 규칙들이 여기저기 흩어지면 Core Domain은 점점 알아보기 어려워진다. Application Service에 조건문이 늘어나고, Repository에는 상태를 직접 바꾸는 쿼리가 생기고, 프론트엔드는 서버의 애매한 상태 값을 해석하기 시작한다.
Core Domain을 평범한 CRUD로 다루면, 복잡성은 사라지지 않는다. 그 복잡성은 조건문, 예외 처리, 운영 매뉴얼, 사람의 기억 속으로 흩어진다.
DDD가 필요한 곳은 바로 이런 영역이다.
설계 수준은 도메인 가치에 맞춰야 한다
좋은 설계란 모든 곳에서 같은 모양을 갖는 것이 아니다. 좋은 설계는 문제의 가치와 복잡도에 맞는 수준을 갖는다.
Core Domain에는 도메인 전문가와의 깊은 대화가 필요하다. 모델의 이름을 신중하게 정해야 하고, 불변식을 찾아야 하며, Aggregate 경계를 고민해야 한다. 테스트도 도메인 규칙을 설명할 수 있어야 한다.
Supporting Subdomain에는 필요한 만큼의 모델링을 적용하면 된다. 핵심 도메인을 방해하지 않고, 충분히 유지보수 가능하면 된다.
Generic Subdomain에는 이미 검증된 솔루션을 사용하는 것이 더 좋은 선택일 수 있다. 직접 잘 만드는 것보다 잘 사는 것이 나을 때도 있다.
이 구분은 팀의 에너지를 지키는 데 중요하다. DDD를 오래 지속하려면 “어디에 쓰지 않을 것인가”도 알아야 한다.
Core Domain은 계속 바뀔 수 있다
한 가지 주의할 점이 있다. Core Domain은 한 번 정하면 영원히 고정되는 것이 아니다. 사업 전략이 바뀌면 핵심 도메인도 바뀔 수 있다.
초기에는 구독 결제와 갱신이 핵심이었지만, 나중에는 사용자별 맞춤 추천이 핵심이 될 수 있다. 처음에는 쿠폰이 단순 지원 기능이었지만, 나중에는 프로모션 전략이 서비스 성장의 핵심이 될 수 있다. 반대로 한때 핵심이었던 기능이 외부 솔루션으로 대체될 수도 있다.
따라서 Core Domain을 찾는 일은 한 번의 분석으로 끝나지 않는다. 제품이 성장하고 시장이 바뀌면 계속 다시 물어야 한다.
지금 우리 서비스의 경쟁력은 어디에서 나오는가?
어떤 규칙이 자주 바뀌고 있는가?
어떤 기능이 비즈니스 성과에 직접 영향을 주는가?
어떤 영역을 잘못 설계하면 변화가 가장 고통스러운가?
이 질문에 대한 답이 설계의 우선순위를 정한다.
마치며
DDD는 모든 코드를 고급스럽게 만드는 방법이 아니다. DDD는 복잡한 도메인을 다루기 위해 설계 에너지를 어디에 집중해야 하는지 묻는 방법이다.
모든 곳에 Aggregate를 만들 필요는 없다. 모든 상태 변경에 Domain Event가 필요한 것도 아니다. 모든 기능을 Hexagonal Architecture로 감싸야 하는 것도 아니다. 중요한 것은 우리 시스템에서 정말 깊게 이해해야 할 도메인이 어디인지 찾는 것이다.
Core Domain을 식별한다는 것은 “여기에는 더 많이 질문하자”라고 결정하는 일이다. 반대로 Generic Subdomain을 구분한다는 것은 “여기에는 과하게 집착하지 말자”라고 결정하는 일이다.
DDD는 집중의 기술이다. 어디에 깊어질 것인지, 어디에서 단순함을 허용할 것인지 구분할 때 DDD는 더 현실적인 설계 방법이 된다.