0484

개발

Domain Driven Design, 개념부터 실제 서비스 적용까지

| 서론

안녕하세요, 팡일입니다.

서비스를 만들다 보면 처음에는 단순한 CRUD처럼 보였던 기능들이 시간이 지날수록 점점 복잡해지는 순간을 마주하게 됩니다. 글을 작성하고, 수정하고, 삭제하는 기능만 있다고 생각했지만, 실제로는 사용자, 권한, 상태, 정책, 통계, 피드백 같은 다양한 규칙들이 함께 얽히기 시작합니다.

이때 중요한 것은 단순히 폴더를 예쁘게 나누는 것이 아니라, 서비스가 해결하려는 문제를 중심으로 구조를 잡는 것입니다. DDD는 바로 이 지점에서 의미를 가집니다. 코드를 기술 중심이 아니라 도메인 중심으로 바라보고, 비즈니스의 핵심 개념이 코드 안에서도 자연스럽게 드러나도록 도와주는 설계 방법론이기 때문입니다.

이번 글에서는 DDD의 기본 개념을 정리한 뒤, 제가 진행했던 re-log 서비스를 DDD 관점에서 바라본다면 어떻게 해석할 수 있을지 함께 정리해보겠습니다.

| DDD란?

DDD는 Eric Evans가 2003년에 출간한 "Domain Driven Design"이라는 책에서 처음 소개된 개념으로, 복잡한 도메인 모델을 설계하고 구현하기 위한 방법론입니다.

DDD는 도메인 전문가개발자가 협력하여 도메인 모델을 정의하고, 이를 기반으로 소프트웨어를 설계하는 것을 목표로 합니다.

1) 주요 개념

(1) 도메인

: 소프트웨어가 해결하고자 하는 문제 영역입니다.

  • 세 가지 서브 도메인
    : 서브 도메인은 전체 도메인을 보다 작은 단위로 나누어, 각각의 서브 도메인이 특정 비즈니스 문제를 해결하도록 하는 것입니다. 이 접근 방식은 서브 도메인 간의 경계를 명확히 하여, 각 서브 도멘인이 독립적으로 관리되고 확장될 수 있도록 합니다.

    1. 코어 도메인 (Core Domain): 비즈니스의 핵심 가치를 감당하는 영역입니다. 여기서의 모델링이 비즈니스 성공에 직접적인 영향을 미칩니다.

    2. 지원 도메인 (Supporting Domain): 코어 도메인을 지원하는 역할을 합니다. 중요도는 코어 도메인보다 낮지만, 코어 도메인이 제대로 기능할 수 있도록 도와주는 필수적인 부분입니다.

    3. 일반 도메인 (Generic Domain): 다른 비즈니스에서도 공통적으로 사용되는 일반적인 기능을 담당합니다. 주로 표준화된 기술이나 외부 라이브러리로 대체할 수 있습니다.

  • 예시로 보는 서브 도메인 이해

    1. 코어 도메인 : 주문 처리, 배달 배차

    2. 지원 도메인 : 알림 발송, 정산 처리

    3. 일반 도메인 : 사용자 인증, 결제 게이트웨이

(2) 유비쿼터스 언어(Ubiquitous Language)

: 도메인 전문가와 개발자가 공통으로 사용하는 언어로, 도메인 모델을 설명하는 데 사용됩니다. 이를 통해 팀원 간의 의사소통을 원활하게 하고, 도메인에 대한 이해를 높일 수 있습니다.

  • 예를 들어, 기획자가 '주문'이라고 할 때, 개발자도 코드에 'Order'라고 씁니다. 코드에 언어가 그대로 반영됩니다.

  • 유비쿼터스 언어가 클래스명, 메서드명, 데이터베이스 컬럼명으로 그대로 사용됩니다.

  • 예 : 고객이 주문을 하면 결제가 승인되고 배달원이 배차된다.

    • 이 문장이 그대로 코드의 흐름이 된다.

  • 주의할 점 : 경계가 다르면, 같은 단어도 의미가 다를 수 있다.

(3) 바운디드 컨텍스트 (Bounded Context)

: 도메인을 명확하게 구분 짓는 경계입니다. 각 바운디드 컨텍스트는 독립적으로 개발되고 배포될 수 있으며, 서로 다른 바운디드 컨텍스트 간의 상호작용은 명확한 인터페이스를 통해 이루어집니다.

  • 하나의 컨텍스트 내에서만 특정 모델과 용어를 사용하도록 함으로써, 혼란을 줄이고, 각 컨텍스트가 독립적으로 관리될 수 있도록 합니다.

  • ContextMap : 여러 Bounded Context가 어떻게 연결되고 관계를 맺는지 보여주는 지도

    • 프로젝트의 전체 도메인 구조를 한눈에 파악할 수 있습니다.

    • Published Language (공개 계약): 컨텍스트 간 통신에 사용되는 공유 모델)

(4) 어그리게이트(Aggregate)

: 도메인 모델의 일관성을 유지하기 위해 관련된 객체들을 묶어 관리하는 단위입니다. 어그리게이트는 하나의 루트 엔티티(aggregate root)를 가지며, 외부에서는 루트 엔티티를 통해서만 접근할 수 있습니다.

  • 어그리게이트 기준으로 도메인을 분리하면, 도메인의 특정 부분에서 일관성을 유지하는 데 집중할 수 있습니다.

  • 어그리게이트는 도메인 로직이 응집력을 가지도록 도와주며, 트랜잭션 경계를 명확히 할 수 있습니다.

  • 즉, 여러 Entity와 valueObject의 묶음이다.

  • 예시

    • Order Aggregate (Order, OrderItems, Address)

    • 주문 총액은 항상 주문 항목들의 합과 일치해야 한다.

  • Aggregate Root

    • 비즈니스 불변 규칙을 보호한다.

      • 예시 : 배달 시작 전에만 주문을 취소할 수 있다"

    • 도메인 이벤트를 발행한다.: 상태 변경이 일어나면, Aggreate Root는 이벤트를 발행하고, 이 이벤트들이 다른 서비스들의 동작을 트리거한다.

(5) 엔티티 (Entity)

: 고유한 식별자를 가지며, 상태와 행동을 갖는 객체입니다. 엔티티는 도메인 모델의 핵심 구성 요소입니다.

  • 시간이 지나도 동일한 식별자를 유지하며, 상태가 변할 수 있습니다.

  • 생명 주기를 가지며, 생명 주기의 흐름이 곧 비즈니스 프로세스를 표현합니다.

  • 예시 : Order, Customer, Restaurant

(6) 벨류오브젝트 (Value Object)

: 고유한 식별자를 가지지 않으며, 불변성을 가지는 객체입니다. 이는 도메인 모델의 속성을 표현하는 데 사용됩니다.

  • 값 객체는 단순히 값만을 담고 있습니다.

  • 예시 : Money, Address, OrderId

2) 3가지 측면

(1) 도메인 탐색(탐색적 DDD)

: 도메인의 현재와 미래, 목표 상태를 모델링햐며, 팀 전체가 도메인을 학습하고 해결책 아이디어를 도출할 수 있도록 시각적이고 협업적인 방법을 지원합니다.

(2) 소프트웨어 아키텍처(전략적 DDD)

: 대규모 소프트웨어 시스템을 도메인 영역에 맞게 분할된 컨텍스트로 나누고, 도메인 이벤트를 통해 이 컨텍스트를 연결합니다. 이 과정에서 전략적으로 중요한 핵심 도메인을 식별하는 것이 중요합니다.

  • 컨텍스트 맵핑 기술을 활용해 여러 개의 Bounded Context를 통합

  • 이벤트 스토밍 기법을 활용해 Bounded Context를 식별

(3) 소프트웨어 설계 (전술적 DDD)

: 도메인에 대한 팀의 공통된 이해를 바탕으로 코드를 작성하며, 각 컨텍스트에서 적절한 패턴(엔티티, 어그리게이트, 이벤트 소싱 등)을 사용하여 소프트웨어를 설계합니다.

  • 전략적 설계에서 더 상세한 부분(Bounded Context 내부)을 모델링

  • 계층형 아키텍처를 통한 도메인 모델을 분리

  • 도메인 이벤트를 통해 도메인을 보다 명확히 모델링

| re-log에서 보는 DDD

지금까지 DDD의 개념을 살펴봤다면, 이제는 이를 실제 서비스에 어떻게 적용해볼 수 있는지 살펴보는 것이 중요합니다.

re-log는 겉으로 보면 단순한 회고 작성 서비스처럼 보일 수 있지만, 내부적으로는 사용자 경험, 감정 기록, 성장 추적이라는 명확한 도메인을 가지고 있습니다.

이 관점에서 re-log를 DDD로 해석해보면, 단순한 CRUD 기반 서비스가 아니라 도메인 중심으로 구조화할 수 있는 서비스임을 확인할 수 있습니다.

1) 도메인 정의

re-log의 도메인은 단순히 “게시글 관리”가 아닙니다.

이 서비스가 해결하려는 문제는 다음과 같습니다.

  • 사용자의 하루를 기록한다

  • 감정을 함께 저장한다

  • 회고를 통해 성장을 추적한다

즉, re-log의 도메인은 다음과 같이 정의할 수 있습니다.

“사용자의 경험과 감정을 기록하고, 이를 통해 성장을 돕는 회고 서비스”

2) 서브 도메인 분리

이 도메인을 기준으로 서브 도메인을 나누면 다음과 같이 정리할 수 있습니다.

(1) Core Domain

: 가장 중요한 영역은 Retrospect(회고)입니다.

  • 회고 작성

  • 회고 수정 / 삭제

  • 감정 선택

  • 공개 범위 설정

이 부분이 서비스의 핵심 가치이며, DDD에서 가장 많은 설계와 고민이 들어가야 하는 영역입니다.

(2) Supporting Domain

: 코어 도메인을 지원하는 영역입니다.

  • Emotion (감정)

  • Category (회고 분류)

  • Feedback (사용자 피드백)

이들은 회고를 더 의미 있게 만들어주는 역할을 합니다. 예를 들어 감정은 단순 UI 요소가 아니라, 사용자의 상태를 해석하는 중요한 기준이 됩니다.

(3) Generic Domain

: 일반적으로 많이 사용하는 공통 기능입니다.

  • 사용자 인증 (Google OAuth 등)

  • 권한 관리

  • 기본 CRUD 인프라

이 부분은 직접 구현하기보다 외부 서비스나 라이브러리를 활용하는 것이 효율적입니다.

3) Bounded Context 나누기

re-log를 Bounded Context 기준으로 나누면 다음과 같이 볼 수 있습니다.

bash
[Retrospect Context]
- 회고 작성
- 감정 선택
- 회고 공개 설정
- 회고 조회

[User Context]
- 로그인
- 사용자 정보 관리

[Feedback Context]
- 사용자 의견 작성
- 피드백 관리

[Admin Context]
- 사용자 관리
- 회고 통계
- 운영 데이터 확인

이렇게 나누면 각 영역이 단순한 기능 단위가 아니라, 명확한 책임을 가진 도메인 단위로 분리됩니다.

4) Aggregate 설계

re-log에서 가장 중요한 Aggregate는 Retrospect입니다.

bash
Retrospect {
  id
  authorId
  content
  emotion
  category
  visibility
  createdAt
}

여기서 Retrospect는 단순 데이터 구조가 아니라, 다음과 같은 규칙을 가지고 있습니다.

  • 감정은 반드시 하나 선택되어야 한다

  • 작성자는 반드시 존재해야 한다

  • 공개 범위는 특정 값으로 제한된다 (private / public 등)

이러한 규칙은 Aggregate Root인 Retrospect가 책임집니다. 즉, 외부에서는 Retrospect를 통해서만 상태를 변경해야 합니다.

5) Entity와 Value Object

(1) Entity

  • Retrospect

  • User

이들은 고유한 ID를 가지며, 시간이 지나도 동일한 객체로 유지됩니다.

(2) Value Object

  • Emotion

  • Category

이들은 식별자가 필요하지 않으며, 값 자체가 의미를 가집니다. 예를 들어 “성장”, “좌절” 같은 감정은 동일하면 같은 값으로 취급됩니다.

6) Ubiquitous Language 적용

re-log에서 사용하는 용어를 그대로 코드에 반영하면 다음과 같습니다.

  • 회고 → Retrospect

  • 감정 → Emotion

  • 피드백 → Feedback

이렇게 되면 기획과 개발이 같은 언어를 사용하게 됩니다.

예를 들어 “회고에 감정을 추가한다”

retrospect.addEmotion(emotion)

이처럼 비즈니스 언어가 그대로 코드가 되는 구조를 만들 수 있습니다.

| 결론

DDD는 단순히 복잡한 아키텍처를 적용하는 방법론이 아니라, 서비스의 본질을 코드 구조에 반영하기 위한 사고방식에 가깝습니다.

re-log를 예로 들면, 이 서비스는 단순히 글을 저장하는 게시판이 아니라 사용자의 하루와 감정, 그리고 성장의 흐름을 기록하는 회고 서비스입니다. 따라서 기능을 페이지나 API 단위로만 나누기보다, 회고, 감정, 사용자, 피드백처럼 서비스의 의미를 담고 있는 도메인 기준으로 바라보는 것이 더 자연스럽습니다.

물론 작은 프로젝트에 DDD를 무리하게 적용하면 오히려 구조가 과해질 수 있습니다. 하지만 DDD 관점으로 서비스를 분석해보는 것만으로도 “이 서비스의 핵심 가치는 무엇인가?”, “어떤 개념을 중심으로 코드를 나눠야 하는가?”, “어떤 규칙이 어디에서 관리되어야 하는가?”를 고민할 수 있습니다.

결국 DDD는 정답 같은 구조를 만드는 것이 아니라, 팀이 같은 언어로 문제를 이해하고, 서비스의 핵심 가치를 코드에 더 명확히 담아내기 위한 방법이라고 볼 수 있습니다.