도메인 모델 시작
- 클린 코드를 위함
아키텍처 개요
- 표현, 응용, 도메인, 인프라스트럭처
- 표현: HTTP 요청에 대한 응답
- 응용: 도메인 모델에 로직 수행을 위임
- 도메인: 도메일 모델 구현
- 인프라스트럭처: 구현 기술(ex. DB, MQ, .. 등등)
계층 구조
- 표현 > 응용 > 도메인 > 인프라스트럭처
- 상위에서 하위만 의존 (↔ 하위는 상위 의존 X)
DIP
- 인프라스트럭처에 의존하면 ‘테스트 어려움’과 ‘기능 확장의 어려음’을 겪게 된다.
- 추상화한 인터페이스를 활용하자.
- 테스트는 Mock 프레임워크를 이용하자.
- 영역(레이어)관점에서 추상화하자.
- (Tip) DI와 DIP는 무엇이 다른가?
도메인 영역의 주요 구성요소
- 엔티티, 밸류, 애그리거트, 리포지토리, 도메인 서비스
애그리거트
- 복잡한 도메인을 이해하고 관리하기 쉬운 단위로 만들어진 상위 수준의 모델
- 단순한 setter를
public
으로 만들지 않는다. - 하나의 트랜잭션에서 하나의 애그리거트만 수정되어야 한다.
- 리포지토리는 애그리거트 단위로 존재한다.
- 편리함을 위해 다른 애그리거트를 직접 참조하는 것 보다 ID를 이용하여 다른 애그리거트를 참조하는 것이 좋다.
orderer.getCustomer().changeAddress(newSippingInfo.getAddress()); // --- (X)
// ...
Customer customer = customerRepository.findById(orderer.getCustomer().getCustomerId()); // --- (O)
customer.changeAddress(newSippingInfo.getAddress());
- N+1 쿼리로 인한 성능 이슈가 있다면 조인을 사용하자.
리포지토리와 모델구현
- 애그리거트를 어떤 저장소에 저장하느냐에 따라 구현 방법이 다르다.
- RDBMS를 사용할 경우, ORM 프레임워크가 적합하다.
- 리포지토리 인터페이스는 도메인 영역에, 구현체는 인프라스트럭처 영역에
리포지토리의 조회 기능
- 조건에 따른 검색
- JPA 경우,
CreiteriaBuilder
,Predicate
사용 - ‘관련 유틸을 만들어 사용해보자’ or
Spring Data JPA
(+ QuertDSL, JinQ) - 조회전용 모델을 만들자.
응용 서비스와 표현 영역
응용 서비스
- 표현 영역: 요구되는 스펙 객체
- 응용 서비스: 도메인 영역과 표현 영역을 연결해 주는 파사드(facade)역할을 한다.
- 데이터 중복 및 유효성을 체크한다 -> 애그리거트를 구한다(생성한다) -> 애그리거드의 도메인 기능을 실행한다 -> 결과를 반환한다.
- 도메인 로직을 도메인 영역과 응용 서비스에 분산해서 구현하면 코드 품질에 문제가 발생한다.
- 코드의 응집성이 떨어진다.
- 중복 코드가 발생한다.
- 응용 서비스는 표현 영역에 필요한 데이터만 리턴하는 것이 기능 실행 로직의 응집도를 높이는 확실한 방법이다. (반대도..)
- 응용 서비스의 파라미터를 넘길 때 표현 영역과 관련된 타입을 사용하면 안된다. (ex.
HttpServletRequest
,HttpSession
등)- 유지보수하는 비용이 증가하게 됨
- 이벤트를 사용하면 코드 다소 복잡해지는 대신 도메인간의 의존성이나 외부 시스템 의존도를 낮춰준다.
표현 영역
- 화면 흐름 제공 및 제어, 요청에 알맞는 처리 및 결과 제공, 세션 관리
- 표현 영역과 응용 서비스을 분리하여 적절한 검증 처리를 한다.
- 조회 같은 기능은 Controller에서 DAO로 바로 접근해도 무관하다?
도메인 서비스
- 하나의 애그리거트로 기능을 구현할 수 없을 때, (우겨넣지 말고) 별도의 도메인 서비스를 구현하자. (ex. 금액 계산)
- 응용 서비스 : 응용 로직 = 도메인 서비스 : 도메인 로직
- 상태 없이 로직만 구현
- 애그리거트 객체에 도메인 서비스를 전달하는 것은 응용 서비스 책임(DI를 응용서비스에서 하고 애그리거트에 객체 전달)
애그리거트 트랜잭션 관리
- 물리적으로 다른 애그리거트를 사용한 일관성 깨짐 방지를 위한 두가지 방식
- 선점 잠금
- 잠금 해제시 까지 블로킹
- JPA에서
LockModeType.PESSIMITIC_WRITE
- deadlock이 걸릴 수 있으므로 힌트같은 것을 이용하자(ex. 최대 대기 시간)
- 비선점 잠금
- 동시 접근 막음
- JPA에서
@Version
Bounded Context
- 도메인을 완벽하게 표현하는 단일 모델을 만드려는 시도는 옳지 못 하다.(ex.
상품-> 재고 관리의 상품, 주문의 상품, 배송의 상품.. 등등) - 하위도메인마다 사용하는 용어와 의미가 다르다. -> 구분되는 경계를 같는 컨텍스트 -> Bounded Context
- 표현영역, 응용 서비스, 인프라 영역에도 포함되는 내용이다.
- CQRS 패턴: 상태를 변경하는 명령 기능과 조회 기능을 위한 모델을 구분하는 것
- API vs API, API vs Message
- 공개 호스트 서비스, 안티 코럽션 계층, 공유 커널, 독립 방식
- 컨텍스트 맵: Bounded Context간의 관계 표시
이벤트
- 트랜잭션 경계의 모호함, 성능 이슈, 로직 섞임 -> 이벤트 사용
- 이벤트: 과거에 벌어진 어떤 것
- 구성 요소
- 주체: 도메인 객체
- 디스패처(publisher): 주체와 핸들러를 연결
- 핸들러(subscriber): 이벤트에 대한 반응
- 구성
- 종류, 발생시간, 추가 데이터
- 용도
- 트리거
- 데이터 동기화(서로 다른 시스템 간)
- 동기 처리 문제? 비동기
Executors
,MQ
- 이벤트 저장소(
포워더
,API
) - 구현은 책을 참고
- 고려사항
EventEntry
추가- 포워더에 전송 실패 문제
- 이벤트 손실
- 이벤트 순서
- 이벤트 재처리
CQRS
- Command Query Responsibility Segregation
- 상태를 변경, 상태를 조회 구분
- 복잡한 도메인에 적합
- 각 모델에 맞는 구현 기술 선택 가능(ex. JPA와 Mybatis)
- 혹은 다른 DB를 사용 가능(ex. RDB와 NoSQL)
- 장점
- 로직에 집중
- 조회 성능 향상
- 단점
- 구현량 증가
- 더 많은 구현 기술 필요
DI와 DIP는 무엇이 다른가?
# DI와 DIP는 무엇이 다른가?
- `Dependency Injection(DI)`, `Dependency Inversion Principle(DIP)`
- DIP는 IoC를 도와주는?가능케하는? 하나의 형태
- DI는 DIP를 적용법 중 하나
# DI의 종류
- Constructor Injection
- Property Injection(Setter Injection)
- Interface Injection
# 오해
- IoC, DIP가 사용된다고 해서 항상 IoC Container가 필요한 것은 아니다.
# Service Locator
- 또 다른 DIP 적용법