네 개의 영역
- 표현 : 사용자의 요청을 받아 응용 영역에 전달하고 응용 영역의 처리 결과를 사용자에게 보여주는 역할
- 응용 : 사용자에게 제공할 기능 구현. 로직을 직접 수행하기 보다는 도메인 모델에 로직 수행 책임 위임
- 도메인 : 도메인의 핵심 로직 구현
- 인프라 : 논리적 개념 표현 보다는 실제 구현 다룸 (RDBMS 연동, 메세지 큐 송수신, 데이터 연동..)
계층 구조 아키텍처
아키텍처는 기본적으로 계층 구조이다.
계층 구조는 특성상 상위 계층에서 하위 계층으로의 의존만 존재하고, 하위 계층은 상위 계층에 의존하지 않는다.
하지만 구현의 편리함을 위해 때로는 계층 구조를 유연하게 적용하기도 한다.
도메인과 응용 계층이 현재 인프라 계층에 의존하고 있다.
하지만 의존한다라는 것은, 달리 표현해 말하자면 종속된다는 뜻이다.
이는 두 가지 문제점을 가지고 있다.
1. 해당 계층만 단독으로 테스트하기 어렵다. (종속된 계층들도 완벽히 동작을 해야함)
2. 구현 방식의 변경이 어렵다. (예를 들어 종속된 인프라의 세션 이름이 변경되거나 인프라의 구현 기술이 변경되면 의존하고 있는 응용 계층도 코드 변경이 일어나야함)
DIP
고수준 모듈이 저수준 모듈을 사용하면 지금 언급한 두 가지 문제인 구현 변경 , 테스트의 어려움이 발생한다.
DIP는 해당 문제를 해결하기 위해 저수준 모듈이 고수준 모듈에 의존하도록 변경한다.
고수준 모듈을 구현하려면 저수준 모듈을 사용해야 하는데 반대로 저수준 모듈이 고수준 모듈에 의존하도록 하려면 어떻게 해야할까?
답은 인터페이스 추상화에 있다.
서비스 입장에서 봤을 때, 예를 들어 룰 엔진이 A로 구현하였는지 B로 구현하였는지는 중요하지 않다.
그저 서비스에서 룰 엔진이 필요할 뿐이다.
DIP를 적용하면 아까 의존의 문제였던 구현 교체의 어려움과 테스트의 어려움을 해결할 수 있다.
구현 교체의 어려움은 이제 저수준 구현 객체만을 변경하면 해결된다.
테스트 역시 Mock을 활용함으로써 테스트를 수행하는데 필요한 기능만 담당하게 하여 대체가 가능하다.
DIP를 적용하면서 주의할 점이 있다.
DIP를 잘못 이해하면 단순히 인터페이스와 구현 클래스를 분리하는 정도로 이해할 수 있다.
DIP의 핵심은 고수준 모듈이 저수준 모듈에 의존하지 않도록 하기 위함이다.
저수준 모듈 관점에서 인터페이스를 추출하면 여전히 고수준 모듈이 저수준 모듈에 의존하는 문제가 생긴다.
DIP를 적용할 때 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출한다.
도메인 영역의 주요 구성요소
엔티티 (Entity) |
고유한 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다. 도메인의 고유한 개념을 표현한다. 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공한다. |
밸류 (Value) |
고유의 식별자를 갖지 않는 객체로 주로 개념적으로 하나의 값을 표현할 때 사용된다. 엔티티의 속성으로 사용할 뿐만 아니라 다른 밸류 타입의 속성으로도 사용할 수 있다. |
에그리거트 (Aggregate) |
연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다. |
레포지토리 (Repository) |
도메인 모델의 영속성을 처리한다. |
도메인 서비스 (Domain Service) |
특정 엔티티에 속하지 않은 도메인 로직을 제공한다. |
엔티티와 밸류
필자는 개발 초년 시절 도메인 모델을 만들 때 DB 테이블의 엔티티와 도메인 모델의 엔티티를 구분하지 못해
동일하게 만들곤 했다. 이 두 모델의 가장 큰 차이점은,
도메인 모델의 엔티티는 데이터와 함께 '도메인 기능'을 함께 제공한다는 점이다.
public class Order {
// 주문 도메인 모델의 데이터
private OrderNo number;
private ShippingInfo shippingInfo;
// 도메인 기능
public void changeNumber(OrderNo number) {
....
}
}
도메인 모델의 엔티티는 단순히 데이터를 담고 있는 데이터 구조라기보다는, 데이터와 함께 기능을 제공하는 객체이다.
또 다른 차이점은, 도메인 모델의 엔티티는 두 개 이상의 데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현할 수 있다는 것이다.
지금 OrderNo, ShippingInfo 처럼 밸류 타입을 만들어서 활용할 수 있다.
RDBMS 같은 관계형 DB에서는 밸류 타입을 제대로 표현하기 힘들기 때문에, 별도의 테이블로 분리해서 관리하거나 개별 데이터를 저장해야한다.
애그리거트
에그리거트는 관련 객체를 하나로 묶은 군집이다.
(ex. '주문'이라는 도메인 개념은 '주문', '배송지 정보', '주문자', '주문 목록', '결제 금액' 등의 하위 모델로 구성된다.)
에그리거트를 사용하면 개별 객체가 아닌 관련 객체를 묶어서 객체 군집 단위로 모델을 바라볼 수 있다.
에그리거트는 군집에 속한 객체를 관리하는 루트 엔티티를 갖는다.
루트 엔티티는 에그리거트에 속해있는 엔티티와 밸류 객체를 활용해 에그리거트가 구현해야할 기능을 제공한다.
리포지토리
도메인 객체를 지속적으로 사용하려면 RDBMS, NoSQL, 로컬 파일 등의 물리적인 저장소에 도메인 객체를 보관해야 한다.
이를 위한 도메인 모델이 리포지토리(Repository)다.
엔티티와 밸류가 요구사항에서 도출되는 도메인 모델이라면, 리포지토리는 구현을 위한 도메인 모델이다.
리포지토리는 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다.
요청 처리 흐름
인프라스트럭처 개요
DIP에서 언급한 것처럼 도메인 영역과 응용 영역에서 인프라의 기능을 직접 사용하는 것보다는
인터페이스를 인프라 영역에서 구현하는 것이 시스템이 더 유연하고 테스트하기 쉽게 만들어준다.
하지만 무조건 인프라에 대한 의존을 없앨 필요는 없다.
구현의 편리함(ex. @Transactional, @Entity..)은 DIP가 주는 다른 장점(변경의 유연함, 테스트 용이)만큼 중요하기 때문에
DIP의 장점을 해치지 않는 범위에서 응용 영역과 도메인 영역에서 구현 기술에 대한 의존을 가져가는 것은 나쁘지 않다.
'책 정리 > 도메인 주도 개발 시작하기' 카테고리의 다른 글
Chapter 6. 응용 서비스와 표현 영역 (1) | 2023.12.12 |
---|---|
Chapter 5. 스프링 데이터 JPA를 이용한 조회 기능 (0) | 2023.12.08 |
Chapter 4. 리포지터리와 모델 구현 (0) | 2023.12.05 |
Chapter 3. 애그리거트 (0) | 2023.12.01 |
Chapter 1. 도메인 모델 시작하기 (0) | 2023.11.23 |