1. 의존성


의존성

  • 설계 = 코드를 어떻게 배치할 것인가에 대한 의사결정
  • 의존성 = 변경에 의해 영향을 받을 수 있는가의 여부
  • e.g. A 가 B 에 의존한다는 것 = B 가 변경되면 A 도 변경될 수 있음

클래스 의존성


연관 관계

class A {
    private B b;
}
  • 객체 참조

의존 관계

class A {
    public B method(B b) {
        return new B();
    }
}
  • 파라미터에 존재
  • 리턴값에 존재
  • 메서드 내부에서 인스턴스 생성

상속 관계

class A extends B {
}

실체화 관계

class A implements B {
}

패키지 의존성


  • 패키지에 포함된 클래스 사이의 의존성

양방향 의존성을 피하라

다중성이 적은 방향을 선택하라

의존성이 필요없다면 제거하라

패키지 사이의 의존성 사이클을 제거하라

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번째 컴파일 에러 - 배달 완료


본질: 도메인 로직의 순차적 실행

두 가지 해결방법

첫 번째 - 절차지향 로직

  1. OrderDeliveredService 를 추가
  2. 배달 완료 로직을 전부 이동

좋은 설계일까?

  • 패키지 간 의존성 사이클 발생

의존성 역전 원리를 적용해 사이클 제거

두 번째 - 도메인 이벤트 퍼블리싱

  • Order 가 Shop 을 직접 호출하던 로직을

  • Order 가 도메인 이벤트를 발행하도록 수정

  • EventHandler 를 통해 이벤트 처리

  • 의존성 사이클 발생

  • 새로운 Billing 패키지로 분리

패키지 의존성 사이클을 제거하는 3가지 방법

  1. 중간 객체

  1. 의존성 역전

  1. 새로운 패키지 추가

4. 의존성과 시스템 분리