kongkong.note

[도메인 주도 개발 시작하기] 3. 애그리거트 본문

DDD

[도메인 주도 개발 시작하기] 3. 애그리거트

hyokong 2025. 12. 21. 17:04

3.1 애그리거트

  • 복잡한 도메인을 이해하고 관리하기 쉬운 단위로 만들기 위해 상위 수준에서 모델을 조망하는 방법
  • 관련된 객체를 하나의 군으로 묶는 것
  • 상위 수준에서 도메인 모델 간의 관계 쉽게 파악 가능
  • 한 애그리거트에 속한 구성요소는 대부분 함께 생성, 제거(다른 애그리거트는 관리 x)
  • 애그리거트 설정하는 기본 : 도메인 규칙, 요구사항 → ex) product, review 는 서로 다른 애그리거트
  • 한 애그리거트는 일반적으로 1개의 엔티티 객체를 갖는다.

 

3.2 애그리거트 루트

  • 한 애그리거트에 속한 모든 객체가 일관된 상태를 유지하기 위한 관리 주체 → ex) 주문 애그리거트 - Order
  • 애그리거트 일관성이 깨지지 않도록 도메인 기능을 구현
  • 애그리거트 외부에서 객체를 직접 변경하면 안됨. 불변 중요
     → setter 메서드를 public 으로 생성 x
     → value 타입은 불변으로 생성

 

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

  • 리포지터리는 애그리거트 단위로 존재
  • 어떤 기술(ORM...)을 이용해서 리포지터리를 구현하느냐에 따라 애그리거트의 구현도 영향을 받는다.
    ex) JAP → class {~~} 에서 value "Type" 으로 사용하던 밸류타입을 @Entity로 구현
  • 리포지터리 조회 메서드는 완전한 애그리거트 제공 필요(한 애그리거트의 모든 구성요소 포함 필요)
  • 애그리거트의 상태가 변경되면 모든 변경을 원자적으로 저장소에 반영 필요

 

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

애그리거트 간 참조가 가능하고, 다른 애그리거트의 루트를 참조

다른 애그리거트 참조는 ID를 이용해서 간접 참조 해야 한다. 같은 애그리거트는 직접 참조 가능.

# 직접 참조
public class Orderer {
  private Member member;
}
  • 객체 그래프 : Orderer - Member - Address - City - ... → 다른 애그리거트까지 모두 연결
# 간접 참조
public class Orderer {
  private MmeberId memberId;
}
  • 객체 그래프 : Orderer - Member  한 애그리거트에 속한 객체만 참조

3.4.1 ID를 이용한 참조와 조회 성능

ID 참조를 하게 되면 N+1 조회 발생 : 조회 전용 쿼리 사용 → 데이터 조회를 위한 별도 DAO 사용 (5장) 

 

3.5 애그리거트 간 집합 연관

1:N 관계

  • ex) 카테고리 : 상품(단일 카테고리)
  • Set 컬렉션 이용
  • N 쪽에서 실제 구현. N:1로 특정 카테고리에 속한 상품 목록을 구현.

N:M 관계

  • ex) 카테고리 : 상품(다수 카테고리)
  • 요구사항에 기반한 단일 N:M 연관만 구현 : 조인 테이블 사용(4장)

 

3.6 애그리거트를 팩토리로 사용

case) 상점이 신고당했는지 여부에 따라 물건 등록

AS-IS : 중요한 도메인 로직 처리가 응용 서비스에 노출

public class RegisterProductService {
    public ProductId registerNewProduce (NewProductRequest request) {
        Store store = storeRepository.findById(request.storeId);
        checkNull(store);
        if (store.isBlocked()){
            throw new StoreBlockedException("Store is blocked");
        }
        ProductId id = productRepository.nextId();
        Product product = new Product(id, store.getId());
        productRepository.save(product);
        return id;
    }
}

TO-BE : Store 애그리거트에 팩토리 메서드 구현
  "Store 상태"에 따른 Product 생성이므로, 이미 본인 상태를 알고 있는 "Store 애그리거트"에 팩토리 메서드 구현

public class Store {
    public Product createProduct(ProductId newProductId){
        if(isBlocked){
            throw new StoreBlockedException("Store is blocked");
        }
        return new Product(newProductId, this.id);
    }
}
public class RegisterProductService {
    public ProductId registerNewProduce (NewProductRequest request) {
        Store store = storeRepository.findById(request.storeId);
        checkNull(store);
        if (store.isBlocked()){
            throw new StoreBlockedException("Store is blocked");
        }
        ProductId id = productRepository.nextId();
        Product product = store.createProduct(id);
        productRepository.save(product);
        return id;
    }
}

팩토리 메서드 : 외부에서 new 직접 사용을 방지하고 객체 생성 로직 숨겨둔 메서드