도메인 주도 설계(DDD)의 사실과 오해 1일차 정리

kyle·2025년 6월 14일
0

조영호님의 도메인 주도 설계의 사실과 오해 1일차를 수강하고 개인적으로 정리한 글입니다.

도메인 주도 설계(Domain Driven Design, DDD)는 사용자가 겪는 문제와 불편함을 쉽게 풀기위한 일종의 사고방식이다. 사고방식이라는 말이 굉장히 중요한데, 도메인 주도 설계에서는 어떻게 구체적으로 구현하는지 보다는, 특정 문제를 풀기 위해 어떤 포인트에 우선순위를 두느냐가 훨씬 중요하다는 의미이다.

하지만 요즘은 이러한 사고방식 보다는 구체적으로 어떻게 구현하는지에 더 초점을 맞추는 경향이 있다. 그래서 아래와 같은 오해들이 종종 생기곤 한다.

  • "DDD를 하려면 헥사고날 아키텍처를 써야 한다더라."
  • "DDD에서는 도메인의 순수성을 보장하기 위해 도메인 엔티티는 무조건 JPA 엔티티와 분리해야 한다더라."
  • "바운디드 컨텍스트와 애그리거트를 나누고 패키지 구조를 정했으니 DDD를 적용한 것이다." 등등

이번 강의에서는 DDD의 근본으로 알려진 에릭 에반스(Eric Evans)의 도메인 주도 설계라는 책을 기반으로 이러한 오해들을 풀고자 한다.


1. DDD의 핵심 : 동작하는 도메인 모델 만들기

책 Part 1의 제목은 동작하는 도메인 모델 만들기(Putting the Domain Model to Work)이다.
이 제목만 이해해도 사실상 DDD의 개념적인 부분에 대해서는 모두 아는 것과 같다.
용어를 하나씩 쪼개서 살펴보자.

  1. 도메인(Domain)

    • 도메인은 사용자가 프로그램을 사용하는 주제 영역을 의미하며, 해결하고자 하는 문제의 경계를 뜻한다.
    • 예를 들어, 음식 배달 시스템에서는 음식 조리나 실제 배달처럼 오프라인 영역은 도메인 경계에 포함되지 않을 수 있다.
    • 대신 메뉴 표시, 주문 접수, 결제 처리 등 온라인 자동화 영역이 도메인이 된다.
  2. 모델(Model)

    • 모델은 단순화를 의미한다. 복잡한 현실에서 관심 있는 요소만 추출하고 나머지를 추상화하는 과정이다.
    • 단순히 클래스 다이어그램을 그리는 것이 아니라, 핵심 개념과 그 관계를 정리하여 사람들이 문제를 이해하고 논의할 수 있도록 만드는 것이다.
    • 결론적으로, 도메인 모델(Domain Model)이란 사용자의 문제를 해결하고자하는 경계를 잘라내서, 이를 단순화 및 추상화 한 것을 말한다.
  3. 동작하는(to work)

    • 과거에는 도메인 모델, 분석 모델, 설계 모델, 구현 모델을 별개의 산출물로 취급했다.
    • DDD는 이 구분을 허물고, 도메인 모델이 곧 코드여야 한다고 강조한다.
    • 즉, 도메인의 핵심 개념이 코드에 그대로 반영되어야 한다는 의미이다.
    • 따라서, 도메인이 변경되면 설계가 변경되고 그에따라 구현 코드가 변경된다. 이는 매우 자연스러운 흐름이다.
  4. 만들기

    • 지식 탐구(Knowledge Crunching)
      • 개발자와 도메인 전문가가 협력하여 문제를 깊이 이해하고 유의미한 지식을 도출하는 과정이다.
      • 서로 따로 작업하는 것이 아니라, 끊임없이 소통하며 도메인 모델을 발전시킨다.
    • 유비쿼터스 언어(Ubiquitous Language)
      • 비즈니스 용어와 개발 용어를 통일하여, 팀 전체가 공통으로 이해하고 사용하는 언어를 구축하는 것이다.
      • 단순한 용어집을 만드는게 아니라 빈번한 커뮤니케이션을 통해 자연스럽게 발전하며, 요구사항 논의부터 코드 작성까지 일관되게 사용한다.

결론적으로 DDD는, 사용자 문제 해결의 영역을 단순화하고, 유비쿼터스 언어를 통해 소통하며 깊게 이해하고, 이를 바탕으로 도메인 모델을 코드에 반영하는 것을 말한다.
이는 하나의 사고방식이므로, 이러한 사고방식이 적용되었다면 어떠한 프레임워크든 어떠한 아키텍처든 DDD가 적용되었다고 할 수 있는 것이다.


2. 객체 지향과 애자일 : DDD 등장 배경

DDD가 2003년에 등장할 당시 소프트웨어 개발 환경은 객체 지향 프로그래밍과 애자일 방법론이 부흥하던 시기였다. 이 두 흐름은 DDD 철학 형성에 큰 영향을 미쳤다.

  1. 객체 지향의 부흥과 기술 중심 개발의 한계

    • 1990년대 GUI 애플리케이션 확산으로 OOP가 각광받았다. 버튼, 텍스트 필드 등이 객체 지향 개념에 적합했기 때문이다.
    • 이후 웹이 등장하면서 서버 사이드 로직이 중요해졌고, Java 같은 OOP 언어가 개발의 주류가 되었다.
    • 그러나 기술 중심 접근은 부작용을 초래했다. 분산 객체 기술과 EJB 등의 프레임워크 의존은 도메인 로직과 기술적 관심사를 뒤섞어 생산성을 저해했다. 개발자는 기술 제약에 맞춰 코드를 작성하게 되었고, 이는 도메인의 본질을 왜곡하는 것으로 이어졌다.
    • 이에 대한 반작용으로 순수 Java 객체(POJO) 기반 도메인 로직 표현 움직임이 나타났다. DDD는 기술에 의존하지 않고 도메인에 집중해야 한다는 철학을 강조하며 등장했다.
  2. 애자일 방법론과 리팩토링의 중요성

    • DDD 출간 당시 소프트웨어 프로세스 역시 큰 변화를 겪고 있었다.
    • 전통 워터폴 방식은 초기 불완전 요구사항과 빈번한 변경에 취약했다.
    • 애자일은 변경을 좋아하고, 짧은 주기로 피드백을 받아 반복적으로 개발하는 방식을 채택한다. 핵심은 빠르기가 아니라 변경에 민첩하게 대응함(Agile)이다.
    • 이 철학은 TDD, CI/CD, 페어 프로그래밍, 리팩토링 등 현대에는 당연히 받아들여지는 기법들의 기반이 되었다.
    • 특히 리팩토링은 DDD에서 필수다. 도메인 모델은 한 번에 완벽해질 수 없으므로, 지속적 리팩토링으로 점진적 개선이 필요하다는 철학이 반영되었다.

3. 모델 주도 설계의 빌딩 블록 (전술적 설계)

책에서는 또한 도메인 중심의 코드를 작성하기 위한 구체적인 빌딩 블록(Building Blocks)을 제시한다.
요즘 DDD에서 전술적 설계라고 불리는 것들에 해당한다.

  1. 도메인 로직의 격리

    • DDD는 특정 아키텍처를 강제하지 않지만, 도메인 로직과 기술적 관심사(DB 접근, 프레임워크 종속 등)를 분리하는 것을 중요 원칙으로 삼는다.
    • 이를 통해 코드에 대한 이해, 유지보수, 변경이 용이해진다.
  2. 불변식(Invariants)과 애그리거트(Aggregate)

    • 불변식 : 도메인에 정의된 비즈니스 규칙으로, 시스템 상태가 변경되더라도 항상 유지되어야 한다 (예: 주문 금액이 음수가 될 수 없음).
    • 애그리거트 : 불변식을 지키기 위해 관련 객체를 하나의 단위로 묶는 개념이다. 동시성 상황에서도 시스템 일관성을 유지하며, 트랜잭션과 저장(레포지토리) 관리를 단위로 수행한다.

비즈니스 규칙의 중요한 경계는 자주 변경되지 않는다는 것에 따라, 애그리거트 단위를 설정하면 코드 구조와 유지보수 측면에서 유리하다.
애그리거트는 불변식의 일관성을 유지할 수 있는 최소 단위로 정의되어야 한다. (불변식의 단위는 곧 트랜잭션의 단위가 됨)


4. 전략적 설계

전략적 설계에 관해서는 아래와 같이 설명한다.

  1. 여러 도메인 모델과 바운디드 컨텍스트(Bounded Context)

    • DDD는 하나의 거대 전사 도메인 모델을 지양하고, 여러 도메인 모델을 독립적으로 구성할 것을 제안한다.
    • 규모가 커질수록 여러 팀이 단일 코드베이스를 관리하는 것은 현실적이지 않으며, 단일 코드베이스는 코드 충돌, 소통 문제, 책임 불명확 등의 이슈를 발생시킨다.
    • 바운디드 컨텍스트특정 도메인 모델이 정의되는 경계를 의미한다.
      • 상품이라는 개념이 판매 컨텍스트, 배송 컨텍스트, 주문 컨텍스트마다 다를 수 있으며, 각 컨텍스트가 독립적인 도메인 모델을 가진다.
    • 중요한 점은, 바운디드 컨텍스트가 반드시 마이크로서비스처럼 물리적 분리를 의미하지는 않는다. 모놀리식에서도 모듈화로 구현 가능하다.
    • 컨텍스트 간 관계와 데이터 변환 방식은 컨텍스트 맵(Context Map)으로 정의해 관리한다.
  2. 도메인 정제(Distillation) : 코어/제네릭/서포팅 서브도메인

    • DDD는 모든 도메인 요소가 동등하게 중요한 것은 아님을 인식하고, 효율적 자원 배분을 위해 도메인을 분류할 것을 권장한다.
      • 코어 도메인(Core Domain) : 회사 핵심 경쟁력 영역으로, 가장 많은 리소스와 전문 인력을 투입할 대상에 해당한다.
      • 제네릭 서브도메인(Generic Subdomain) : 공통적 문제 영역으로, 외부 솔루션 도입이나 최소한의 투자로 처리할 수 있다 (예: ERP).
      • 서포팅 서브도메인(Supporting Subdomain) : 운영에 필요하지만 핵심은 아닌 영역으로, 외주나 효율적 내부 관리가 가능하다.
    • 이 분류는 문제 공간(Problem Space)의 서브도메인과, 솔루션 공간(Solution Space)의 바운디드 컨텍스트 간 대응 관계를 정립하는 데 유용하다。

5. DDD의 최신 동향과 실용적 적용

DDD는 시간이 지나면서 다양한 개념과 연계되어 진화해 왔다.

  1. 도메인 이벤트(Domain Event)와 이벤트 소싱(Event Sourcing)

    • 애그리거트 간 결합도를 낮추기 위해 도메인 이벤트를 활용한다.
    • 애그리거트 상태 변경 시 이벤트를 발행하여, 타 애그리거트나 외부 시스템이 반응하게 한다.
    • 이것은 직접 수정 대신 결과적 일관성(Eventual Consistency)을 지향한다.
    • 이벤트 소싱은 모든 도메인 이벤트를 저장하고, 이를 재생하여 시스템 상태를 복구하는 패턴이다. 최종 상태뿐 아니라 변경 과정까지 기록하여 유연한 데이터 관리가 가능해진다.
  2. CQRS (Command Query Responsibility Segregation)

    • 조회(Query) 로직과 수정(Command) 로직이 섞이면 도메인 모델이 비대해질 수 있다.
    • CQRS는 이를 해결하기 위해 조회 전용 모델과 명령/비즈니스 처리 모델을 분리한다.
    • 이로써 도메인 모델 단순성을 유지하고, 각 기능을 쉽게 최적화할 수 있다.
  3. MSA, 헥사고날 아키텍처와의 관계

    • 최근 마이크로서비스 아키텍처(MSA) 확산과 함께 DDD가 재조명받고 있다.
    • DDD의 바운디드 컨텍스트는 MSA에서 서비스 경계 설정의 중요한 기준이 된다.
    • 또한 헥사고날 아키텍처(Ports and Adapters)는 도메인 로직과 인프라를 분리하여, DDD와 자주 함께 언급된다.
    • 그러나 DDD, MSA, 헥사고날 아키텍처는 서로 별개의 개념이며, DDD는 특정 아키텍처를 강제하지 않는다.
    • 도메인 로직이 격리된다면 어떤 아키텍처에서도 DDD 철학을 적용할 수 있다.

6. DDD는 복잡성 관리의 철학이다!

DDD의 본질은 사용자 문제 해결에서 출발한다. 그리고 이를 도메인을 이해하고 단순화하여 코드에 반영하는 데서 해답을 찾는다.

DDD는 한 번에 모든 것을 완벽히 설계하는 방법론이 아니다.
오히려 변화를 수용하고, 도메인 전문가와 개발자가 유비쿼터스 언어로 협력하며, 지속적 리팩토링으로 모델을 점진적으로 개선하는 사고 방식이자 철학이다.
이를 통해 현실의 복잡 속에서도 유연하고 견고하며, 도메인 변화에 민첩하게 대응하는 소프트웨어를 구축할 수 있다.

DDD의 개념은 이상적으로 보일 수 있지만, 현업에서는 트레이드 오프와 제약 속에서 현실적으로 적용된다.
따라서 DDD를 무조건 따르는 규칙이 아닌, 복잡한 도메인 문제 해결과 효과적 소프트웨어 설계를 위한 실용적 도구로 상황에 맞춰 활용할 지혜가 필요하다.


7. 느낀점

사실 강의를 듣기 이전까지는 나도 다른 사람들과 다르지 않게, 공공연한 오해를 가지고 있었다.
실제로 회사에서 프로젝트를 할 때, "JPA Entity와 Domain Entity를 구분하면 확장성 있고 좋지 않을까?"라는 생각으로 무작정 분리해서 개발하기도 했다.
그로 인해 작은 프로젝트였음에도 불구하고, 불필요한 매퍼 클래스와 기타 코드들이 우후죽순 생겨나서 유지보수하기 힘들어졌을 때도 있었다.
DDD는 허황된 것인가라는 생각도 해봤고, 아니면 내가 '코드'를 잘 못써서 그랬나? 라는 생각도 했었다.

오늘 강의를 듣고나서 생각해보니, 평소에 내가 너무 구현에만 집착하고 있었던 것 같다.

  • "어떻게 패키지를 나누지? application, domain, infrastructure, presentation 이면 되려나?"
  • "어떻게 도메인 순수성 보장하지? JPA Entity와 별개로 POJO로 Domain Entity를 만들고 시작하면 되려나?"
  • "DDD하려면 일단 비슷한 개념끼리 바운디드 컨텍스트랑 애그리거트로 나누고 시작하면 되려나?"
  • "DDD하려면 어떤 고급 아키텍처를 학습해야 하나?"
  • "유비쿼터스 언어 중요하다는데, 대강 합의해서 노션에 단어장으로 작성해놓으면 되려나?"
  • "애자일은 무조건 빠르게 만들면 되는거 아닌가? 일단 만들기만 하고 출시하면 되려나?"

이런 생각들만 항상 가지고 있었고, "정확히 우리가 풀고자하는 문제는 무엇인지, 이를 어떻게 단순화하여 모델로 만들 것인지, 불변식과 트랜잭션 단위는 어떻게 되는지..." 등등은 생각해본 적이 없었다.

결국 바운디드 컨텍스트, 애그리거트, 엔티티, 값객체, 단어장, 데일리스크럼, 회고 등등 이런 것들은 모두 DDD와 애자일을 달성하기 위해 사용되는 도구이자 결과물들에 불과하다. 중요한 것은 이런 것들을 왜 정의하고 수행하는지 그 이유가 있어야 한다는 것이다. 일단 결과물들을 적용했으니 DDD라거나, 애자일하게 개발을 하고 있다거나 하는 것들은 모두 순서가 반대가 된 것이다.

모놀리식에 단순한 레이어드 아키텍처 기반이라 해도 DDD의 사고방식을 따르면, 그건 DDD를 적용했다고 볼 수 있다.
이렇듯 앞으로는 특정한 구현이나 세세한 코드에 집착하지 않고, 사고방식에 집중하는 습관을 들여야겠다. 사고방식에 집중하면 자연스럽게 도구는 따라올 것이다.
"애자일은 회고가 필수라던데, 그래서 주기적으로 회고를 해요."가 아니라 "피드백 기반으로 민첩하게 개발하기 위해 주기적으로 회고하고 다음 방향성으로 빠르게 바꾸어요."가 되어야 하는 것이다.

profile
https://github.com/kylekim2123

0개의 댓글