Search

DDD : 도메인 주도 개발 시작하기

 아키텍처 개요

2-1 네 개의 영역

표현 영역, 응용 영역, 도메인 영역, 인프라스트럭처 영역은 아키텍처를 설계할 때 출현하는 전형적인 네가지 영역이다.

표현 영역(Presentation Layer)

표현 영역은 사용자의 요청을 해석해서 응용 서비스에 전달하고 응용 서비스의 실행 결과를 사용자가 이해할 수 있는 형식으로 변환하여 응답한다.
웹 어플리케이션에서 표현 영역은 HTTP 요청을 응용 영역이 필요로 하는 형식으로 변환해서 응용 영역에 전달하고 응용 영역의 응답을 HTTP 응답으로 변환하여 전송한다.

응용 영역(Application Layer)

응용 영역은 기능을 구현하기 위해 도메인 영역의 도메인 모델을 사용한다.
응용 서비스는 로직을 직접 수행하기 보다는 도메인 모델에 로직 수행을 위임한다.

도메인 영역(Domain Layer)

도메인 영역은 도메인 모델을 구현한다.

인프라스트럭처 영역(Infrastructure Layer)

인프라스트럭처 영역은 구현 기술에 대한 것을 다룬다.
이 영역은 RDBMS 연동을 처리하고, 메시징 큐에 메시지를 전송하고나 수신하는 기능을 구현하고, 몽고DB나 레디스 등 데이터 연동을 청리한다.
또한 HTTP 클라이언트를 이용해서 REST API를 호출하는 것도 처리한다.
즉, 외부 시스템과의 연동(?) 혹은 인프라(?)를 담당하고 있다고 보면 될 듯하다.

2-2 계층 구조 아키텍처

표현 → 응용 → 도메인 → 인프라스트럭처
계층 구조는 위 순서 처럼 상위 계층에서 하위 계층으로 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않는다.
하지만 응용 계층에서 인프라스트럭처 계층에 외부 시스템과의 연동을 위해 직접 의존 하기도 한다.
표현, 응용, 도메인 계층은 상세히 기술을 구현한 인프라스트럭처에 결국 종속된다.
이 경우 두가지 문제점이 있다.
독립적인 테스트가 어렵다.
→ 응용 계층을 테스트 하려면 인프라스트럭처 계층이 완벽하게 동작해야만 한다.
구현 방식을 변경하기 어렵다.
→ 인프라스트럭처의 구현을 변경하려면 해당 소스를 직접 변경해야 한다.(OCP 위반)
위 문제점에 해답은 바로 DIP 이다.

2-3 DIP(Dependency Inversion Principle, 의존 역전 원칙)

예를들어 OrderService가 OrderRepository에 의존해야 테스트를 진행하거나 구현체를 교체하기 편리한데
MyBatisOrderRepository나 JpaOrderRepository에 의존하면 테스트를 하거나 변경하기 쉽지 않을것이다
근데 여기서 DIP를 단순히 인터페이스와 구현 클래스의 분리로만 생각하면 그것은 잘못된 것이다.
책의 예시를 빌려보면
도메인 영역의 CalculateDiscountService 가 인프라 영역의 RuleEngine을 의존하고 있다.
이게 왜 잘못된 것이냐면 RuleEngine 인터페이스는 고수준 모듈인 도메인 관점이 아니라 룰 엔진이라는 저수준 모듈 관점에서 도출되었기 때문이다.
CalculateDiscountService는 할인 금액을 계산하는 기능이 필요한 것이지 RuleEngine으로 구현했는지 직접 구현했는지 중요하지 않다.
즉, DIP를 적용할 때 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출해야만 한다.
그리고 하위 기능을 추상화한 인터페이스는 고수준 모듈에 위치시킨다.

2-4 도메인 영역의 주요 구성요소

도메인 영역의 주요 구성요소는 아래와 같다
요소
설명
엔티티 (Entity)
고유의 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다. 도메인의 고유한 개념을 표현한다. 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공한다.
밸류 (Value)
고유하진 않지만 개념적으로 하나인 값을 표현할 때 사용된다. 엔티티의 속성을 ㅗ사용할 뿐만 아니라 다른 밸류 타입의 속성으로도 사용할 수 있다.
애그리거트 (Aggregate)
연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다.
리포지터리 (Repository)
도메인 모델의 영속성을 처리한다.
도메인 서비스 (Domain service)
특정 엔티티에 속하지 않은 도메인 로직을 제공한다. 도메인 로직이 여러 엔티티와 밸류를 필요로 하면 도메인 서비스에서 로직을 구현한다.

2-4-1 엔티티와 밸류

DB 테이블의 엔티티와 도메인 모델의 엔티티의 구분
도메인 모델의 엔티티는 단순히 데이터를 담고 있는 데이터 구조라기 보다는 데이터와 함께 기능을 제공하는 객체이다. DB 테이블의 엔티티는 DB와 규격이 동일하며 단순히 데이터만 담고 있는 객체 (?)
도메인 모델의 엔티티는 두 개 이상의 데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현할 수 있다.

2-4-2 애그리거트

애그리거트는 관련 객체를 하나로 묶은 군집이다.
즉, 하위 개념을 표현한 무델을 하나로 묶어서 상위 개념으로 표현하는 것이다.
예를 들면 ‘주문’이라는 애그리거트는 ‘주문’, ‘배송지 정보’, ‘주문자’, ‘주문 목록’, ‘총 결제 금액’ 등의 하위 모델로 구성된다.
애그리거트는 군집에 속한 객체를 관리하는 루트 엔티티를 갖는다.
루트 엔티티는 애그리거트에 속해 있는 엔티티와 밸류 객체를 이용해서 애그리거트가 구현해야 할 기능을 제공한다. 애그리거트를 사용하는 코드는 애그리거트 루트가 제공하는 기능을 실행하고 애그리거트 루트를 통해서 간접적으로 애그리거트 내의 다른 엔티티나 밸류 객체에 접근한다.
이것은 애그리거트의 내부 구현을 숨겨서 애그리거트 단위로 구현을 캡슐화할 수 있도록 돕는다.

2-4-3 리포지터리

도메인 객체를 지속적으로 사용하려면 물리적인 저장소에 도메인 객체를 보관해야 한다. 이를 위한 도메인 모델이 리포지터리이다.
리포지터리는 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다.

 애그리거트

3-1 애그리거트

앞서 설명한 것 처럼 애그리거트는 관련된 객체를 하나의 군으로 묶어 준다.
수많은 객체를 애그리거트로 묶어서 바라보면 상위 수준에서 도메인 모델 간의 관계를 파악할 수 있다.
애그리거트는 모델을 이해하는데 도움을 줄 뿐만 아니라 일관성을 관리하는 기준도 된다.
한 애그리거트는 다른 애그리거트에 속하지 않는다. 애그리거트는 독립된 객체 군이며 각 애그리거트는 자기 자신을 관리할 뿐 다른 애그리거트를 관리하지 않는다.
흔히 ‘A가 B를 갖는다.’로 생각하고 A와 B를 애그리거트로 묶어서 설계하기 쉽다.
위 설계가 맞을때도 있지만 아닐수도 있기때문에 주의해야 한다.
예를 들면 상품과 리뷰다. 상품은 리뷰를 갖고 있기때문에 하나의 애그리거트로 묶을수도 있지만 실은 그렇게되면 안된다.
상품과 리뷰는 함께 생성되지 않고, 함꼐 변경되지도 않는다. 게다가 상품을 변경하는 주체가 상품 담당자라면 리뷰를 변경하는 주체는 고객이다.
저자의 경험을 빌려서 얘기를 해보자면 생각보다 다수의 애그리거트가 한 개의 엔티티 객체만 갖는 경우가 많았으며 두 개 이상의 엔티티로 구성되는 애그리거트는 드물었다고 한다.

3-2 애그리거트 루트