이 글은 “DDD START! - 도메인 주도 설계 구현과 핵심 개념 익히히 (최범균 저)” 책 내용을 정리한 글입니다.

DDD Start - 2. 아키텍처 개요

네 개의 영역

  • 표현 or UI (Presentation) : 사용자의 요청을 받아 응용 영역에 전달하고 응용 영역의 처리 결과를 다시 사용자에게 보여주는 역할
    • 사용자의 요청을 해석해서 응용 서비스에 전달하고 응용 서비스의 실행 결과를 사용자가 이해할 수 있는 형식으로 변환해서 응답한다.
  • 응용 (Application) : 시스템이 사용자에게 제공해야 할 기능을 구현한다.
    • 기능 구현을 위해 도메인 모델을 사용한다.
    • 로직을 직접 수행하기 보다는 도메인 모델에 로직 수행을 위임
  • 도메인 (Domain) : 도메인 모델을 구현한다.
    • 도메인 핵심 로직을 개발
  • 인프라스트럭처 (Infrastructure) : 구현 기술에 대한 것을 다룬다.
    • DB, Kafka, Redis 등
    • 도메인 영역, 응용 영역, 표현 영역은 구현 기술을 사용한 코드를 직접 만들지 않는다. => 인프라스트럭처 영역에서 제공하는 기능을 사용해서 필요한 기능을 개발

계층 구조 아키텍처

  • 계층 구조는 상위 계층에서 하위 계층으로의 의존만 존재한다.
    • 계층 구조를 엄격하게 적용하면 상위 계층은 바로 아래의 계층에만 의존을 가져야하지만 편리함을 위해 계층 구조를 유연하게 적용한다.

architecture

DIP

  • 표현, 응용, 도메인 계층이 상세한 구현 기술을 다루는 인프라스트럭처 계층에 종속된다.
  • 인프라스트럭처에 의존하면 아래와 같은 단점이 있다.
    • 테스트의 어려움
    • 기능 확장의 어려움
  • DIP는 저수준 모듈이 고수준 모듈에 의존하도록 바꾼다. => 고수준 모듈에서 추상화한 인터페이스를 정의
  • DIP를 적용하면 고수준 모듈이 인프라스트럭처 영역에 의존할 때 발생하는 문제를 해소할 수 있다.

DIP 주의사항

  • 저수준 모듈에서 인터페이스를 추출하면 안된다.
  • 인터페이스는 고수준 모듈인 도메인 관점에서 도출되어야 한다.

DIP와 아키텍처

  • DIP를 적용하면 인프라스트럭처 영역이 응용 영역과 도메인 영역에 의존하는 구조가 된다.
  • 도메인과 응용 영역에 영향을 주지 않거나 최소화하면서 구현 기술을 변경하는게 가능하다.

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

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

엔티티와 밸류

  • 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 제공한다.
  • 도메인 모델의 엔티티는 두 개 이상의 데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현할 수 있다.
  • 밸류는 불변으로 구현하는 것을 권장한다.

애그리거트

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

리포지토리

  • 리포지토리는 구현을 위한 도메인 모델이다.
  • 리포지토리는 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다.
  • 도메인 모델을 사용해야 하는 코드는 리포지토리를 통해서 도메인 객체를 구한 뒤에 도메인 객체의 기능을 실행해야 한다.
  • 리포지토리는 도메인 객체를 영속화 하는데 필요한 기능을 추상화 한 것으로 고수준 모듈에 속한다.
  • 리포지토리 인터페이스는 도메인 영역에 속하며, 실제 구현 클래스는 인프라스트럭처 영역에 속한다.
  • 응용 서비스는 리포지터리와 밀접한 연관이 있다.
    • 응용 서비스는 필요한 도메인 객체를 구하거나 저장할 때 리포지토리를 사용한다.
    • 응용 서비스는 트랜잭션을 관리하는데 트랜잭션 처리는 리포지토리 구현 기술에 영향을 받는다.

요청 처리 흐름

  • 표현 영역 => 응용 서비스 => 도메인 오브젝트 => Repository
    • 표현 영역 : 사용자가 전송한 데이터를 이용해서 응용 서비스에 기능 실행을 위임. 사용자가 전송한 데이터를 응용 서비스가 요구하는 형식으로 변환해서 전달
    • 응용 서비스 : 도메인 모델을 이용해서 기능을 구현
      • 트랜잭션을 관리한다.

인프라스트럭처 개요

  • 인프라스트럭처는 표현 영역, 응용 영역, 도메인 영역을 지원한다.
  • 도메인 영역과 응용 영역에서 인프라스트럭처의 기능을 직접 사용하는 것보다 이 두 영역에 정의한 인터페이스를 인프라스트럭처 영역에서 구현하는 것이 시스템을 더 유연하고 테스트하기 쉽게 만들어준다.
  • 무조건 인프라스트럭처에 대한 의존을 없애는게 좋은 것은 아니다.
    • 트랜잭션 처리를 위해 @Transactional 사용
    • 영속성 처리를 위해 JPA 애노테이션을 도메인 모델 클래스에 사용
  • DIP의 장점을 해치지 않는 범위에서 응용 영역과 도메인 영역에서 구현 기술에 대한 의존을 가져가는 것은 현명하다.
    • JPA를 사용할 경우 @Entity나 @Table과 같은 JPA 전용 애노테이션을 도메인 모델 클래스에 사용

모듈 구성

  • 아키텍처의 각 영역은 별도 패키지에 위치한다.
  • 도메인이 크면 하위 도메인으로 나누고 각 하위 도메인마다 별도 패키지를 구성한다.
  • 도메인 모듈은 도메인이 속한 애그리거트를 기준으로 다시 패키지를 구성한다.
  • 애그리거트와 모델과 리포지터리는 같은 패키지에 위치시킨다.
# 영역별로 별도 패키지로 구성한 모듈
com.myshop
  ㄴ ui
  ㄴ application
  ㄴ domain
  ㄴ infrastructure
  
# 도메인이 크면 하위 도메인 별로 모듈을 나눈다.
com.myshop
  ㄴ catalog
  	ㄴ ui 
  	ㄴ application
  	ㄴ domain
  	ㄴ infrastructure
  ㄴ order
  ...
  ㄴ member
  ...
  
# 하위 도메인을 하위 패키지로 구성한 모듈
com.myshop
  ㄴ catalog
  	ㄴ ui 
  	ㄴ application
  	ㄴ domain
  	  ㄴ product
  	  ㄴ category
  	ㄴ infrastructure