Study/Side Proejct / / 2025. 3. 19. 21:29

[DDD-Quickly] 3,4장 요약 정리 - 모델 주도 설계와 리팩터링

이번 포스팅에서는 도메인 주도 설계(DDD) 책의 3장과 4장에 대해 정리하고, 그 과정에서 얻은 인사이트를 공유해보려 합니다. DDD는 단순히 “도메인을 분석하고 모델링하는 것”을 넘어, 모델을 코드와 긴밀하게 연결해 나가는 일련의 과정이 핵심이라는 점이 인상 깊었습니다. 3장과 4장에서 평소에 가장 궁금했던 내용들에 대한 답이 많이 나온 것 같습니다.


1. 모델 주도 설계(Model-Driven Design)란?

소프트웨어 개발에서 “분석과 설계가 완전히 분리”되고, 비즈니스 전문가, 분석가, 개발자가 서로 동떨어진 방식으로 일하면 모델과 코드 간에 큰 괴리가 발생합니다.
→ 이 괴리를 줄이기 위해 고안된 설계 기법이 바로 도메인 주도 설계(DDD)입니다.

1) 왜 모델 주도 설계인가?

  • 도메인의 핵심 개념을 담은 “모델”이 소프트웨어의 설계와 구현 전 과정에서 중심이 됩니다.
  • 모델을 기반으로 개발하면, “코드의 변경 = 모델의 변경”이라는 등식을 지킬 수 있습니다.
  • 모델의 무결성(정확성)을 유지하려면, 모델을 잘 아는 사람이 실제 코드 구현도 책임져야 합니다.

2) 모델 주도 설계 vs. 개발 툴과 언어

  • DDD는 자체가 특정 기술 스택·프레임워크를 강제하지 않습니다.
  • 다만, 객체지향 프로그래밍(OOP)처럼 “모델링 패러다임”을 지원하는 툴·언어가 더 적합합니다.
    • 예: 클래스, 상호 메시지, 인스턴스 등이 모델의 개념(엔티티, 값 객체, 연관)과 직접 매핑될 수 있으므로.

2. 분석 모델만으로는 부족하다

분석 단계에서 만든 “분석 모델”이 아무리 훌륭해도, 실제 코드 구현에 까다로우면 현장에서 쓸모가 떨어집니다.

  • “엔티티의 영속성” 문제, 복잡한 객체 연관 등은 코드로 옮길 때 드러나는 실질적인 이슈입니다.
  • 분석 모델과 코드 설계를 별도의 담당자가 각각 진행하면 매핑 관계가 점차 사라질 수 있습니다.

→ 해법: 모델과 코드 설계 과정을 끊임없이 왕복하며 개선하자

결국, 도메인 전문가와 개발자가 서로의 영역에서만 일하지 않고, 모델을 공동으로 다듬고 코드에 반영하는 “모델 주도 설계”가 필수라는 결론에 도달하게 됩니다.


3. 모델 주도 설계를 위한 핵심 패턴들

DDD의 주요 설계 패턴에는 엔티티, 값 객체, 서비스, 모듈, 애그리게잇, 팩토리, 리파지토리 등이 있습니다.
이를 어떻게 배치하고 계층화하느냐가 DDD 구현의 중요한 포인트가 됩니다.

1) 계층형 아키텍처(Layered Architecture)

  • 도메인 레이어: 비즈니스 로직의 심장부
  • 애플리케이션 레이어: 애플리케이션 서비스(흐름 제어) 담당
  • UI/프레젠테이션 레이어: 사용자에게 정보 표시, 사용자 입력 수집
  • 인프라스트럭처 레이어: 데이터베이스, 네트워크, 메시지 전달 등 기술적인 부분

→ 각 레이어가 “도메인”과 섞이지 않도록 분리하면, “도메인 로직”이 선명해지고 재사용성, 유지보수성이 높아집니다.

2) 엔티티(Entity)

  • “식별자(Identifier)”로 구분되는 지속성 있는 객체
  • 라이프사이클 전반에 걸쳐 같은 식별자를 유지
  • 예: 주문(ORDER), 계좌(ACCOUNT), 회원(USER) 등이 대표적인 엔티티가 될 수 있습니다.

3) 값 객체(Value Object)

  • 식별자가 없는 객체, 속성 자체가 중요한 케이스
  • 불변(Immutable)으로 다루는 것이 권장됨
  • 예: 주소(Address), 좌표(Coordinates), 통화(Money) 등이 해당

4) 서비스(Service)

  • “도메인 개념을 객체로 매핑하기 어렵거나, 여러 엔티티에 걸친 행위”를 캡슐화
  • 상태를 가지지 않으며, 특정 연산만을 제공
  • 예: 여러 계좌 간 자금 이체, 결제 승인/취소 등

5) 모듈(Module)

  • 도메인이 커질수록 구조화와 조직화가 필요
  • 높은 응집도, 낮은 결합도를 요구
  • 모듈에 명확한 역할과 이름을 부여해, 코드 가독성과 유지보수성을 개선

6) 집합(Aggregate)

  • 모델 내 연관 관계가 복잡해질 경우, “하나의 통일된 무결성”을 적용하기 위해 사용
  • 루트 엔티티(Root)를 통해서만 내부 엔티티나 값 객체를 조작
  • 불변식을 유지하는 데 큰 도움을 줌

4. 깊은 통찰을 향한 리팩터링

처음 만든 모델은 항상 “거칠고 피상적”입니다. 도메인이 복잡하다면, 여러 번의 시행착오와 리팩터링을 거쳐야 비로소 안정되고 깊이 있는 모델이 완성됩니다.

1) 지속적인 리팩터링

  • 기술적인 리팩터링: 코드 구조와 품질 개선(중복 제거, 메서드 추출 등)
  • 도메인 모델 리팩터링: 도메인 지식이 바뀌거나 녹아들어 설계 자체를 개선
  • 두 가지 방향이 함께 진행되어야 유의미한 변화가 생깁니다.

2) 핵심 개념 드러내기

  • 기존 코드 상에 암시적으로 존재하던 개념을 찾아서 명확히 드러내는 과정
  • 각종 계산이나 절차가 복잡해 보일 때, “사실상 존재하는 핵심 개념”을 모델로 끌어내면 가독성이 크게 개선

예시) 책에서 든 간단한 예로, 책장을 구현하는 Bookshelf 코드가 있다고 해봅시다:

public class Bookshelf {
    private int capacity = 20;
    private Collection<Book> content;

    public void add(Book book) {
        if(isSpaceAvailable()) {
            content.add(book);
        } else {
            throw new IllegalArgumentException("The bookshelf has reached its limit.");
        }
    }

    private boolean isSpaceAvailable() {
        return content.size() < capacity;
    }
}
  • 여기서 공간 제약(capacity) 같은 “불변식(invariant)”을 좀 더 명시적으로 드러낼 수 있습니다.
  • 이 과정에서 isSpaceAvailable() 메서드는 “제약 조건”을 표현하는 로직이 됩니다.

3) 명세(Specification)

  • 특정 객체가 “어떤 조건”을 만족하는지 여부를 판별하는 로직
  • 복잡한 비즈니스 규칙을 명세화된 객체 안에 캡슐화하여, 코드 중복을 제거하고 일관성을 높일 수 있습니다.
  • 큰 규칙을 여러 개의 작은 규칙(명세)로 분할, 이를 조합(AND, OR 등)해 가독성을 높이게 됩니다.

✍️ 마무리 정리

  1. “모델 주도 설계(Model-Driven Design)”는 모델이 단순한 UML이나 분석 산출물로 끝나는 게 아니라, 실제 코딩 단계까지 긴밀히 스며드는 설계 기법
  2. 엔티티, 값 객체, 서비스, 모듈, 애그리게잇, 팩토리, 리파지토리 패턴을 통해 도메인의 구조와 라이프사이클을 명확히 표현할 수 있음
  3. 모델과 코드는 서로 떨어질 수 없는 한 몸이며, 개발자는 모델을 아주 잘 이해해야 함
  4. 초기 모델은 얕을 수밖에 없고, 현실 도메인이 복잡할수록 끊임없는 리팩터링과 협업을 통해 점점 정교해짐
  5. 지속적인 리펙토링을 통해 안정화가 가능함