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

[DDD Intro #30] 내가 이해한 DDD: 좋은 설계는 좋은 질문에서 시작된다

페이지 수정

DDD를 처음 접하면 용어가 먼저 보인다. Entity, Value Object, Aggregate, Repository, Domain Service, Domain Event, Bounded Context, Context Map. 여기에 CQRS, Event Sourcing, Hexagonal Architecture, Microservice까지 이어지면 DDD는 거대한 기술 체계처럼 느껴진다.

그래서 DDD를 공부하는 일은 처음에는 용어를 이해하는 일처럼 보인다. 무엇이 Entity이고 무엇이 Value Object인지, Aggregate Root는 어디까지 책임져야 하는지, Repository는 DAO와 어떻게 다른지 고민한다.

하지만 시간이 지날수록 DDD의 핵심은 용어 자체가 아니라는 생각이 든다. DDD는 정답을 주는 방법론이라기보다, 더 좋은 질문을 던지게 하는 사고방식에 가깝다.

DDD는 코드를 보기 전에 도메인을 보게 한다

DDD를 알기 전에는 설계를 주로 코드 구조의 문제로 생각하기 쉽다. 패키지를 어떻게 나눌지, 인터페이스를 어디에 둘지, 어떤 아키텍처를 적용할지 먼저 고민한다.

물론 이런 질문도 중요하다. 하지만 DDD는 그보다 앞선 질문을 던진다.

우리는 어떤 문제를 풀고 있는가?
이 도메인에서 가장 중요한 규칙은 무엇인가?
도메인 전문가들은 이 상황을 어떤 말로 설명하는가?
같은 단어가 서로 다른 의미로 쓰이고 있지는 않은가?
무엇을 한 번에 일관되게 지켜야 하는가?

이 질문들은 코드 바깥에서 시작하지만 결국 코드로 돌아온다. 도메인을 제대로 이해하지 못하면 코드 구조가 아무리 깔끔해도 모델은 오래 버티지 못한다.

DDD는 개발자가 비즈니스 언어와 더 가까워지게 만든다. 개발자는 요구사항을 전달받아 구현하는 사람이 아니라, 도메인을 함께 탐구하고 그 이해를 코드에 남기는 사람이 된다.

좋은 이름은 좋은 설계의 시작이다

DDD를 공부하면서 가장 크게 느낀 것 중 하나는 이름의 중요성이다. 좋은 이름은 단순한 가독성 문제가 아니다. 좋은 이름은 모델의 경계를 만들고, 책임을 제한하며, 대화를 가능하게 한다.

setStatus(CANCELED)cancelByMember(reason, now)는 같은 결과를 만들 수 있다. 하지만 두 코드는 다른 말을 한다. 전자는 상태를 바꾸고, 후자는 도메인 행위를 표현한다.

String planCodePlanCode planCode도 다르다. 전자는 문자열이고, 후자는 도메인 개념이다. PaymentServiceRefundPolicy도 다르다. 전자는 무엇이든 할 수 있는 서비스처럼 보이고, 후자는 환불 정책이라는 책임을 드러낸다.

좋은 이름은 추측을 줄인다. 그리고 도메인 전문가와 개발자가 같은 말을 하도록 돕는다.

DDD에서 유비쿼터스 언어가 중요한 이유는 결국 이것이다. 설계는 이름을 통해 드러난다. 이름이 흐리면 모델도 흐려진다.

모델은 계속 바뀐다

처음 만든 모델이 완벽할 수는 없다. 도메인을 더 이해하면 모델도 바뀐다. 처음에는 User 하나로 충분해 보였지만, 나중에는 Member, Subscriber, Buyer, Operator가 서로 다른 개념임을 알게 된다.

처음에는 CANCELED 상태 하나로 충분해 보였지만, 나중에는 사용자 해지, 관리자 직권 종료, 결제 실패로 인한 중지, 기간 만료, 환불 완료를 구분해야 한다는 사실을 알게 된다.

이 변화는 실패가 아니다. 도메인을 더 잘 이해하게 되었다는 신호다.

문제는 처음 만든 모델을 고집하는 것이다. 모델이 바뀌어야 하는 순간에 조건문과 예외 처리만 계속 쌓으면, 코드는 점점 도메인과 멀어진다.

DDD는 모델이 고정된 산출물이 아니라 계속 정제되는 지식이라는 점을 일깨운다.

경계를 나누는 일은 어렵다

DDD에서 가장 어려운 것은 클래스 하나를 잘 만드는 일이 아닐 수 있다. 더 어려운 것은 경계를 나누는 일이다.

Bounded Context를 나눈다는 것은 “여기서 이 말은 이런 의미다”라고 선언하는 일이다. 같은 단어라도 다른 맥락에서는 다른 모델일 수 있음을 인정하는 일이다.

구독 서비스의 “고객”은 회원 컨텍스트에서는 계정일 수 있고, 구독 컨텍스트에서는 구독자일 수 있으며, 정산 컨텍스트에서는 청구 대상일 수 있다. 이 차이를 인정하지 않으면 하나의 거대한 공통 모델을 만들게 된다.

경계를 나누는 일은 기술적 결정만이 아니다. 조직, 팀의 책임, 배포 구조, 데이터 소유권, 업무 언어가 모두 얽힌다. 그래서 어렵다.

하지만 경계를 나누지 않으면 복잡성은 사라지지 않는다. 단지 하나의 큰 모델 안에 섞일 뿐이다.

DDD는 모든 곳에 필요하지 않다

연재를 이어오면서 계속 강조하고 싶은 점은 이것이다. DDD는 모든 곳에 적용해야 하는 정답이 아니다.

단순한 CRUD에는 단순한 설계가 더 낫다. 모든 조건을 Specification으로 만들 필요도 없고, 모든 기능에 Domain Event를 발행할 필요도 없다. 모든 시스템을 CQRS와 Event Sourcing으로 만들 필요도 없다.

DDD는 복잡한 도메인에 설계 에너지를 집중하는 방법이다. Core Domain을 찾고, 그곳에 깊게 들어가야 한다. 반대로 Generic Subdomain이나 단순 지원 기능에는 과도한 설계를 피해야 한다.

좋은 설계는 복잡한 구조를 많이 쓰는 것이 아니다. 문제의 복잡도와 가치에 맞는 구조를 선택하는 것이다.

코드에 도메인 지식이 남아야 한다

DDD의 가장 현실적인 가치는 도메인 지식이 코드 안에 남게 한다는 점이다.

요구사항 문서와 회의록은 시간이 지나면 낡는다. 사람은 팀을 떠나고, 정책은 바뀐다. 결국 오랫동안 남는 것은 코드다. 그런데 코드가 도메인 언어를 말하지 못하면, 도메인 지식은 사라진다.

좋은 도메인 모델은 코드를 읽는 사람에게 이렇게 말한다.

이 구독은 어떻게 시작되는가?
언제 갱신되는가?
언제 해지될 수 없는가?
결제 실패는 어떤 의미인가?
요금제 변경 정책은 어디에 있는가?

코드가 이런 질문에 답할 수 있다면, 코드 자체가 도메인 지식의 저장소가 된다.

DDD는 도메인 지식을 코드에 보존하는 방법이다. 이것이 패턴보다 더 중요한 본질이라고 생각한다.

좋은 질문이 좋은 설계를 만든다

이 연재를 한 문장으로 마무리한다면 이렇게 말하고 싶다.

좋은 설계는 좋은 질문에서 시작된다.

DDD는 많은 개념과 패턴을 제공하지만, 그 패턴들은 질문을 대신해주지 않는다. 오히려 더 좋은 질문을 던지게 한다.

이 값은 원시 타입으로 충분한가, 도메인 개념인가?
이 객체는 식별성과 생명주기를 갖는가?
이 규칙은 어디에서 지켜야 하는가?
이 변경은 어떤 Aggregate 안에서 일관되어야 하는가?
이 이벤트는 도메인에서 의미 있는 사건인가?
이 모델은 어느 경계 안에서만 유효한가?
이 영역은 정말 Core Domain인가?

이 질문들에 계속 답하다 보면 모델은 조금씩 좋아진다. 처음부터 완벽한 모델을 만들 수는 없지만, 더 나은 모델로 이동할 수 있다.

마치며

DDD는 정답이 아니다. DDD를 적용한다고 좋은 설계가 자동으로 만들어지지도 않는다. Entity, Value Object, Aggregate, Repository를 만들었다고 해서 도메인 주도 설계가 되는 것도 아니다.

하지만 DDD는 좋은 방향을 제시한다. 도메인을 먼저 보라고 말한다. 도메인 전문가의 언어를 코드에 남기라고 말한다. 모델의 경계를 의식하라고 말한다. 중요한 도메인에 집중하라고 말한다. 그리고 모델을 계속 정제하라고 말한다.

내가 이해한 DDD는 결국 대화의 방식이다. 도메인 전문가와의 대화, 팀원과의 대화, 코드와의 대화, 그리고 계속 바뀌는 모델과의 대화다.

좋은 설계는 한 번에 완성되지 않는다. 좋은 질문을 계속 던지고, 그 답을 코드에 정직하게 반영하는 과정 속에서 조금씩 만들어진다. DDD는 그 과정을 안내하는 유용한 언어다.


페이지 수정
공유하기:

다음 글
[DDD Intro #29] DDD 도입 체크리스트