Application Layer ? Domain Layer ?
앞에서 선택한 아키텍처를 사용하게 되면 응용 영역
에서 사용자에게 제공해야할 기능을 구현하게 된다.
주문이란 하위 도메인을 예로 들면 주문 등록, 주문 취소, 상품 상세 조회와 같은 기능을 제공한다.
응용 영역(Application)은 기능을 구현하기 위해 도메인 영역에 존재하는 도메인 모델
을 사용한다.
class CancelOrderService {
@Transactional
fun cancelOrder(orderId: String) {
val order = findOrderById(orderId)
if (order == null) throw new OrderNotFoundException(orderId)
order.cancel() // 위임
}
}
위와 같이 핵심 로직을 응용 계층(CancelOrderService)에서 구현하는 것이 아니라 도메인(Order)모델에 위임한다.
계층 구조 아키텍처
계층 구조는 그 특성상 상위 계층에서 하위 계층으로의 의존만 존재하고 반대는 의존하지 않는다.
하지만 편리함을 위해 계층 구조를 유연하게 적용하기도 한다.
응용 계층은 바로 아래 계층인 도메인 계층에 의존하지만 외부 시스템과 연동을 위해 더 아래 계층인 인프라스트럭쳐 계층에 의존하기도 함
Domain Layer 주요 구성요소
도메인 영역은 도메인의 핵심 모델을 구현한다.
요소 | 설명 |
---|---|
엔티티(Entity) | 고유의 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다. Order, Member, Product 같이 고유한 개념을 표현 |
밸류(Value) | 고유의 식별자를 갖지 않으며, 개념적으로 하나인 값을 표현할 때 사용 엔티티 속성, 다른 밸류 타입의 속성으로도 사용 |
애그리거트(Aggregate) | 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것 Order 엔티티, Orderer, OrderLine 밸류 객체를 '주문'애그리거트로 묶는다 |
리포지터리(Repository) | 도메인 모델의 영속성을 처리 DBMS 테이블에서 엔티티 객체를 로딩하거나 저장하는 기능 제공 |
도메인 서비스(Domain Service) | 특정 엔티티에 속하지 않은 도메인 로직을 제공 도메인 로직이 여러 엔티티와 밸류를 필요로 하면 도메인 서비스에서 구현 |
엔티티(Entity)와 벨류(Value)
- DB 모델 엔티티 != 도메인 모델 엔티티
- 차이점1. 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 함께 제공한다.(도메인 관점에서 기능을 구현하고 캡슐화해서 데이터가 임의로 변경되는 것을 막는다.)
- 차이점2. 도메인 모델의 엔티티는 두 개 이상의 데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현할 수 있음(RDBMS는 밸류 타입을 표현하기 힘듬)
- Order테이블에 Orderer 정보를 함께 넣어서 만들 것인가, Order, Orderer 테이블을 분리해서 만들 것인가
- @Entity를 도메인 모델 엔티티로 사용할 수 있을까?
- 밸류는 불변으로 구현할 것을 권장하며, 데이터를 변경할 때는 객체 자체를 완전히 교체해야 한다. (copy())
애그리거트 개념
관련 객체를 하나로 묶은 군집
- ex. 주문(Order)
- 주문, 배송지 정보, 주문자, 주문 목록, 총 결제 금액의 하위 모델들을 하나로 묶는 상위 개념(주문)으로 표현
왜 애그리거트란 개념이 필요할까?
- 도메인 모델이 복잡해지면 개발자가 전체 구조가 아닌 하나의 엔티티와 밸류에만 집중하는 상황이 발생한다.
- 이 때 상위 수준에서 모델을 관리하지 않고 개별 요소에만 초점을 맞추다 보면, 큰 수준에서 모델을 이해하지 못해 관리할 수 없는 상황에 빠질 수 있다.
애그리거트는 루트 엔티티
를 갖는다. (나중에 자세히 다루겠지만..)루트 엔티티?
- 애그리거트에 속해 있는 엔티티, 밸류 객체를 이용해서 애그리거트가 구현해야 할 기능을 제공한다.
- 애그리거트 루트를 통해 애그리거트 내의 엔티티와 밸류 객체에 접근하며, 내부 구현을 숨겨 애그리거트 단위로 캡슐화 한다.
- ex. 주문의 배송지를 변경하기 위해선, 꼭 루트 엔티티인 Order를 통해야 한다. Order에서는 기능을 제공하고
주문 배송
관련 엔티티에게 로직을 위임한다.
리포지토리
- 도메인 객체를 물리 적인 저장소(RDBMS, NoSql ..)에 보관해야야 하는데, 이를 위한 도메인 모델이 Repository이다.
- 리포지토리는 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다.
- Repository는 도메인 모델 관점에서 도메인 객체를 영속화하는데 필요한 기능을 추상화한 고수준 모듈에 속한다.
- 실제 구현은 JpaRepository같은 구현 서비스가 되시겠다.
인프라스트럭처 개요
인프라스트럭쳐(Infrastructure)는 표현, 응용, 도메인 영역을 지원한다.
DIP에 언급한 것처럼 도메인 영역와 응용 영역에서 인프라스트럭처의 기능을 직접 사용하는 것 보다 두 영역에 정의한 인터페이스를 인프라스트럭쳐 영역에서 구현하는 것이 시스템을 유연하고 테스트하기 쉽게 만들어준다.
but, 무조건 인프라스트럭처에 대한 의존성을 없앨 필요는 없다.
@Entity
data class Order(
...
)
- 스프링을 사용할 경우 응용 서비스 트랜잭션 처리를 위해 @Transactional을 사용하는 것이 편리
- 영속성 처리를 위해 JPA를 사용할 경우 @Entity나 @Table같은 JPA 전용 애노테이션을 도메인 모델 클래스에 사용하는 것이 편리 (XML 매핑설정보다)
구현의 편리함은 DIP가 주는 장점만큼 중요하기 때문에 DIP의 장점을 해치지 않는 범위에서 응용 영역과 도메인 영역에서 구현 기술에 대한 의존을 가져가는 것은 나쁘지 않음
만약 의존을 완전히 갖지 않도록 시도하면 자칫 구현이 복잡하고 어려워질 수 있다.
ex. @Transactional 코드에서 스프링 의존을 없애려면 복잡한 스프링 설정이 필요하다.
++)
- DIP 를 위해, OrderRepositor(인터페이스), JpaOrderRepository(실제 구현체)로 나눠서 구현하는게 이득이 있을지?
- 스프링 데이터 JPA에서는 Repository 인터페이스만 정의하면 구현 객체는 스프링 데이터 JPA가 알아서 만들어준다. 따라서 실질적으로 리포티터리 인터페이스를 구현한 클래스를 직접 작성할 일은 거의 없다.
- 지정한 규칙에 맞게 Repository 인터페이스를 정의하면 구현한 객체를 알아서 만들어서 스프링 빈(Spring Bean)으로 등록해준다.
- 도메인 객체 단위로 Repository를 구현하는데, DB 모델 엔티티과 도메인 엔티티 모델을 동일시하여 설계하는 편이 좋지 않을까? 만약 레거시라면 어떻게 구현해야 할까?
- 테이블이 분리되어있어도, 하나의 엔티티로 설계가 가능하면 엔티티를 하나로 만든다. 그렇다면 실제 저장할때는 어떻게 해야할까?..
- 모듈 구성은 ui(controller), application, domain, infrastructure 어떨지
출처) 도메인 주도 개발 시작하기 - 최범균
'프로그래밍 노트 > 도메인 주도 설계(DDD)' 카테고리의 다른 글
[도메인 주도 설계] 도메인 모델과 바운디드 컨텍스트 (0) | 2023.08.21 |
---|---|
[도메인 주도 설계] 도메인 서비스(Domain Service) (0) | 2023.08.16 |
[도메인 주도 설계] 표현 영역과 응용 영역(Presentation Layer, Application Layer) (2) | 2023.08.14 |
[도메인 주도 설계] 애그리거트에 대해 (3) | 2023.08.07 |
[도메인 주도 설계] 도메인이란? (도메인과 친해져보자) (0) | 2023.07.23 |