도메인 주도 개발 시작하기(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개 미만으로 타입 개수를 유지하는편이 좋다
◦
이 개수가 넘어가면 패키지를 분리하는것이 좋다