Blog

도메인 주도 개발 시작하기(2)

도메인 주도 개발 시작하기(2)

2-1. 네 개의 영역

표현 영역: 사용자의 요청을 해석해서 응용 서비스에 전달하한다
응용 영역: 사용자의 요청을 전달받는 응용 영역은 시스템이 사용자에게 제공해야 할 기능을 구현한다
응용 영역은 도메인 영역의 도메인 모델을 사용한다
응용 서비스는 로직을 직접 수행하기보다는 도메인 모델에 로직 수행을 위임한다
도메인 영역: 도메인 모델을 구현한다
도메인 모델은 도메인의 핵심 로직을 구현한다
인프라스트럭처 영역: 구현 기술에 대한 것을 다룬다
RDBMS 연동, Message Queue 처리, Redis, MongoDB 데이터 연동 등
논리적인 개념을 표현하기보다는 실제 구현을 다룬다

2-2. 계층 구조 아키텍처

계층 구조
flowchart TD
	표현 --> 응용 --> 도메인 --> 인프라스트럭처
Mermaid
복사
도메인의 복잡도에 따라 응용과 도메인을 분리하기도 하고, 한 계층으로 합치기도 한다
계층 구조는 그 특성상 상위 계층에서 하위 계층으로의 의존만 존재하고, 하위 계층은 상위 계층에 의존하지 않는다
계층 구조를 엄격하게 적용한다면 상위 계층은 바로 아래의 계층에만 의존을 가져야 하지만, 구현의 편리함을 위해 계층 구조를 유연하게 적용한다
표현, 응용, 도메인 계층은 인프라스트럭처 게층에 종속된다
인프라스트럭처에 의존하면 테스트 어려움기능 확장의 어려움 이라는 두 가지 문제가 발생하게 된다

2-3. DIP

DIP (Dependency Inversion Principle) → 의존 역전 원칙
저수준 모듈이 고수준 모듈에 의존한다
DIP 를 적용하면 앞의 다른 영역이 인프라스트럭처 영역에 의존할 때 발생했던 두 가지 문제를 해소할 수 있다

2-3-1. DIP 주의사항

DIP 의 핵심은 고수준 모듈이 저수준 모듈에 의존하지 않도록 하기 위함이다
DIP 를 적용할 때 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출한다

2-3-2. DIP 와 아키텍처

인프라스트럭처 영역은 구현 기술을 다루는 저수준 모듈이고, 응용 영역과 도메인 영역은 고수준 모듈이다
인프라스트럭처 계정이 가장 하단에 위치하는 계층형 구조와 달리 아키텍처에 DIP 를 적용하면 인프라스트럭처 영역이 응용 영역과 도메인 영역에 의존하는 구조가 된다
flowchart TD
	인프라스트럭처 --> 응용 --> 도메인
	인프라스트럭처 --> 도메인
Mermaid
복사
인프라스트럭처에 위치한 클래스가 도메인이나 응용 영역에 정의한 인터페이스를 상속받아 구현하는 구조가 된다
도메인과 응용 영역에 대한 영향을 주지 않거나 최소화하면서 구현 기술을 변경하는 것이 가능하다
DIP 를 항상 적용할 필요는 없다
사용하는 구현 기술에 따라 완벽한 DIP 를 적용하기보다는 구현 기술에 의존적인 코드를 도메인에 일부 포함하는게 효과적일 때도 있다

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

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

2-4-1. 엔티티와 밸류

도메인 모델의 엔티티는 단순히 데잉터를 담고 있는 데이터 구조라기보다는 데이터와 함께 기능을 제공하는 객체이다
도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화해서 데이터가 임의로 변경되는 것을 막는다
도메인 모델의 엔티티는 두 개 이상의데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현할 수 있다
밸류는 불변으로 구현할 것을 권장하며, 엔티티의 밸류 타입 데이터를 변경할 때는 객체 자체를 완전히 교체한다는 것을 의미한다

2-4-2. 애그리거트

애그리거트는 관련 객체들을 하나로 모은 군집이다
애그리거트를 사용하면 개별 객체가 아닌 관련 객체를 묶어서 객체 군집 단위로 모델을 바라볼 수 있게 된다
애그리거트는 군집에 속한 객체를 관리하는 루트 엔티티를 갖는다
루트 엔티티는 애그리거트에 속해 있는 엔티티와 밸류 객체를 이용해서 애그리거트가 구현해야 할 기능을 제공한다

2-4-3. 리포지터리

도메인 객체를 지속적으로 사용하려면 RDBMS, NoSQL, Local File 과 같은 물리적인 저장소에 도메인 객체를 보관해야 한다
이를 위한 도메인 모델이 리포지터리(Repository) 이다
리포지터리는 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다
도메인 모델을 사용해야 하는 코드는 리포지터리를 통해서 도메인 객체를 구한 뒤 도메인 객체의 기능을 실행한다
도메인 모델 관점에서 리포지터리는 도메인 객체를 영속화하는데 필요한 기능을 추상화한것으로 고수준 모듈에 속한다
flowchart TD
	application -.-> Order
	application --> OrderRepository
	OrderRepository --> JpaOrderRepository
	
	
Mermaid
복사
응용 서비스는 의존 주입과 같은 방식을 사용해서 실제 리포지터리 구현 객체에 접근한다
응용 서비스와 리포지터리는 밀접한 연관이 있다
응용 서비스는 필요한 도메인 객체를 구하거나 저장할 때 리포지터리를 사용한다
응용 서비스는 트랜잭션을 관리하는데, 트랜잭션 처리는 리포지터리 구현 기술의 영향을 받는다
리포지터리는 응용 서비스가 필요로하는 메소드를 제공한다
애그리거트를 저장하는 메소드
애그리거트 루트 식별자로 애그리거트를 조회하는 메소드

2-5. 요청 처리 흐름

표현 영역은 사용자가 전송한 데이터 형식이 올바른지 검사하고 문제가 없다면 데이터를 이용해서 응용 서비스에 기능 실행을 위임한다
sequenceDiagram
	autonumber
	participant u as User
	participant c as Controller
	participant a as AppService
	participant d as Domain Object
	participant r as Repositroy

	u -->> c: HTTP 요청
	activate c
		c -->> c: 요청 데이터를 응용 서비스에 맞게 변환
		c -->> a: 기능 실행
		activate a
			a -->> r: 데이터 조회
			r --) a: 도메인 객체 반환
			a -->> d: 도메인 로직 실행
			a --) c: 리턴
		deactivate a
		c --) u: HTTP 응답
	deactivate c
Mermaid
복사
응용 서비스는 도메일 모델을 이용해서 기능을 구현한다
기능 구현에 필요한 도메인 객체를 리포지터리에서 가져와 실행하거나 신규 도메인 객체를 생성해서 리포지터리에 저장한다
두 개 이상의 도메인 객체를 사용해서 구현하기도 한다
응용 서비스는 도메인의 상태를 변경하므로 변경 상태라 물리 저장소에 올바르게 반영되도록 트랜잭션을 관리해야 한다

2-6. 인프라스트럭처 개요

도메인 객체의 영속성 처리, 트랜잭션, STMP 클라이언트, REST 클라이언트 등 다른 영역에서 필요로하는 프레임워크, 구현 기술, 보조 기능을 지원한다
도메인 영역과 응용 영역에서 인프라스트럭처의 기능을 직접 사용하는 것보다 이 두 영역에 정의한 인터페이스를 인프라스트럭처 영역에서 구현하는 것이 시스템을 더 유연하고 테스트하기 쉽게 만들어준다
구현의 편리함은 DIP 가 주는 다른 장점 (변경의 유연함, 테스트가 쉬움) 만큼 중요하기 때문에 DIP 장점을 해치치 않는 범위에서 응용 영역과 도메인 영역에서 구현 기술에 대한 의존을 가져가는 것이 나쁘지 않다

2-7. 모듈 구성

아키텍처의 각 영역은 별도의 패키지에 위치한다
flowchart TD
	ui -.-> application -.-> domain
	infrastructure -.-> domain
Mermaid
복사
도메인 모듈은 도메인에 속한 애그리거트를 기준으로 다시 패키지를 구성한다
애그리거트, 모델, 리포지터르는 같은 패키지에 위치시킨다
도메인이 복잡하면 도메인 모델과 도메인 서비스를 별도의 패키지에 위치시킬 수도 있다
com.myshop.order.domain.order: 애그리거트 위치
com.myshop.order.domain.service: 도메인 서비스 위치
응용 서비스도 도메인 별로 패키지를 구분할 수 있다
com.myshop.catalog.application.product
com.myshop.catalog.application.category
모듈 구조를 얼마나 세분화해야 하는지에 대한 정해진 규칙은 없기 때문에 한 패키지에 너무 많은 타입이 몰려서 코드를 찾을 때 불편한 정도만 아니면된다
한 패키지에 가능한 10 ~ 15개 미만으로 타입 개수를 유지하는편이 좋다
이 개수가 넘어가면 패키지를 분리하는것이 좋다