Java & Spring / / 2023. 2. 21. 11:12

[DDD] 도메인 주도 개발 - (2) 애그리거트

728x90
[DDD] 도메인 주도 개발 - (1) 좋은 아키텍처와 도메인 주도 설계
[DDD] 도메인 주도 개발 - (2) 애그리거트
[DDD] 도메인 주도 개발 - (3) Repositoy 모델 구현
[DDD] 도메인 주도 개발 - (4) CQRS - 조회 중심 도메인 구현 
[DDD] 도메인 주도 개발 - (5) 도메인 서비스
[DDD] 도메인 주도 개발 - (6) 응용 서비스와 표현 영역
[DDD] 도메인 주도 개발 - (7) 애그리거트 트랜잭션 관리
[DDD] 도메인 주도 개발 - (8) 도메인 모델과 BOUNDED CONTEXT
[DDD] 도메인 주도 개발 - (9) 이벤트

 


 

Contents

     

    1. Aggregate 애그리거트란?

    애그리거트연관된 Entity(e)와 Value(v)의 개념적 묶음으로

    한 마디로 도메인을 묶음으로 파악하는 거다.

     

    각각 객체 수준(또는 ERD의 테이블 단위)로 보면 관계 파악이 어려운데,

    관계파악이 어려우면 내용을 파악하느라 변경(확장)이 어려우므로

    복잡한 도메인을 이해하고, 일관성을 관리하기 위해 애그리거트(집합)이라는 묶음을 만들어 주는 방법이다. 

     

    애그리거트 특징

    같은 애그리거트에 속한 객체는 유사한 라이프 사이클을 가져서 함께 생성되고 제거된다.

    반면에, 다른 애거리거트에 속한 객체는 서로 영향을 주지 않는다.

    명확한 경계를 가진다고 보면 된다.

     

    애그리거트(집합) 만들기 

    한 애그리거트(집합)을 만들려면 관련 객체를 묶어주면 되는데,

      1) 도메인 규칙이 같거나

      2) 요구사항에 따라 같이 변경되면

    같은 에그리거트라고 볼 수 있다.

     

     

     

    2. 애그리거트 루트

    애그리거트에는 여러 객체들이 포함되는데,

    그 중에서 애그리거트 전체를 관리하는 주체애그리거트 루트라고 한다.

    한 애그리거트의 객체는 직간접적으로 루트 엔티티에 속하게 된다.

     

     

    특징

    1) 애그리거트 루트는 일관성을 유지하도록 도메인 규칙에 따라 구현해야한다.

    2) 루트가 아니면 애그리거트에 속한 객체들을 직접 변경할 수 없음.

        - 루트를 중심으로 기능 실행, 다른 엔티티나 밸류에 접근

        - 애그리거트 내부에 숨겨져 있어서 캡슐화

     

     이때 주로 Setter를 가져와서 직접 값을 변경해왔다면, 지난 포스트에서 이야기한 것처럼

      (1) setter는 private

      (2) set~대신 의미가 더 명확한 메소드를 구현 (complete~, cancel~, change~)

      (3) Value는 불변타입

    사용하는게 좋다.

     

     

    기능 구현 

    1) 구성요소 상태 참조 변경

    2) 루트의 일부 기능 실행을 하위 다른 애그리거트 객체에 위임

     - 분리한 별도 클래스에 위임한다

    이때 private 객체로 생성해서 외부에서 변경하지 못하게 한다.

    - 만약 객체를 불변으로 구현할 수 없다면, 객체 변경 기능을 package에 한정하는 protected로라도 변경한다.

     

    트랜잭션 범위

    트랜잭션 범위는 작을 수록 좋다. 즉 잠금대상이 적을 수록 좋다

    테이블 1개보다 3개를 잠글 때 성능(처리량)이 감소한다.

     

    따라서 한 트랜잭션에서는 한 애그리거트만 수정한다.

    두 개 이상 시, 충돌은 증가하고 처리량은 감소한다

     

    만약 부득이하게 2개 이상을 수정해야 하는 경우가 있다

    1) 팀 표준이 DB가 다른 글로벌 트랜잭션을 사용하게 하거나
    2) 기술 제약으로 도메인 이벤트나 미동기 방식을 쓸 수 없거나
    3) 편리를 위한 UI 요구사항

     

    이 때는 애그리거트에서 수정하지 않고

    1) 응용 서비스에서 수정  @Transactional

    2) 도메인 이벤트(동기, 비동기)를 사용

     

     

     

    3. 리포지토리

    리포지토리가 애그리거트의 단위가 되는데,

    각각 별도 테이블을 뜻하는 것이 아니라 루트만 존재한다면 나머지는 객체로 존재한다.

     

    한 마디로 1개의 리포지토리가 애그리거트 전체저장소에 영속화해야한다는 뜻이다.

    Order인 애그리거트 루트를 변경하면, 그에 따른 Orderline, Orderer 3개가 모두 저장된다.

    즉, 리포지토리 메소드는 완전한 애그리거트를 제공하는 것이다. (모두 포함)

    만약 완전한 애그리거트가 아니면, Order에서 orderLine을 조회하면 에러가 발생한다.

    따라서 NoSql도 애그리거트 상태 변화시 모든 변화를 저장해야한다. (데이터 일관성)

     

    애그리거트를 다룰 때 리포지토리는

    1) 조회 findById

    2) 변경 save

    만 필수로 있으면 되며, 검색과 삭제는 필요시 추가하면 된다.

     

    기술 종속성

    JPA, 하이버네이트는 관계형 모델에 객체 도메인 모델을 맞춘 것이다.

    그 떄문에 Value 도메인 모델을 @Entity로 사용해야했다.

    때문에 지금까지는 Entity와 Value를 구분해서 개발하지 않았다.

     

    @Embeddable, @Embedded를 사용해서 이제 구분 가능하다. 

     

     

     

    4. 애그리거트 참조

    관리주체가 애그리거트 루트이기 때문에, 애그리거트 루트를 참조하는 것이 좋은데 (?)

    이때 직접참조, 간접참조 방식이 있다.

     

    직접참조

    직접 참조하는 방식은 편리하다

    JPA가 로딩 기능을 제공하기 때문에 ManyToOne, OnetoOne을 이용하면 된다.

    Public class CheckOut {  // 도서 대출
         private Book book;
    }

    다만 3가지 문제가 발생한다. 문제는

    1) 편한 탐색 오용
    2) 성능 문제
    3) 확장의 어려움

    1) 편한 탐색 오용

    직접 참조 시, 쉽게 변경이 가능하지만, 변경 범위는 자신에 한정한다

    의존 결합도를 높여서 결과적으로 변경을 어렵게 만든다.

     

    2) 성능 문제

    JPA의 즉시로딩(Eager), 지연로딩(Lazy) 등을 사용해서 해결 가능하다

     

    3) 확장

    서로 다른 DBMS를 쓸 경우에는?

    JPA와 같은 단일 기술로는 처리가 불가능하다.

     

    이러한 문제로 Id로 다른 애그리거트를 간접 참조한다.

     

    간접참조

    id를 이용한 간접 참조를 말한다.

    Public class CheckOut { 
         private BookId bookId;
    }

    다른 엔티티를 참조할 때, 객체 레퍼런스로 참조한다.

    이렇게 하면 모든 객체를 참조로 연결할 필요가 없이, 한 애그리거트에 속한 객체만 연결할 수 있다.

    서비스에서 아이디를 이용해서 findByID로 조회하면 되므로  (지연로딩과 동일)

    다른 애그리거트의 수정을 원칙적으로 방지할 수 있다.

     

    즉, 지연 로딩의 3가지 문제가 해결 된다.

    1) 물리적으로 연결X, 구현복잡도를 낮춰서, 의존을 줄이고, 응집도를 높임
    2) 지연, 즉시 로딩을 고민할 필요X
    3) 다른 구현 기술을 사용O
      - 각 도메인을 별도 프로세스로 서비스하면 된다.

    다만, 데이터가 많아서 지연로딩의 n+1문제가 발생하면 전체 속도(성능)이 감소한다.

    이 때는 조회 전용 쿼리를 분리해서

       - 별도 DAO를 분리하여 조인쿼리(세타조인)로 처리 

       - 쿼리가 복잡하다면 Mybatis로 처리

    하면 된다.

    이를 CQRS라고 한다.

     

    cf) 다른 저장소 사용 시, 한번에 조회가 안 되면

     1) 캐시를 적용해서 조회 성능을 높이거나
     2) 조회 전용 저장소를 따로 구성한다.

     

    이렇게 되면 코드가 복잡해지고 시스템 처리량이 늘어날 수 있다.

     

     

     

    5. 집합 연관 시에는?

    1:N, M:N의 연관이 발생하면, 컬렉션을 사용하게 된다.

     

    1:N 연관

    예를 들어 1:N 연관인 도서 카테고리와 책이 있다고 치자.

    public class Category {
         private List<Book> books; 
    }

    이렇게 표현할 수 있지만, Category 입장에서 book을 연관지으면 갯수가 많으면 성능 저하 (전체를 가져올 경우)되므로

    따라서 N:1로 연관을 짓는게 좋다.

     

    public class Book {
         private CategoryId categoryId; 
    }

    또, 전체 조회는 피하고 category, page, size (+ 검색 조건)을 파라미터로 받아서 조회하는 게 좋다

    Spring의 Pageable을 이용하면 내부에서 다 계산해주니 편리하다.

    List<Book> books = booksRepository.findByCategoryId(categoryId, pageable)

    pageable은 page를 0부터 세기 때문에 개인적으로 PageRequest를 따로 구현하여 사용한다.

     

    M:N 연관

    예를 들어, 책이 여러 카테고리에 속하면 m:n 연관이 된다. 

    이런경우 양방향이라도 단방향으로 구현하는 게 좋다.

    public class Book {
         private List<CategoryId> categoryIds; 
    }

    RDBMS에서는 테이블은 'Book' - 'Book-Category' - 'Category'  세 개로 구성해 맵핑 테이블을 만드는게좋다. 

     

    또 QueryDsl을 이용해서 조인을 해서 한번에 원하는 데이터마 뽑아내는 게 좋다.

     

     

    6. 애그리거트 팩토리

    신규 도서를 등록 할 때, 아래와 같은 요구사항이 있다고 치자

    1. 관리자 권한에 따라 등록 여부 판단

    2. 신규 도서를 등록

     

    여러 기능을 판단해야 할 때는 주로 서비스에서 구현하고는 했는데,

    이때 등록여부를 서비스에서 판단 시, 중요 도메인로직이 노출되고 만다. 

    관리자 권한에 따른 등록 여부도 도메인에서 구현해야한다.

     

    이런 경우에는 3가지 방식이 있다.

    1. 별도 도메인 서비스 생성
    2. 팩토리 클래스 사용
    3. Manager Aggregate를 따로 만들어서, 새로운 애그리거트에 기능을 이전

     

    새로운 애그리거트로 이전

    Manager Aggregate에 상품등록 기능을 이전하는게 좀 이상하다고 생각했는데,

    어차피 관리자만 상품 등록을 할 수 있으니까 이렇게 해도 문제가 없을 것 같다.

    public class ManagerService { 
    
        public Book createNewBook { 
              Manager manager = findById(managerId); 
              Book newBook = manager.createBook;
              return newBook;
    	}
    }

    createBook 안에서 1) 관리자 권한을 확인하고 2) 상품을 등록하면 된다.

    즉, 필요한 데이터와 중요 도메인 로직을 함께 구현하는 것이다

     

    이렇게 하면 도메인 로직만 변경하면 되서, 서비스에도 영향을 받지 않고 응집도가 올라간다.

     

     

    ### 출처 :
    'DDD START! 도메인 주도 설계 구현과 핵심 개념 익히기(최범균)'

    http://www.yes24.com/Product/Goods/27750871

    300x250
    • 네이버 블로그 공유
    • 네이버 밴드 공유
    • 페이스북 공유
    • 카카오스토리 공유