-
도메인 주도 개발 시작하기 CH.1 ~ CH.3DESIGN PATTERN & ARCHITECTURE 2024. 6. 10. 23:13
쿠버네티스 스터디를 끝내고 새로운 스터디를 들어가게 되어, 책을 공부하면서 정리를 해보려고 한다!
책은 최범균 저자의 <도메인 주도 개발 시작하기> 이다.
https://www.yes24.com/Product/Goods/108431347
Ch. 1 : 도메인 모델 시작하기
1.1
도메인
- 소프트웨어로 해결하고자 하는 문제영역
- 한 도메인은 다시 하위 도메인으로 나눌 수 있음
- 고정된 하위 도메인이 존재하는 것은 아님
- 상황에 따라서 하위 도메인 구성 여부가 달라짐
- 특정 도메인을 위한 소프트웨어라고 해서 도메인이 제공해야 할 모든 기능을 직접 구현하는 것은 아님
1.2
도메인 전문가와 개발자 간 지식 공유
- 도메인 전문가는 해당 도메인에 대한 지식과 경험을 바탕으로 본인들이 원하는 기능 개발을 요구
- 개발자는 이러한 요구사항을 분석 및 설계하여 코드를 작성, 테스트, 배포
- => 코딩에 앞서 요구사항을 올바르게 이해하는 것이 중요
- 개발자와 전문가가 직접 대화
- 이해관계자와 개발자도 도메인 지식을 갖춰야 함
1.3
도메인 모델
- 특정 도메인을 개념적으로 표현한 것
- 도메인 자체를 이해하기 위한 개념 모델
- 여러 관계자들이 동일한 모습으로 도메인을 이해하고 지식을 공유하는데 도움
- 구현 기술에 맞는 구현 모델이 따로 필요
1.4
도메인 모델 패턴
- 아키텍처
- 사용자 인터페이스 또는 표현
- 사용자의 요청을 처리하고 사용자에게 정보를 보여줌
- 응용
- 사용자가 요청한 기능을 실행
- 도메인 계층을 조합해서 기능을 실행
- 도메인
- 시스템이 제공할 도메인 규칙을 구현
- 도메인의 핵심 규칙을 구현
- 도메인 모델 패턴 : 도메인 규칙을 객체 지향 기법으로 구현하는 패턴으로 핵심 규칙을 구현한 코드가 도메인 모델에만 위치하기 때문에 규칙이 바뀌거나 규칙을 확장해야 할 때 다른 코드에 영향을 줄일 수 있음
- 도메인의 핵심 규칙을 구현
- 시스템이 제공할 도메인 규칙을 구현
- 인프라스트럭처
- 외부 시스템과의 연동을 처리
- 사용자 인터페이스 또는 표현
1.5
도메인 모델 도출
- 도메인에 대한 이해 없이 코딩을 시작할 수 없음
- 모델을 구성하는 핵심 구성 요소, 규칙 기능을 찾아야 함
- 특정 조건이나 상태에 따라 제약이나 규칙이 달리 적용되는 경우가 많음
- 문서화
- 지식을 공유하는 목적
- 전체 구조를 이해하고 더 깊게 이해할 필요가 있는 부분을 코드로 분석해 나가면 됨
1.6
엔티티와 밸류
- 엔티티
- 고유한 식별자를 가짐
- 식별자가 같으면 두 엔티티는 같다고 판단 가능
- 중복되지 않도록 주의
- 생성 방식
- 특정 규칙에 따라 생성
- ex) 주문번호, 운송장 번호, 카드번호, 현재 시간과 다른 값을 조합 등
- UUID나 Nano ID와 같은 고유 식별자 생성기 사용
- 값을 직접 입력
- 일련번호 사용
- 시퀀스나 DB의 자동 증가 칼럼 사용
- 특정 규칙에 따라 생성
- 고유한 식별자를 가짐
- 밸류
- 개념적으로 완전한 하나를 표현할 때 사용
- 코드의 가독성 향상
- 안전한 코드 작성을 위해 데이터 변경 기능을 제공하지 않는 불변 타입으로 구현을 권장
- 식별자의 경우 도메인에서 특별한 의미를 지니는 경우가 많아 식별자를 위한 밸류 타입을 사용해서 의미가 잘 드러날 수 있도록 할 수 있음
- 도메인 객체가 불완전한 상태로 사용되는 방지하기 위해 생성자를 통해 생성 시점에 필요한 것을 전달받을 수 있도록 해야함
- 도메인 모델에 get/set 메서드를 무조건적으로 추가하는 것을 지양
- set 메서드의 경우 private으로 내부에서 데이터를 변경할 목적으로 사용
- 불변 밸류 타입을 사용하여 불변 타입의 장점을 살리도록 구현
- DTO의 get / set
- 프레젠테이션 계층과 도메인 계층이 데이터를 주고받을 때 사용
- set 메서드 대신 프레임워크를 이용한 private 필드 직접 값 할당 방법을 사용해보자
1.7
도메인 용어와 유비쿼터스 언어
- 최대한 도메인 용어를 사용해서 도메인 규칙을 코드로 작성하여 언어 차이에서 오는 버그를 줄이자
- 유비쿼터스 언어
- 전문가, 관계자, 개발자가 도메인과 관련해서 사용하는 공통의 언어
Ch.2 : 아키텍처 개요
2.1
네 개의 영역
- 표현 영역
- 사용자의 요청을 받아 응용 영역에 전달하고 응용 영역의 처리 결과를 다시 사용자에게 보여주는 역할
- 응용 영역
- 시스템이 사용자에게 제공해야 할 기능을 구현
- 기능 구현을 위해 도메인 영역의 도메인 모델을 사용
- 로직을 직접 수행하기 보다 도메인 모델에 로직 수행을 위임
- 도메인 영역
- 도메인 모델을 구현
- 도메인의 핵심 로직을 구현
- 도메인 모델을 구현
- 인프라스트럭처 영역
- 구현 기술을 다룸
- 논리적인 개념을 표현하기 보다 실제 구현을 다룸
- 도메인, 응용, 표현 영역은 인프라스트럭처에서 제공하는 기능을 사용해서 필요한 기능을 개발
2.2
계층 구조 아키텍처
- 표현 영역과 응용 영역은 도메인 영역을 사용
- 도메인 영역은 인프라스트럭처 영역을 사용
- => 상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않음
- 계층 구조를 유연하게 적용하기도 함
- 표현, 응용, 도메인 계층이 인프라스트럭처에 의존하면 테스트가 어렵고, 기능 확장이 어려워짐
2.3
DIP
- 고수준 모듈의 기능을 구현하려면 여러 하위 기능이 필요
- 고수준 모듈 : 응용, 도메인 영역
- 저수준 모듈 : 하위 기능을 실제로 구현한 것
- 저수준 모듈 : 인프라스트럭처 영역
- DIP
- 의존 역전 원칙
- 추상화 인터페이스를 이용해서 고수준 모듈이 저수준 모듈에 의존하지 않고, 저수준 모듈이 고수준 모듈에 의존하도록 변경
- 이를 통해 구현 기술을 변경하더라도 수정이 필요하지 않고, 테스트가 용이해짐
- DIP를 적용할 때 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출
- 단, DIP를 항상 적용할 필요는 없음
- @Entity와 같이 특정 구현 기술에 의존적인 코드를 도메인에 포함하는 것이 효과적일 때도 있음
2.4
도메인 영역의 주요 구성요소
- 엔티티
- 고유의 식별자를 갖는 객체
- 자신의 라이프 사이클을 갖는다
- 도메인의 고유한 개념을 표현
- 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공
- 도메인 모델의 에닡티 != DB관계현 모델의 엔티티
- 기능 구현을 캡슐화해서 데이터가 임의로 변경되는 것을 막음
- 두개 이상의 데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현 가능
- 밸류
- 식별자를 갖지 않는 객체
- 개념적으로 하나의 값을 표현
- 다른 밸류 타입의 속성이나 엔티티 속성으로 사용
- 불변으로 구현할 것을 권장
- 밸류 타입의 데이터를 변경할 떄는 객체 자체를 완전히 교체 하는 것을 의미
- 애그리거트
- 관련 객체를 하나로 묶은 군집
- 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것
- 상위 수준에서 모델을 볼 수 있어야 전체 모델의 관계와 개별 모델을 이해하는데 도움이 됨
- 객체를 관리하는 루트 엔티티를 가짐
- 애그리거트에 속해 있는 엔티티와 밸류 객체를 이용해서 애그리거트가 구현해야 할 기능을 제공
- 관련 객체를 하나로 묶은 군집
- 레포지터리
- 도메인 모델의 영속성 처리
- 물리적인 저장소에 도메인 객체를 보관
- 구현을 위한 도메인 모델
- 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의
- 응용 서비스는 의존 주입과 같은 방식을 사용해서 실제 레포지터리 구현 객체에 접근
- 응용 서비스가 필요로 하는 메서드를 제공
- 도메인 모델의 영속성 처리
- 도메인 서비스
- 특정 엔티티에 속하지 않은 도메인 로직을 제공
2.5
요청 처리 흐름
- 1. 표현 영역
- 사용자가 전송한 데이터 형식이 올바른지 검사
- 응용 서비스에 기능 실행을 위임
- 사용자가 전송한 데이터를 응용 서비스가 요구하는 형식으로 변환해서 전달
- 2. 응용 서비스
- 도메인 모델을 이용해서 기능 구현
- 두개 이상의 도메인 객체를 사용해서 구현하기도 함
- 도메인의 상태를 변경하므로 변경 상태가 물리 저장소에 올바르게 반영 되도록 트랜잭션을 관리해야함
- 앱 서비스가 변경의 경우 도메인 객체를 이용해서 도메인 로직을 실행하고 단순 조회의 경우 레포지토리를 바로 조회하기도 함
2.6
인프라스트럭처 개요
- 표현, 응용, 도메인 영역을 지원
- 구현의 편리함이 DIP가 주는 다른 장점만큼 중요하기 때문에 응용 영역과 도메인 영역에서 상황에 따라 구현 기술에 대한 의존성을 가져갈 수도 있음
2.7
모듈 구성
- 도메인이 크면 하위 도메인 별로 모듈을 나눔
- 애그리거트, 모델, 레포지터리는 같은 패키지에 위치
- 도메인이 복잡하면 도메인 모델과 도메인 서비스를 별도의 패키지에 위치시킬 수 있음
- ex)
- 애그리거트 : com.shop.order.domain.order
- 도메인 서비스 : com.shop.order.domain.service
- ex)
- 응용 서비스도 도메인 별로 패키리를 구분할 수 있음
- com.shop.order.application.order
- com.shop.order.application.product
Ch.3 : 애그리거트
3.1
애그리거트
- 상위 수준에서 모델 구성을 파악하여 요구 사항 구현에 용이하도록 함
- 관련된 모델을 하나로 모았기 때문에 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 가짐
- 애그리거트에 속한 구성요소는 대부분 함께 생성하고 함께 제거
- 경계를 가짐
- 애그리거트에 속한 객체는 다른 애그리거트에 속하지 않음
- 독립된 객체군으로 자기자신을 관리할 뿐 다른 애그리거트르 관리하지 않음
- 경계 설정
- 도메인 규칙과 요구사항
- 도메인 규칙에 따라 함께 생성되는 구성요소이거나 함께 변경되는 빈도가 높은 객체 한 애그리거트에 속할 가능성이 높음
- A가 B를 갖는다는 조건이 한 애그리거트에 속하는 것을 의미하는 것은 아님
- ex) product 와 review
3.2
애그리거트 루트
- 도메인 규칙을 지키려면 애그리거트에 속한 모든 객체가 정상 상태를 가져야 함
- 애그리거트 루트
- 애그리거트에 속한 모든 객체를 일관된 상태로 관리
- 애그리거트가 제공해야 할 도메인 기능을 구현
- 도메인 규칙에 따라 애그리거트에 속한 객체의 일관성이 깨지지 않도록 메서드 구현
- 애그리거트 내부의 다른 객체를 조합해서 기능을 완성
- 구성 요소의 상태를 참조하고 기능 실행을 위임
- 전체적인 성능을 위해 트랜잭션 범위는 작은 것이 좋음
- => 처리량과 충돌을 방지하기 위해 한 트랜잭션 안에서는 한개의 애그리거트만 수정해야함
- = 즉, 한 애그리거트에서 다른 애그리거트를 변경하면 안됨
- 결합도 증진
- 애그리거트 외부에서 애그리거트에 속한 객체를 직접 변경해서는 안됨
- 논리적인 데이터 일관성이 깨지게 됨
- 밸류 타입의 내부 상태를 변경하려면 애그리거트 루트를 통해서만 가능해야 함
- 둘이상의 변경이 필요하다면 응용 서비스에서 수정
- ex) 도메인 이벤트 활용
- 상황에 따라서는 한 트랜잭션에서 두개 이상의 애그리거트를 변경하는 것을 고려
3.3
리포지터리와 애그리거트
- 애그리거트가 개념상 완전한 한 개의 도메인 모델을 표현하므로 영속성을 처리하는 리포지터리는 애그리거트 단위로 존재
- 애그리거트는 개념적으로 하나이므로 레포지터리는 애그리거트 전체를 저장소에 영속화 해야함
- = 동일하게, 애그리거트를 구하는 레포지토리 메서드는 완전한 애그리거트를 제공해야 함
- 제공하지 않으면 필드나 값이 올바르지 않아 애그리거트의 기능 실행중 nullPointerException과 같은 문제가 발생할 수 있음
- 애그리거트 상태가 변경되면 모든 변경을 원자적으로 저장소에 반영해야 함
- ex) 트랜잭션 이용
3.4
ID를 이용한 애그리거트 참조
- 다른 애그리거트를 직접 참조시 문제
- 다른 애그리거트의 상태를 변경할 수 있음
- 애그리거트간의 의존 결합도를 높임
- 지연, 즉시 로딩 등 성능과 관련된 고민 필요
- 기술의 확정에 어려움
- 다른 애그리거트의 상태를 변경할 수 있음
- => ID를 활용해서 다른 애그리거트를 참조하도록 하여 모델의 복잡도를 낮추고 응집도를 높인다
- => 애그리거트 수준에서 지연 로딩과 동일한 결과를 만들어냄
- 단, 조회 성능을 위해 조인을 사용하는 것을 고려 해야함
- 애그리거트 마다 다른 저장소를 사용하면 조회 성능 증진을 위해 캐시나 조회 전용 저장소를 따로 구성하는 것을 고려
3.5
애그리거트 간 집합 연관
- 1-N의 관계
- 컬렉션을 이용해서 표현 가능
- 개념적으로는 애그리거트 간에 1-N 연관이 있더라도 성능 문제 때문에 애그리거트 간의 1-N 연관을 실제 구현에 반영하지 않음
- M-N의 관계
- 개념적으로 양쪽 애그리거트에 컬렉션으로 연관을 만듬
- 실제 요구사항을 고려해서 구현에 포함할지를 결정해야 함
- 목록이나 상세 화면과 같은 조회 기능은 조회 전용 모델을 이용해서 구현하는 것이 좋음
3.6
애그리거트를 팩토리로 사용
- 애그리거트를 생성하는 팩토리 역할
- 응용 서비스에서 애그리거트의 상태를 확인하지 않아도 됨
- 애그리거트가 갖고 있는 데이터를 이용해서 다른 애그리거트를 생성해야 한다면, 애그리거트에 팩토리 메서드를 구현하는 것을 고려해야함
- 도메인 로직을 한곳에 위치시키는 것이 목적
'DESIGN PATTERN & ARCHITECTURE' 카테고리의 다른 글
도메인 주도 개발 시작하기 CH.8 ~ CH.9 (1) 2024.07.03 도메인 주도 개발 시작하기 CH.6 ~ CH.7 (0) 2024.06.26 도메인 주도 개발 시작하기 CH.4 ~ CH.5 (0) 2024.06.19 도메인 주도 설계 핵심 (0) 2024.03.31 EDA ( Event - Driven -Architecture) (1) 2022.09.29