DDD 입문 @ZUM
사내 DDD(Domain Driven Development) 세미나 관련해서 정리한 내용입니다. Domain 중심으로 개발을 진행하는 관점에 대한 내용이 주된 내용입니다.
도메인 모델
-
소프트웨어로 해결할 문제 영역
- 도메인 모델
-
도메인의 구성요소를 개념적으로 표현
- 정적 모델
- 클래스, ER
- 동적 모델
- 커뮤니케이션, 시퀀스, 상태
-
- 단순 데이터 모델
- 단순 속성 나열
- 반쪽짜리 모델
- Action이 없음(?)
- 도메인 모델에 필요한거
- 표현력
- 불피요한 번역 /해석이 필요하지 않도록 (코드를 보고 알아야…)
- 기능
- 객체 모델로 표현
- 동적 측면 / 제약 조건 표현
- 메소드를 잘활용하면 제약조건 표현에 좋다.(네이밍으로 표현(?))
- 정적 : 모델링 할때 모델(도메인)에 상태와 행위를 같이 표현해야 보기 좋다.
- 표현력
DDD
- 모델 구성요소
- 기본모델
- 엔티티, 밸류
- 묶음
- 에그리거트
- 기능
- 객체 모델
- 서비스
- 영속
- 레파지 토리
- 기본모델
- 엔티티 중심 모델 만들기
- 식별자 생성
- 특정 규칙, UUID, 시스템 식별자, 일려번호 등…
- 밸류 : 개념적으로 하나의 값
- 주소, 수신자등의 값을 ShippingInfo라는 Value로 표현
- 모델에 기능 넣기
- https://wckhg89.github.io/archivers/oop
- 메소드로 기능과 제약 표현
- 해당 메소드 이름만 보고 모델이 어떤 일을 하는지 알기 좋다
- How ?
- 가능하면 public set 메소드를 쓰지말자
- 도메인의 의도를 사라지게 하는 효과가 있다.
- get도 웬만하면 쓰지말자
- 도메인에 getter setter를 넣으면 절차지향적인 개발이 되기 쉽다.
- DTO 영역 경계 간 데이터를 전달하기 위한 DTO와는 구분
- 가능하면 public set 메소드를 쓰지말자
- 주의사항
- 모든 엔티티들에게 연관관계를 맺지 말자 (연관관계 최소화)
- n+1 problem을 야기하기가 쉽다.
- 모든 엔티티들에게 연관관계를 맺지 말자 (연관관계 최소화)
- 개발 코드와 상황에 대한 불일치를 계속해서 네이밍을 바꾸면서 발전 시켜야한다.
애그리거트
- 복잡해지는 모델의 경계를 잘 정해야 한다.
-
개별 모델을 상위 수준에서 적당하게 묶어주는 단위
-
유사한 라이프 사이클을 가지는 단위 (한 애그리거트에 묶일 가능성이 높음)
- 한 애그리거트는 다른 애그리거트에 있는 객체와 엮이지 않음
- 주문 1 애그리거트, 주문 2 애그리거트는 엮이지 않음
- 애그리거트 루트
- 애그리거트의 모든 객체는 루트에 직/간접적으로 속한다.
- 애그리거트에 속한 객체의 상태를 바꿀 수 있는것은 애그리거트 루트를 통해서만 가능하다.
- 애그리거트의 트랜잭션
- 범위는 자기자신의 애그리거트로 제한해야한다.
- 애그리거트의 크기 경계
- 상품 (1) : 리뷰 (N)
- 상품과 리뷰는 함꼐 바뀌지 않음 (라이프 사이클이 다름)
- 리뷰의 관리 주체(리뷰를 다는사람)와 상품의 관리 주체(관리자)가 다름
- 상품 (1) : 리뷰 (N)
아키텍처
- DIP (의존관계 역전의 원칙)
-
고수준 모듈 / 저수준 모듈
-
고수준 : 가격할인 계산 (고객정보를 구함 / 룰을 이용해서 할인 금액 구함) (더 상위 수준)
-
저수준 : RDMS JPA / Drools로 룰을 적용 (더 저수준)
- 의존의 방향 (고수준 -> 저수준)
-
저수준 고수준이 섞여 있으면 저수준의 로직 변화에 고수준이 영향을 받게 된다.
-
DIP 적용으로 방지 할 수 있다.
- 전략 패턴(?)
- 인터페이스를 활용하여 구현체를 따로 두면 저수준의 구현이 바뀌더라도 다른 구현체로 갈아 끼워주면 된다.
- 인터페이스의 네이밍은 고수준 관점에서의 네이밍 혹은 기능을 정의 하는게 좋다.
-
- 영역 (패키지 구성(?))
- 표현 (컨트롤러)
- 응용 서비스
- 사용자의 요청을 처리할 기능을 구현
- 처리 흐름을 구현!
-
도메인 로직은 도메인 영역에 위임
- 도메인에 역할을 위임을 잘 할 수록 더 짧은 코드가 작성된다.
- 도메인
- 인프라 스트럭쳐
- 응용서비스 혹은 도메인에서 사용하는 실제 구현
-
- 응용 서비스의 입력과 출력
- 입력
- 필요한 파라미터만 넘기자
- 출력
- 조회전용 모델 (CQRS)
- 트랜잭션
- Transaction 단위가 된다.
-
응용 서비스에 도메인 로직을 넣지 않기
- 몇가지 고민거리
- 서비스의 크기 어디까지
-
인터페이스를 구지 가져야하나?
- 구현의 편의성을 우선으로 생각하는 편이십니당.
- 입력
- 표현영역 (Controller)
- 사용자에게 화면을 제공하고 사용 흐름 제어
- 사용자 요청을 응용 서비스에 전달
- 세션 관리
- 인프라 스트럭쳐
- 기반 구현 기술 제공 (실제 구현)
- DIP 고려
- 모듈 구성
- 각 도메인 별로 패키지를 둔다.
- 그 하위에 표현 / 응용 / 도메인 / 인프라 스트럭쳐 패키지를 둔다. (DDD 관점)
- 각 도메인 별로 패키지를 둔다.
- CQRS
-
상태를 바꾸는 기능(Command) / 조회하는 기능(Query)
-
위의 상황에 따라서 모델을 나누자
- 주문 상태 변경 (Command Model)
- 주문 내용 조회 (Query Model)
-
리파지토리, 모델 구현
- 기본적으로 애그리거트(루트) 단위로 리파지토리 생성
- 테이블 단위로 존재하는 것이 아니다.
- 리포지토리는 완전한 애그리거트를 다룸
- 로딩 시점에 애그리거트에 속한 모든 연관 객체 로딩
- 즉 엔티티나 콜렉션에 대해 EAGER 로딩 기본으로 사용
- 저장 시점에 애그리거트에 속한 모든 연관객체 저장
- 삭제 시점에 애그리거트에 속한 모든 연관객체 삭제
- 로딩 시점에 애그리거트에 속한 모든 연관 객체 로딩
- 별도 테이블이라고 무조건 Entity 라고 생각하지 말자
- 한 애그리거트에 속해 있다면 Value가 아닐지 의심해보자
- Value라고 생각되면, JPA의 @Embeddable 어노테이션을 사용해서 애그리거트 루트 엔티티에 밸류로 포함시키자.
- 밸류 컬렉션
- 애그리거트 내부의 콜렉션은 주로 밸류 콜렉션
- 엔티티일 경우는 드물다.
- 애그리거트 내부의 콜렉션은 주로 밸류 콜렉션
- 한 애그리거트에 속해 있다면 Value가 아닐지 의심해보자
- 애그리거트간의 연관
- 애그리거트 루트를 참조한다.
- 직접 참조보다는 ID를 통한 간접 참조를 선호
- 편한 탐색 오용 방지 (다른 애그리거트 루트에서 다른 커스터머의 패스워드를 손쉽게 바꿀 수 있게된다.)
- 성능에 대한 고민 제거
- 시스템 확장시 유리
올바른 소프트웨어?
- 사용자에게 가치를 주는 SW
- 만드는게 어떤건지를 알아야 할텐데…
- 도메인 중심 사고
- 도메인 중심으로 사고하게 되면 만드는게 무엇인지 잘 이해할 가능성이 높아진다.
- BDD (Behavior-Driven Development)
가치의 변화에 대응
-
변화에 대응할 수 있는 역량 필요
- 객체지향, 함수형 사고
- 리펙토링
- TDD
- 기술
요즘 기술, 좋은 기술 ?
-
상황에 맞는 기술 선택
-
조직 상황을 고려하지 않은 기술은 변화에 빠르게 대응할 수 없게 만들어 경쟁력 낮춤