2carrot84
by 2carrot84
8 min read

Categories

  • book

Tags

  • DDD
  • Domain Driven Design
  • 도메인 주도 개발 시작하기

오늘은 우아콘2024 관련 유튜브 영상 DDD 그거 그렇게 하는 거 아닌데 를 보다 팀에서 같이 스터디를 하며 읽었던 최범균님의 도메인 주도 개발 시작하기 책이 생각나서 스터디 하면서 정리해둔 내용을 포스팅으로 옮겨두면 좋겠다 싶어서 포스팅을 하려고 합니다. 도메인 주도 개발 시작하기

이번 포스팅에서는 챕터 1,2 내용을 다루고자 합니다.

chapter 1. 도메인 모델 시작하기
chapter 2. 아키텍처 개요

chapter 1. 도메인 모델 시작하기

1.1 도메인이란?

  • 소프트웨어로 해결하고자 하는 문제 영역
    • 한 도메인은 다시 하위 도메인으로 나눌 수 있다
      • 한 하위 도메인은 다른 하위 도메인과 연동하여 완전한 기능을 제공
    • 특정 도메인을 위한 소프트웨어라고 해서 도메인이 제공해야 할 모등 기능을 직접 구현하는 것은 아니다 (외부시스템 연계)
    • 도메인마다 고정된 하위 도메인이 존재하는 것은 아니다
    • 하위 도메인을 어떻게 구성할지 여부는 상황에 따라 달라진다

1.2 도메인 전문가와 개발자간 지식 공유

  • 전문가는 해당 도메인에 대한 지식과 경험을 바탕으로 본인들이 원하는 기능 개발을 요구한다
    • 개발자는 이런 요구사항을 분석하고 설계하여 코드를 작성하여 테스트하고 배포한다
      • 요구사항을 올바르게 이해하지 못하면 요구하지 않은 엉뚱한 기능을 만들게 된다
  • 요구사항을 올바르게 이해하려면 어떻게 해야 하나?
    • 개발자와 전문가가 직접 대화
      • 개발자와 전문가 사이에 내용을 전파하는 전달자가 많으면 많을수록 정보가 왜곡되고 손실이 발생하게 된다
    • 이해관계자와 개발자도 도메인 지식을 갖춰야 한다
      • 도메인 전문가, 관계자, 개발자가 같은 지식을 공유하고 직접 소통할수록 원하는 제품이 만들어질 가능성이 높아진다
    • 도메인 전문가가 소프트웨어 전문가는 아니기 때문에 기존에 만들어진 소프트웨어를 기준으로 요구사항을 맞출 때가 있다
      • 요구사항을 이해할 때 왜 이런 기능을 요구하는지 또는 실제로 원하는게 무엇인지 생각하고 전문가와 대화를 통해 진짜로 원하는 것을 찾아야 한다

1.3 도메인 모델

  • 도메인 모델은 특정 도메인을 개념적으로 표한한 것 (개념 모델)
    • 도메인 모델을 사용하면 여러 관계자들이 동일한 모습으로 도메인을 이해하고 도메인 지식을 공유하는데 도움이 된다
    • 도메인을 이해하려면 도메인이 제공하는 기능과 주요 데이터 구성을 파악해야 한다 (객체 모델)
    • 도메인을 이해하는데 도움이 된다면 표현 방식이 무엇인지 중요하지 않다
    • 개념모델을 이용해서 바로 코드를 작성할 수 있는 것은 아니기에 구현 기술에 맞는 구현모델이 따로 필요하다
      • 개념 모델과 구현 모델은 서로 다른 것이지만 구현 모델이 개념 모델을 최대한 따르도록 할 수는 있다
  • 모델의 각 구성요소는 특정 도메인으로 한정할 때 비로소 의미가 완전해지기 때문에 각 하위 도메인마다 별도로 모델을 만들어야 한다

1.4 도메인 모델 패턴

  • 아키텍처 구성
    • UI or Presentation : 사용자의 요청을 처리하고 사용자에게 정보를 보여준다
    • Application : 사용자가 요청한 기능을 실행한다 도메인 계층을 조합해서 기능을 실행
    • Domain : 시스템이 제공할 도메인 규칙을 구현한다
    • Infrastructure : 데이터베이스나 메시징 시스템과 같은 외부 시스템과의 연동을 처리한다
  • 도메인 모델은 아키텍처 상의 도메인 계층을 객체지향 기법으로 구현하는 패턴
    • 도메인 계층은 도메인의 핵심 규칙을 구현
  • 핵심 규칙을 구현한 코드는 도메인 모델에만 위치
    • 규칙이 바뀌거나 규칙을 확장해야 할 때 다른 코드에 영향을 덜 주고 변경내역을 모델에 반영할 수 있게 된다
  • 개념 모델과 구현 모델
    • 개념 모델은 순수하게 문제를 분석한 결과물이다
      • 구현 가능한 모델로 전환하는 과정을 거치게 된다
    • 전반적인 개요를 알 수 있는 수준으로 개념 모델을 작성
      • 프로젝트 초기에는 개념 모델로 도메인에 대한 전체 윤곽을 이해하는데 집중
      • 구현하는 과정에서 구현 모델로 점진적으로 발전

1.5 도메인 모델 도출

  • 구현을 시작하려면 도메인에 대한 초기 모델이 필요
    • 도메인을 모델링할 때 기본이 되는 작업은 모델을 구성하는 핵심 구성요소, 규칙, 기능을 찾는 것
      • 이 과정은 요구사항에서 출발한다

1.6 엔티티와 밸류

1.6.1 엔티티

  • 엔티티의 가장 큰 특징은 식별자를 가진다는 것이다
    • 식별자는 엔티티 객체마다 고유해서 각 엔티티는 서로 다른 식별자를 갖는다
    • 두 엔티티 객체의 식별자가 같으면 두 엔티티는 같다고 판단할 수 있다

1.6.2 엔티티의 실별자 생성

  • 흔히 식별자는 다음 중 한가지 방식으로 생성
    1. 특정 규칙에 따라 생성
    2. UUID 나 Nano ID와 같은 고유 식별자 생성기 사용
    3. 값을 직접 입력
    4. 일련번호 사용 (시퀀스나 DB의 자동증가 컬럼 사용)

1.6.3 밸류 타입

  • 밸류 타입은 개념적으로 완전한 하나를 표현할때 사용한다
  • 밸류 객체의 데이터를 변경할 때는 변경한 데이터를 갖는 새로운 밸류 객체를 생성하는 방식을 선호한다
    • 데이터 변경 기능을 제공하지 않는 타입을 불변(immutable) 이라고 표현 (안전한 코드 작성)

1.6.4 엔티티 식별자와 밸류 타입

  • 식별자는 단순한 문자열이 아니라 도메인에서 특별한 의미를 지니는 경우가 많다
    • 식별자를 위한 밸류 타입을 사용해서 의미가 잘 드러나도록 할 수 있다

1.6.5 도메인 모델에 set 메서드 넣지 않기

  • 도메인 모델에 get/set 메서드를 무조건 추가하는 것은 좋지 않은 버릇이다
    • 특히 set 메서드는 도메인 핵심 개념이나 의도를 코드에서 사라지게 한다
  • 습관적으로 작성한 set 메서드의 필드값만 변경하고 끝남
    • 상태 변경과 관련된 도메인 지식이 코드에서 사라지게 된다
  • 도메인 객체를 생성할 때 온전하지 않은 상태가 될 수 있다
    • 도메인 객체가 불완전한 상태로 사용되는 것을 막으려면 생성시점에 필요한 것을 전달해야 한다 (생성자를 통해 필요한 데이터를 모두 받아야 한다)
  • 불변 밸류 타입을 사용하면 자연스럽게 밸류 타입에는 set 메서드를 구현하지 않는다

1.7 도메인 용어와 유비쿼터스 언어

  • 도메인에서 사용하는 용어를 코드에 반영하지 않으면 그 코드는 의미를 해석해야 하는 부담을 준다
    • 코드의 가독성을 높여서 코드를 분석하고 이해하는 시간을 줄여준다
      • 최대한 도메인 용어를 사용해서 도메인 규칙을 코드로 작성하게 되므로 버그도 줄어든다
  • 유비쿼터스 언어
    • 도메인과 과련된 공통의 언어를 만들고 모든 곳에서 같은 용어를 사용한다
      • 소통과정에서 발생하는 용어의 모호함을 줄일 수 있고, 개발자는 도메인과 코드 사이에서 불필요한 해석 과정을 줄일 수 있다
    • 새로 발견한 용어는 코드나 문서에도 반영해서 산출물에 최신 모델을 적용한다

chapter 2. 아키텍처 개요

2.1 네 개의 영역

  • 아키텍처를 설계할 때 출현하는 네 가지 영역
    • 표현, 응용, 도메인, 인프라스트럭처
  • 웹 애플리케이션의 표현 영역은 HTTP 요청을 응용 영역이 필요로 하는 형식으로 변환해서 응용 영역에 전달하고 응용 영역의 응답을 HTTP 응답으로 변환하여 전송
  • 응용 영역은 기능을 구현하기 위해 도메인 영역의 도메인 모델을 사용한다.
    • 응용 서비스는 로직을 직접 수행하기 보다는 도메인 모델에 로직 수행을 위임한다.
      • 도메인 모델은 도메인의 핵심 로직을 구현
  • 인프라스트럭처 영역은 구현 기술에 대한 것을 다룬다.
    • 인프라스트럭처 영역은 논리적인 개념을 표현하기 보다는 실제 구현을 다룬다.
  • 도메인 영역, 응용 영역, 표현 영역은 구현 기술을 사용한 코드를 직접 만들지 않는다.

2.2 계층 구조 아키텍처

  • 네 영역을 구성할 때 많이 사용하는 아키텍처가 계층구조이다.
    • 상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않는다.
  • 계층 구조를 엄격하게 적용한다면 상위 계층은 바로 아래의 계층에만 의존을 가져아 한다.
    • 구현의 편리함을 위해 계층 구조를 유연하게 적용하기도 한다.
  • 표현, 응용, 도메인 계층이 상세한 구현 기술을 다루는 인프라스트럭처 계층에 종속된다.
    • 인프라스트럭처에 의존하면 테스트 어려움기능 확장의 어려움 이라는 두 가지 문제가 발생 하는 것을 알게 되었다.

2.3 DIP

  • 고수준 모듈의 기능을 구현하려면 여러 하위 기능이 필요하다.
  • 고수준 모듈이 저수준 모듈을 사용하면 앞서 계층구조 아키텍처에서 언급한 두가지 문제가 발생
    • 구현 변경과 테스트가 어렵다.
      • 저수준 모듈이 고수준 모듈에 의존하도록 바꾼다 (추상화한 인터페이스)
  • 고수준 모듈이 저수준 모듈을 사용하려면 고수준 모듈이 저수준 모듈에 의존해야 하는데, 반대로 저수준 모듈이 고수준 모듈에 의존 (DPI, 의존 역전 원칙)
  • 실제 구현 대신 스텁이나 모의 객체와 같은 테스트 목적의 대역을 사용하여 거의 모든 상황을 테스트할 수 있다.

    2.3.1 DIP 주의사항

  • DIP 를 적용한 결과 구조만 보고 저수준 모듈에서 인터페이스를 추출하는 경우가 있다.
    • 이 구조에서 도메인 영역은 구현 기술을 다루는 인프라스트럭처 영역에 의존
      • DIP를 적용할 때 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출한다.

        2.3.2 DIP 와 아키텍처

  • 인프라 스트럭처 영역은 구현 기술을 다루는 저수준 모듈이고 응용 영역과 도메인 영역은 고수준 모듈이다.
    • DIP 를 적용하면 계층형 구조와 달리 인프라스트럭처 영역이 응용 영역과 도메인 영역에 의존(상속) 하는 구조가 된다.
      • 도메인과 응용 영역에 대한 영향을 주지 않거나 최소화하면서 구현 기술을 변경하는 것이 가능
  • 사용하는 구현 기술에 따라 구현 기술에 의존적인 코드를 도메인에 일부 포함하는게 효과적일때도 있다. 또는 추상화 대상이 잘 떠오르지 않을 때도 있다
    • DIP 의 이점을 얻는 수준에서 적용 범위를 검토

2.4 도메인 영역의 주요 구성요소

  • 도메인 영역의 주요 구성요소
    • 엔티티 : 고유의 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다.
      • 도메인의 고유한 개념을 표현, 도메인 모델의 데이터와 관련된 기능을 함께 제공
    • 밸류 : 고유의 식별자를 갖지 않는 객체로 주로 개념적으로 하나의 값을 표현
      • 엔티티의 속성으로 사용, 다른 밸류 타입의 속성으로도 사용
    • 애그리거트 : 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것
    • 리포지터리 : 도메인 모델의 영속성을 처리
    • 도메인 서비스 : 특정 엔티티에 속하지 않은 도메인 로직을 제공
      • 도메인 로직이 여러 엔티티와 밸류를 필요로 하면 도메인 서비스에서 로직을 구현

        2.4.1 엔티티와 밸류

  • DB 테이블 엔티티와 도메인 모델 엔티티의 차이
    • 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 제공하는 객체
      • 주문 엔티티는 데이터 뿐만 아니라 배송지 변경을 위한 기능을 제공
      • 도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화해서 데이터가 임의로 변경되는 것을 막는다.
    • 도메인 모델의 엔티티는 두 개 이상의 데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현
      • Orderer 는 주문자 이름과 이메일 데이터를 포함

        2.4.2 애그리거트

  • 개별 객체뿐만 아니라 상위 수준에서 모델을 볼 수 있어야 전체 모델의 관계와 개별 모델을 이해하는데 도움이 된다 (애그리거트)
    • 애그리거트는 관련 객체를 하나로 묶은 군집이다.
      • 하위 개념을 표현한 모델을 하나로 묶어서 상위 개념으로 표한할 수 있다
  • 애그리거트를 사용하면 개별 객체가 아닌 관련 개체를 묶어서 객체 군집 단위로 모델을 바라볼 수 있게 된다.
    • 애그리거트 간의 관계로 도메인 모델을 이해하고 구현하게 되며, 큰 틀에서 도메인 모델을 관리할 수 있다.
  • 애그리거트는 군집에 속한 객체를 관리하는 루트 엔티티를 갖는다.
    • 루트 엔티티는 애그리거트에 속해 있는 엔티티와 밸류 객체를 이용해서 애그리거트가 구현해야활 기능을 제공
    • 애그리거트를 사용하는 코드는 애그리거트 루트가 제공하는 기능을 실행하고 애그리거트 루트를 통해서 간접적으로 애그리거트 내의 다른 엔티티나 밸류 객체에 접근
      • 애그리거트의 내부 구현을 숨겨서 애그리거트 단위로 구현을 캡슐화

        2.4.3 리포지터리

  • 도메인 객체를 지속적으로 사용하려면 물리적인 저장소에 도메인 객체를 보관해야 한다.
    • 이를 위한 도메인 모델이 리포지터리
      • 구현을 위한 도메인 모델
  • 리포지터리는 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다.
    • OrderRepository 의 메서드를 보면 대상을 찾고 저장하는 단위가 애그리거트 루트인 Order 인 것을 알 수 있다.
      • Order 는 애그리거트에 속한 모든 객체를 포함하고 있으므로 결과적으로 애그리거트 단위로 저장하고 조회한다.
  • 도메인 모델 관점에서 OrderRepository 는 도메인 객체를 영속화하는 데 필요한 기능을 추상화한 것으로 구수준 모델에 속한다.
    • 기반 기술을 이용해서 OrderRepository 를 구현한 클래스는 저수준 모듈로 인프라스트럭처 영역에 속한다.

2.5 요청 처리 흐름

요청 처리 흐름

2.6 인프라스트럭처 개요

  • 인프라스트럭처는 표현 영역, 응용 영역, 도메인 영역을 지원
    • 도메인 객체의 영속성 처리, 트랜잭션, SMTP 클라이언트, Rest 클라이언트 등 다른 영역에서 필요로 하는 프레임워크, 구현 기술, 보조 기능을 지원
  • 도메인 영역과 응용 영역에서 인프라스트럭처의 기능을 직접 사용하는 것 보다 이 두 영역에서 정의한 인터페이스를 인프라스트럭처 영역에서 구현하는 것이 더 유연하고 테스트하기 쉽게 만들어 준다.
    • DIP 의 장점을 해치지 않는 범위에서 응용 영역과 도메인 영역에서 구현 기술에 대한 의존을 가져가는 것이 나쁘지 않다고 생각
      • 응용 영역과 도메인 영역이 인프라스트럭처에 대한 의존을 완전히 갖지 않도록 시도하는 것은 자칫 구현을 더 복잡하고 어렵게 만들 수 있다. (@Transactional)

2.7 모듈 구성

  • 영역별로 모듈이 위치할 패키지를 구성할 수 있다.
    • 도메인이 크면 하위 도메인으로 나누고 각 하위 도메인마다 별도 패키지를 구성
    • 도메인 모듈은 도메인에 속한 애그리거트를 기준으로 다시 패키지를 구성
      • 도메인이 복잡하면 도메인 모델과 도메인 서비스를 별도 패키지에 위치 시킬 수 도 있다.
  • 모듈 구조를 얼마나 세분화해야 하는지에 대해 정해진 규칙은 없다.
    • 한 패키지에 10 ~ 15 미만으로 타입 개수를 유지하길 권장

마무리

그럼 이만. 🥕👋🏼🖐🏼

참고자료🤣

도메인 주도 개발 시작하기