본문으로 건너뛰기
Leo Jung
뒤로 가기

[DDD Intro #1] DDD는 기술이 아니라 관점이다

페이지 수정

DDD를 처음 공부하면 자연스럽게 기술적인 단어들이 먼저 눈에 들어온다. Entity, Value Object, Aggregate, Repository, Domain Service, Domain Event. 조금 더 나아가면 Layered Architecture, Hexagonal Architecture, CQRS, Event Sourcing 같은 단어들이 따라온다.

그래서 DDD는 쉽게 “복잡한 객체지향 설계 기법”이나 “마이크로서비스를 잘 나누는 방법”처럼 보인다. 실제로 DDD를 도입한다고 하면서 패키지를 domain, application, infrastructure로 나누고, 모든 클래스에 그럴듯한 이름을 붙이는 것으로 시작하는 팀도 많다.

하지만 그렇게 시작하면 DDD는 금방 부담스러운 기술 목록이 된다. 무엇을 Entity로 만들지, 어떤 것을 Value Object로 분리할지, Aggregate Root는 어디까지 책임져야 하는지 논쟁하게 된다. 정작 중요한 질문은 뒤로 밀린다.

우리는 어떤 문제를 풀고 있는가? 이 도메인에서 정말 중요한 규칙은 무엇인가? 도메인 전문가가 쓰는 말과 코드가 쓰는 말은 같은가? 같은 단어가 서로 다른 맥락에서 다른 의미로 쓰이고 있지는 않은가?

DDD의 출발점은 패턴이 아니다. DDD의 출발점은 도메인을 바라보는 관점이다.

코드는 왜 도메인에서 멀어지는가

소프트웨어는 처음에는 단순하게 시작한다. 요구사항도 명확해 보이고, 데이터 구조도 단순해 보인다. 예를 들어 구독 서비스에서 사용자의 구독 상태를 관리한다고 해보자.

처음에는 상태가 몇 개 없다.

ACTIVE
CANCELED
EXPIRED

이 정도면 간단한 CRUD처럼 보인다. 컨트롤러에서 요청을 받고, 서비스에서 상태를 바꾸고, Repository로 저장하면 된다.

subscription.setStatus(SubscriptionStatus.CANCELED);

문제는 시간이 지나면서 시작된다.

운영팀은 “해지”와 “환불”을 구분하기 시작한다. 결제팀은 이미 결제된 구독과 아직 결제되지 않은 구독을 다르게 처리해야 한다고 말한다. 고객센터는 사용자가 직접 해지한 것과 관리자가 직권 해지한 것을 구분해야 한다고 말한다. 마케팅팀은 무료 체험 중 해지한 사용자와 유료 구독 중 해지한 사용자를 다르게 분석하고 싶어 한다.

이제 “구독을 취소한다”는 말은 더 이상 단순한 상태 변경이 아니다.

무료 체험 중 해지
정기 결제 직후 해지
환불 가능한 해지
환불 불가능한 해지
관리자에 의한 직권 해지
결제 실패로 인한 자동 중지
사용 기간 만료로 인한 종료

도메인은 처음부터 복잡했을 수도 있고, 시간이 지나면서 우리가 그 복잡성을 더 잘 알게 되었을 수도 있다. 중요한 것은 코드가 이 차이를 담아내지 못하면, 복잡성은 사라지는 것이 아니라 다른 곳으로 흩어진다는 점이다.

상태 값은 늘어나고, 서비스 메서드는 길어지고, 조건문은 중첩된다. 어느 순간 코드는 이렇게 말한다.

if (subscription.getStatus() == ACTIVE &&
    subscription.getPaymentStatus() == PAID &&
    subscription.isRefundable() &&
    !subscription.isTrial()) {
    // ...
}

하지만 도메인 전문가는 이렇게 말한다.

정기 결제 후 7일 이내에 사용 이력이 없으면 전액 환불할 수 있다.

두 문장은 같은 사실을 가리킬 수 있다. 그러나 코드의 언어와 도메인의 언어 사이에는 거리가 있다. DDD는 이 거리를 줄이려는 시도다.

DDD는 도메인 지식을 코드에 보존하는 방법이다

DDD가 흥미로운 이유는 “좋은 설계 패턴을 제공하기 때문”만은 아니다. DDD는 소프트웨어의 중심에 도메인 지식을 놓으라고 말한다.

소프트웨어는 결국 어떤 현실의 문제를 해결하기 위해 존재한다. 은행 시스템은 계좌, 이체, 한도, 정산, 위험 관리 같은 금융 도메인을 다룬다. 이커머스 시스템은 상품, 주문, 결제, 배송, 반품, 정산을 다룬다. 구독 서비스는 요금제, 구독, 갱신, 해지, 환불, 이용 권한을 다룬다.

이때 중요한 것은 데이터가 아니다. 데이터베이스 테이블이 몇 개 필요한지도 중요하지만, 그것이 도메인 이해의 중심은 아니다. 더 중요한 것은 도메인 안에서 어떤 개념들이 어떤 의미를 갖고, 어떤 규칙에 따라 변화하며, 어떤 경계 안에서 일관성을 유지해야 하는가이다.

DDD에서 말하는 모델은 이 도메인 지식을 소프트웨어 안에 담아내기 위한 수단이다. 모델은 그림이나 문서에서 끝나지 않는다. 모델은 코드 속에서 살아야 한다.

예를 들어 다음 두 코드를 비교해보자.

subscription.setStatus(SubscriptionStatus.CANCELED);
subscription.cancelBySubscriber(reason);

둘 다 결과적으로 상태를 CANCELED로 바꿀 수 있다. 하지만 두 코드는 전혀 다른 태도를 갖고 있다.

첫 번째 코드는 데이터를 수정한다. 두 번째 코드는 도메인 행위를 표현한다. 첫 번째 코드를 읽는 사람은 “상태가 취소로 바뀌었구나” 정도만 알 수 있다. 두 번째 코드를 읽는 사람은 “구독자가 직접 해지했구나”라는 도메인 의미를 알 수 있다.

DDD는 이런 차이를 중요하게 본다. 도메인에서 의미 있는 행위와 규칙이 코드의 이름, 구조, 책임 안에 드러나야 한다.

기술보다 먼저 질문이 바뀌어야 한다

DDD를 도입한다고 해서 곧바로 아키텍처가 바뀌어야 하는 것은 아니다. 패키지 구조를 바꾸고, Repository 인터페이스를 만들고, 모든 값을 Value Object로 감싼다고 해서 자동으로 DDD가 되는 것도 아니다.

DDD를 시작했다는 가장 중요한 신호는 코드 구조가 아니라 질문의 변화다.

이전에는 이렇게 물었다.

이 API는 어떤 테이블을 수정해야 하지?
이 화면에 필요한 필드는 무엇이지?
이 로직은 Service에 둘까, Util에 둘까?

DDD를 고민하기 시작하면 질문이 조금 달라진다.

이 행위는 도메인에서 어떤 의미를 갖는가?
이 규칙은 어느 객체가 책임져야 하는가?
이 단어는 모든 팀에서 같은 의미로 쓰이는가?
이 변경은 어떤 일관성을 깨뜨리면 안 되는가?
이 모델은 어느 경계 안에서만 유효한가?

질문이 바뀌면 설계도 바뀐다. 설계가 바뀌면 코드의 모양도 바뀐다. 하지만 순서는 중요하다. DDD는 코드 모양에서 출발하지 않는다. 도메인에 대한 질문에서 출발한다.

도메인 전문가는 요구사항 전달자가 아니다

DDD에서 자주 등장하는 말 중 하나가 도메인 전문가다. 그런데 도메인 전문가를 단순히 “요구사항을 알려주는 사람”으로 이해하면 부족하다.

도메인 전문가는 기능 목록을 전달하는 사람이 아니라, 도메인의 언어와 규칙을 함께 발견하는 사람이다. 개발자는 그들의 말을 그대로 받아 적는 사람이 아니다. 개발자는 질문해야 한다.

“해지와 환불은 같은 건가요?”

“결제 실패로 중지된 구독도 해지라고 부르나요?”

“무료 체험이 끝나서 자동으로 종료된 경우와 사용자가 직접 해지한 경우는 정책상 같은가요?”

“고객센터에서 말하는 ‘복구’는 이전 구독을 되살리는 건가요, 새 구독을 생성하는 건가요?”

이런 질문을 통해 도메인 지식은 더 선명해진다. 처음에는 당연해 보였던 단어들이 사실은 모호했다는 사실이 드러난다. 하나의 상태 값으로 충분해 보였던 것이 여러 개의 도메인 사건으로 분리되기도 한다.

DDD는 이런 지식 탐구의 과정을 설계의 중심에 둔다.

DDD는 복잡성을 없애지 않는다

DDD에 대해 기대를 잘못 가지면 실망하기 쉽다. DDD는 복잡한 도메인을 단순한 코드로 마법처럼 바꿔주지 않는다. 오히려 숨겨져 있던 복잡성을 드러낸다.

이 점이 중요하다.

복잡한 도메인을 단순하게 보이도록 만드는 것은 좋은 설계가 아닐 수 있다. 단순해 보이는 코드 뒤에 도메인 규칙이 흩어져 있다면, 그 복잡성은 사라진 것이 아니라 발견하기 어려워진 것이다.

좋은 DDD는 복잡성을 정직하게 드러낸다. 다만 아무 곳에나 흩어지게 두지 않는다. 중요한 규칙은 도메인 객체 안으로 들어가고, 의미 있는 변화는 도메인 이벤트로 표현되며, 서로 다른 모델은 Bounded Context로 분리된다. 복잡성을 없애는 것이 아니라, 이름 붙이고 경계를 만들고 책임을 배치한다.

그래서 DDD는 단순한 시스템에는 과할 수 있다. 관리자 화면에서 공지사항 제목과 내용을 저장하는 기능에 깊은 도메인 모델이 필요하지 않을 수 있다. 하지만 정책이 많고, 상태 전이가 중요하고, 같은 단어가 맥락마다 다르게 쓰이고, 장기적으로 변화할 도메인이라면 DDD의 관점은 큰 힘을 발휘한다.

DDD를 공부할 때의 순서

DDD를 처음 공부하면 전술적 패턴부터 공부하고 싶어진다. Entity와 Value Object는 눈에 잘 보이고, 코드로 바로 연습할 수 있기 때문이다. 하지만 DDD를 제대로 이해하려면 조금 더 앞에서 시작하는 편이 좋다.

먼저 도메인을 이해해야 한다. 그다음 도메인을 설명하는 언어를 살펴봐야 한다. 같은 언어가 같은 의미로 쓰이는 경계를 찾아야 한다. 그 후에야 Aggregate, Repository, Domain Event 같은 패턴이 왜 필요한지 이해할 수 있다.

패턴은 답이 아니라 결과다.

Aggregate를 먼저 외우면 “어디까지 묶어야 하지?”라는 질문에 갇힌다. 하지만 “어떤 변경이 반드시 함께 일관되어야 하지?”라고 물으면 Aggregate의 필요성이 보인다.

Domain Event를 먼저 외우면 “이벤트를 발행해야 할까?”라는 기술적 질문에 갇힌다. 하지만 “도메인에서 다른 관심사가 알아야 할 만큼 중요한 일이 일어났는가?”라고 물으면 이벤트의 의미가 보인다.

Bounded Context를 먼저 외우면 “서비스를 어떻게 나누지?”라는 질문에 갇힌다. 하지만 “이 단어가 어느 경계 안에서 같은 의미를 갖는가?”라고 물으면 모델의 경계가 보인다.

마치며

DDD는 기술이 아니다. 물론 DDD를 구현하려면 기술이 필요하다. 객체지향 설계도 필요하고, 아키텍처도 필요하고, 데이터 저장 방식도 고민해야 한다. 하지만 그것들은 DDD의 중심이 아니다.

DDD의 중심에는 도메인이 있다. 더 정확히 말하면 도메인을 이해하려는 태도가 있다. 도메인 전문가와 같은 언어를 만들고, 그 언어를 코드에 반영하며, 모델이 유효한 경계를 찾고, 중요한 규칙을 코드 안에 보존하려는 태도다.

그래서 DDD는 기술이라기보다 관점이다.

DDD를 한다는 것은 Entity, Value Object, Repository라는 이름을 붙이는 일이 아니다. 도메인을 더 잘 이해하기 위해 질문을 바꾸고, 그 이해가 코드 안에서 사라지지 않도록 구조를 만드는 일이다.

이 연재는 그 관점에서 출발하려고 한다. 다음 글에서는 DDD에서 말하는 “모델”이 무엇인지 살펴본다. 모델은 현실의 복사본이 아니다. 모델은 우리가 해결하려는 문제를 위해 현실을 선택적으로 해석한 결과다.


페이지 수정
공유하기:

이전 글
[DDD Intro #2] 모델은 현실의 복사본이 아니다