우아한 객체 지향

190620 우아한 객체지향을 듣고 정리한 내용.

의존성을 이용해 설계 진화시키기

객체 지향을 말할때도 역할이나 책임에 대한 이야기도 하지만, 실제적으로 역할이나 책임이 필요한건 의존성을 어떻게 관리하는지가 핵심이다. 의존성에 따라 설계가 많이 바뀐다.

설계

간단히 말해서 코드를 어떻게 배치할 것인가에 대한 의사결정. 어떤 클래스에 어떤 코드를 넣을 것이고, 어떤 패키지에 어떤 코드를 넣을 것이고, 어떤 프로젝트에 어떤 코드를 넣을 것인지에 따라서 설계의 방향이 굉장히 바뀐다.

여기서 핵심은 "변경"에 초점을 맞추는 것이다. 같이 변하는 코드는 같이 두고, 같이 변경되지 않는 코드는 따뤄 둬야한다. 그렇다면 변경의 핵심은 무엇일까? 바로 "의존성"이다.

의존성

의존성이란, 변경에 의해 영향을 받을 가능성이라고 할 수 있다. 의존성은 크게 두 가지로 나뉘는데, 클래스 사이의 의존성과 패키지 사이의 의존성이 있다.

좋은 의존성을 관리하기 위한 규칙들

양방향 의존성을 피하라

항상 양쪽 클래스를 동기화를 시켜줘야 하며, 굉장히 신경쓸게 많다. 그 과정에서 성능이슈가 자주 발생하며, 두 개의 싱크를 맞추는 과정에서 버그 또한 많이 발생한다.

class A {
  private b: B;

  setA(b: B) {
    this.b = b;
    this.b.setA(this);
  }
}

class B {
  private a: A;

  setA(a: A) {
    this.a = a
  }
}

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

성능 이슈가 발생할 수 있으, 객체들의 관계를 유지하기 위한 많은 노력이 필요하다.

class A {
  private b: ReadonlyArray<B>;
}

class B {}

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

위 방법보다 더 좋은 방법은 의존성이 없다면 제거하는 것이다.

class A {}

class B {}

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

패키지 사이에 양방향 의존성이 있다면 꼭 제거하자.

연관관계

연관 관계란 탐색가능성(navigability)라고 할 수 있다. 예를들어 OrderOrderLineItem이 있다고 하자.

Order가 뭔지 알면, Order를 통해 원하는 OrderLineItem을 찾을 수 있는 것이다.

두 객체 사이에 협력이 필요하고 두 객체의 관계가 영구적이라면 연관관계를 이용해 탐색을 구현한다.

class Order {
  private orderLineItems: ReadonlyArray<OrderLineItem>; // 연관관계를 통해 협력

  place() {
    this.validate();
    this.ordered();
  }

  private validate() {
    // ...
    for(const orderLineItem of this.orderLineItems) {
      orderLineItem.validate(); // 연관관계를 통해 협력
    }
  }
  private ordered() {}
}

객체 참조로 구현한 연관 관계의 문제점

협력을 위해 필요하지만, 두 객체 사이의 결합도가 높아진다.

메모리상에 있을때는 성능에 사실 큰 이슈가 없지만, ORM을 사용하거나 DB로 직접 매핑을 할 시에는 저 연관 관계가 있는 순간 헬게이트(?)가 열린다. (어디까지 읽어야 하고 어디까지 읽지 말아야 하는가...)

수정 시 도메인 규칙을 함께 적용할 경계는? (= 트랜잭션 경계는 어디까지인가?)

Order의 상태를 변경할 때 연관된 도메인 규칙을 함께 적용해야하는 객체의 범위를 어디까지 해야될지 모호해 진다. 이 말은 트랜잭션 경계를 어디까지 해야하는지와 같으며, 어떤 테이블에서 어떤 테이블까지 하나의 단위로 잠금(lock)을 설정할 것인가에 대한 고민으로 이어지게 된다.

트랜잭션 경합으로 인한 성능 저하로 이어질 수 있다.

객체 참조는 모든 것을 연결 시켜버린다.

객체 참조는 결합도가 가장 높은 의존성을 가진다.

연관 관계를 끊기 위한 대안으로..

Repository를 통한 탐색(약한 결합도)

내부 조회 로직은 이상한 메서드가 덕지덕지 붙을 수는 있지만 비즈니스 로직 관점에서는 단방향으로 만들 수가 있다.

어떤 객체들을 묶고 어떤 객체들을 분리할 것인가?

간단한 규칙으로는 아래와 같다.

  • 함께 생성되고 함께 삭제되는 객체들을 함께 묶어라

  • 도메인 제약사항을 공유하는 객체들을 함께 묶어라

  • 가능하면 분리하라.

하지만 비즈니스룰에 따라 묶는 기준은 언제든지 달라질 수 있다. 정답은 없으니 위에 예제를 전부라고 생각하지 말자.

예제

영상 및 아래 그림 참조

나중에 더 자세히..

도메인 이벤트(Domain Event) 퍼블리싱

Last updated