1. 의존성
의존성
- 설계 = 코드를 어떻게 배치할 것인가에 대한 의사결정
- 의존성 = 변경에 의해 영향을 받을 수 있는가의 여부
- e.g. A 가 B 에 의존한다는 것 = B 가 변경되면 A 도 변경될 수 있음
클래스 의존성
연관 관계
- 객체 참조
의존 관계
- 파라미터에 존재
- 리턴값에 존재
- 메서드 내부에서 인스턴스 생성
상속 관계
실체화 관계
패키지 의존성
- 패키지에 포함된 클래스 사이의 의존성
양방향 의존성을 피하라
다중성이 적은 방향을 선택하라
의존성이 필요없다면 제거하라
패키지 사이의 의존성 사이클을 제거하라
2. 예제 살펴보기
주문 플로우
Domain Object - 메뉴 & 주문
문제점 - 메뉴 불일치
주문 Validation
협력 설계하기
관계의 방향 = 협력의 방향 = 의존성의 방향
- 연관관계 = 협력을 위해 필요한 영구적인 탐색 구조
- 의존관계 = 협력을 위해 일시적으로 필요한 의존성 (파라미터, 리턴타입, 지역변수)
연관관계 = 탐색가능성
연관관계와 협력
- 두 객체 사이에 협력이 필요하고 두 객체의 관계가 영구적이라면 연관관계를 이용해 탐색 경로 구현
3. 설계 개선하기
- 설계를 진화 시키기 위한 출발점 = 코드 작성 후 의존성 관점에서 설계 검토
두 가지 문제
- 객체 참조로 인한 결합도 상승
- 패키지 의존성 사이클
의존성 살펴보기
- 서비스와 도메인 패키지를 나누어 설계를 도식화한 모습
의존성 사이클
무엇이 문제인가?
- Order 는 가게의 최소 주문 금액 또는 영업중인지 판단하기 위해 객체 참조를 통해 의존하고 있다.
- OrptionGroupSpecification 과 OptionSpecification 이 OrderOptionGroup 과 OrderOption 에서 가격과 이름 데이터를 가져온다.
- 즉, 현재 양방향 연관 관계가 잡혔다.
- OrderOption 을 고칠 때 OptionSpecification 도 고쳐야한다.
- Shop 을 고치면 Order 도 고쳐야한다.
- 즉, 패키지가 잘못 구성되어 있거나 의존성의 방향이 잘못되어있다.
양방향 연관 관계 케이스 3개, 해결 방법 모두 다름
중간 객체를 이용한 의존성 사이클 끊기
- Order 에서 Shop 쪽으로 단방향 의존성으로 변경되었다.
의존성 역전 원리
- 클래스들이 추상화에 의존해라
- 추상화 = 추상 클래스 혹은 인터페이스가 아니다
- 추상화 = 상대적으로 잘 변하지 않는 것
- 즉, Option 은 OptionSpecification 이나 OrderOption 보다 추상적이다.
객체 참조로 구현한 연관 관계의 문제점
성능 문제 - 어디까지 조회할 것인가?
- 객체 참조로 구현할 경우 객체를 통해 어디든지 접근할 수 있기 때문에 Lazy Loading 이슈가 날 수 밖에 없다.
- 이는 다수의 쿼리가 발생하는 것으로 이어진다.
- 그렇다면 어디까지 읽어야하는지 가이드가 없다.
수정 시 도메인 규칙을 함께 적용할 경계는?
- 다른 말로, 트랜잭션 경계는 어디까지인가?
- 어떤 테이블에서 어떤 테이블까지 하나의 단위로 Lock 을 설정할 것인가?
- 즉, 롱 트랜잭션 문제가 발생
객체 참조가 꼭 필요한가?
- 객체 참조는 결합도가 가장 높은 의존성
- 필요한 경우 객체 참조를 끊자
객체 참조를 통한 탐색(강한 결합도)
Repository 를 통한 탐색(약한 결합도)
어떤 객체들을 묶고 어떤 객체들을 분리할 것인가?
간단한 규칙
- 함께 생성되고 함께 삭제되는 객체들을 함께 묶어라
- 도메인 제약사항을 공유하는 객체들을 함께 묶어라
- 가능하면 분리하라
객체 묶기
- 주문의 Constraint 와 배송의 Constraint 는 다른 것 처럼 서로 독립적인 객체들로 묶기
- 경계 안의 객체는 참조를 이용해 접근
- 같이 읽어야 하니까 Lazy Loading 을 줘서 편함
- 같이 생성 수정 삭제되니까 Cascade 를 줄 수 있음
- 경계 밖의 객체는 ID 를 이용해 접근
ID를 이용해서 연관관계 설정
- 그룹화된 객체들을 단위로 트랜잭션을 분리할 수 있음
ShopRepository 를 통해 탐색
1번째 컴파일 에러
OrderValidator 를 통해 검증 로직 모으기
- 좋은 설계인가?
- 객체지향은 여러 객체를 오가며 로직을 파악해야된다.
- 이런 설계는 한 곳에서 로직을 파악할 수 있다.
- 응집도가 낮다 = 같이 변경되지 않는 로직이 같이 있는 것
- 검증과 주문 처리 로직이 한 곳에 모여 있음
- OrderValidator 를 통해 응집도를 높일 수 있음
- 객체지향은 여러 객체를 오가며 로직을 파악해야된다.
2번째 컴파일 에러 - 배달 완료
본질: 도메인 로직의 순차적 실행
두 가지 해결방법
첫 번째 - 절차지향 로직
- OrderDeliveredService 를 추가
- 배달 완료 로직을 전부 이동
좋은 설계일까?
- 패키지 간 의존성 사이클 발생
의존성 역전 원리를 적용해 사이클 제거
두 번째 - 도메인 이벤트 퍼블리싱
- Order 가 Shop 을 직접 호출하던 로직을
- Order 가 도메인 이벤트를 발행하도록 수정
- EventHandler 를 통해 이벤트 처리
- 의존성 사이클 발생
- 새로운 Billing 패키지로 분리
패키지 의존성 사이클을 제거하는 3가지 방법
- 중간 객체
- 의존성 역전
- 새로운 패키지 추가