도메인 주도 개발(Domain Driven Design)

jjunhwan.kim·2022년 11월 22일
0

도메인 주도 개발 시작하기

3. 애그리거트

3.1 애그리거트

  • 상위 수준 개념을 이용하여 전체 모델을 정리하면 전반적인 관계를 이해하는데 도움이 된다.
  • 도메인 객체 모델이 복잡해지면 개별 요소 위주로 모델을 이해하게 되고 전반적인 구조나 큰 수준에서 도메인 간의 관계를 파악하기 어려워진다.
  • 도메인 요소 간의 관계를 파악하기 어렵게되면 코드를 변경하고 확장하는 것이 어려워진다.
  • 복잡한 도메인을 이해하기 쉽게 상위 수준에서 모델을 조망할 수 있는 방법이 애그리거트이다.
  • 애그리거트는 관련된 객체를 하나의 객체로 묶은 것이다.

3.2 애그리거트 루트

  • 애그리거트는 여러 객체로 구성된다. 애그리거트에 속한 모든 객체가 일관된 상태를 유지하려면 애그리거트 전체를 관리할 주체가 필요하다. 이 책임을 지는 것이 애그리거트의 루트 엔티티이다.
  • 애그리거트 루트의 핵심 역할은 애그리거트의 일관성이 깨지지 않도록 하는 것이다.
  • 애그리거트 외부에서 애그리거트에 속한 객체를 직접 변경하면 안 된다. 이는 일관성을 깨는 원인이 된다.
  • 불필요한 중복을 피하고 애그리거트 루트를 통해서만 도메인 로직을 구현하게 만들려면 다음 두 가지를 습관적으로 적용해야 한다.
    • 단순히 필드를 변경하는 set 메서드를 public 범위로 만들지 않는다. => 의미가 드러나는 메서드를 구현해서 사용한다.
    • 밸류 타입은 불변으로 구현한다. => 밸류 객체가 불변이면 밸류 객체의 값을 변경할 수 없으므로 외부에서 밸류 객체의 상태를 변경할 수 없다.

... 수정필요

3.3 리포지터리와 애그리거트

  • 리포지터리는 애거리거트 단위로 존재한다. 물리적으로 별도의 테이블에 저장해도 각각 리포지터리를 만들지 않는다.
  • 리포지터리는 보통 다음의 두 메서드를 기본으로 제공한다.
    • save
    • findById
  • 리포지터리는 애그리거트 전체를 영속화해야한다
    • Order 애그리거트와 관련된 테이블이 세 개라면 모든 테이블에 데이터를 저장해야한다.
  • 애그리거트를 구하는 리포지터리 메서드는 완전한 애그리거트를 제공해야 한다.
  • 리포지터리가 완전한 애그리거트를 제공하지 않으면 NullPointerException 같은 문제가 발생할 수 있다.

3.4 ID를 이용한 애그리거트 참조

  • 하나의 애그리거트는 다른 애그리거트를 참조할 수 있다.
  • 애그리거트 관리 주체는 애그리거트 루트이므로 애그리거트에서 다른 애그리거트를 참조한다는 것은 다른 애그리거트의 루트를 참조하는 것과 같다.
  • 애그리거트 간의 참조는 필드를 통해 쉽게 구현할 수 있다. 하지만 필드를 통한 참조는 다음 문제를 야기할 수 있다.
    • 편한 탐색 오용
      • 한 애그리거트 내부에서 다른 애그리거트 객체에 접근할 수 있으면 다른 애그리거트의 상태를 쉽게 변경할 수 있다. 하지만 한 애그리거트가 관리하는 범위는 자기 자신으로 한정해야 한다.
      • 한 애그리거트에서 다른 애그리거트의 상태를 변경하는 것은 애그리거트 간의 의존 결합도를 높여서 결과적으로 애그리거트의 변경을 어렵게 만든다.
      • 한 트랜잭션에서 하나의 애그리거트만 수정해야 한다. 만약 한 트랜잭션에서 두 개 이상의 애그리거트를 수정해야 한다면 응용 서비스에서 두 애그리거트를 수정하도록 구현한다.
    • 성능에 대한 고민
      • 애그리거트를 직접 참조하면 성능에 관련된 고민을 해야 한다. JPA를 사용하면 객체의 지연 로딩과 즉시 로딩 중 어느 방식을 사용할지 고민해야 한다.
    • 확장 어려움
      • 단일 서버에 단일 DBMS로 서비스를 제공하다가 도메인 별로 시스템을 분리하게 되면 필드를 통한 참조를 사용하기 어려울 수 있다.
  • 이러한 세 가지 문제를 완화할 때 사용할 수 있는 것이 ID를 이용해서 다른 애그리거트를 참조하는 것이다.
    • ID 참조를 사용하면 한 애그리거트에 속한 객체들만 참조로 연결하고 애그리거트 간에는 ID를 사용하여 참조한다.
    • 애그리거트 별로 다른 저장소를 사용하도록 구현하기도 용이하다.
    • 하지만 조회시 조인을 사용할 수 없기 때문에 N+1 조회 문제가 발생할 수 있다.
    • 별도의 조회 전용 쿼리를 사용하여 문제를 해결할 수 있다.
    • 애그리거트마다 서로 다른 저장소를 사용하면 한 번의 쿼리로 관련 애그리거트를 조회할 수 없다. 이 때는 캐시를 적용하거나 조회 전용 저장소를 따로 구성한다.

0개의 댓글