3장 애그리거트

박준수·2024년 8월 22일
0

DDD 스터디

목록 보기
3/11
post-thumbnail
post-custom-banner

애그리거트

  • 도메인 객체 모델이 복잡해지면 개별 구성요소 위주로 모델을 이해하게 되고 전반적인 구조나 큰 수준에서 도메인 간의 관계를 파악하기 어려워진다.

→ 코드를 변경하고 확장하는 것이 어려워진다.

해결 방법

  • 관련된 객체를 하나의 군으로 묶어 준다.
  • 수많은 애그리거트로 묶어서 바라보면 상위 수준에서 도메인 모델 간의 관계를 파악할 수 있다.

특징

  • 애그리거트 단위로 일관성을 관리하기 때문에, 애그리거트는 복잡한 도메인을 단산한 구조로 만들어준다. → 도메인 기능을 확장하고 변경하는 데 필요한 노력도 줄어든다.
  • 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 갖는다.
  • 보통 다수의 애그리거트가 한 개의 엔티티 객체만 갖는 경우만 많았으며 두 개 이상의 엔티티로 구성되는 애그리거트는 드물었음.

애그리거트 루트

  • 애그리거트에 속한 모든 객체가 일관된 상태를 유지하려면 애그리거트 전체를 관리할 주체가 필요함
  • 애그리거트 루트를 통해서만 도메인 로직을 구현하게 만들려면 도메인 모델에 대한 적용점
    • 단순히 필드를 변경하는 set 메서드를 공개(public) 범위로 만들지 않는다.
    • 밸류 타입은 불변으로 구현한다.
      • 불변으로 구현할 수 없다면 접근 제한자 범위를 통해 애그리게이트 외부에서 상태 변경 기능을 실행하는 것을 방지한다.

트랜잭션 범위

  • 작을수록 좋다.
  • 하나의 트랜잭션에서 세 개의 테이블을 수정하면 잠금 대상이 많아짐 → 동시에 처리할 수 있는 트랜잭션 개수가 줄어듬 → 전체적인 성능을 떨어뜨림
  • 따라서 동일하게 한 트랜잭션에서는 한 개의 애그리거트만 수정해야 함
  • 애그리거트는 최대한 서로 독립적이어야 하는데 한 애그리거트가 다른 애그리거트의 기능에 의존하기 시작하면 애그리거트 간 결합도가 높아진다.
  • 부득이하게 한 트랜잭션으로 두 개 이상의 애그리거트를 수정해야 한다면 애그리거트에서 다른 애그리거트를 직접 수정하지 말고 응용 서비스에서 두 애그리거트를 수정하도록 구현한다.

도메인 이벤트를 사용하면 한 트랜잭션에서 한 개의 애그리거트를 수정하면서도 동기나 비동기로 다른 애그리거트의 상태를 변경하는 코드를 작성할 수 있다.

리포지터리와 애그리거트

  • 객체의 영속성을 처리하는 리포지터리는 애그리거트 단위로 존재

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

public class Order {
	private Orderer orderer;
	...
	
}

public class Orderer {
	private Member member;
	private String name;
	
	...
}

public class Member {

	 ...
}
  • 필드를 이용한 애그리거트 참조를 사용하면 다른 애그리거트의 데이터를 쉽게 조회할 수 있음

문제점

  • 편한 탐색 오용
    • 애그리거트 내부에서 다른 애그리거트 객체에 접근할 수 있으면 구현의 편리함 때문에 다른 애그리거트를 수정하고자 하는 유혹에 빠지기 쉽다.
  • 성능에 대한 고민
    • 즉시 로딩과 지연 로딩 방식 선택에 대한 고민
  • 확장 어려움
    • 하위 도메인마다 다른 종류의 DBMS를 사용하는 경우도 있음 → 애그리거트 루트를 참조하기 위해 JPA와 같은 단일 기술을 사용할 수 없음
public class Order {
	private Orderer orderer;
	...
	
}

public class Orderer {
	private MemberId memberId;
	private String name;
	
	...
}

public class Member {
	private MemberId id;
	 ...
}
  • 애그리거트 간 물리적인 연결을 제거하기 때문에 모델의 복잡도를 낮춰준다.
  • 애그리거트 간의 의존을 제거하므로 응집도를 높여주는 효과도 있다.
  • ID 참조 방식을 사용하면서 N+1 조회와 같은 문제가 발생하지 않도록 하려면 조회 전용 쿼리를 사용하면 된다.

애그리거트 간 집합 연관

  • 1 대 N 관계 보단 N 대 1로 연관을 짖자.
  • M 대 N 관계라면 중간 테이블을 생성하자

애그리거트를 팩토리로 사용하기

public class RegisterProductService {
	
	public ProductId registerNewProduct(NewProductRequest req) {
		Store stroe = storeRepository.findById(req.getStoreId());
		checkNull(store);
		if(store.isBlocked()) {
			throw new StoreBlockedException();
		}
		ProductId id = productRepository.nextId();
		Product product = new Product(id, store.getId(), .. );
		productRepository.save(Product);
		return id;
}
  • Store가 Product를 생성할 수 있는지를 판단하고 Product를 생성하는 것은 논리적으로 하나의 도메인 기능인데 이 도메인 기능을 응용 서비스에서 구현하고 있다.
public class Store {

	public Product createProduct(ProductId newProductId , .. ) {
		if(isBlocked()) throw new StoreBlockedException();
		return new Product(newProductId, getId(), ..);
		}
	}
}
// Product 생성 가능 여부를 확인하는 도메인 로직을 변경해도 도메인 영역의 Store만 변경하면 됨
// 응용 서비스는 영향을 받지 않고 도메인의 응집도도 높아짐
  • 애그리거트가 갖고 있는 데이터를 이용해서 다른 애그리거트를 생성해야 한다면 애그리거트에 팩토리 메서드를 구현하는 것을 고려해 보자.
  • Store 애그리거트가 Product 애그리거트를 생성할 때 많은 정보를 알아야 한다면 Store 애그리거트에서 Product 애그리거트를 직접 생성하지 않고 다른 팩토리에 위임하는 방법도 있다.
profile
방구석개발자
post-custom-banner

0개의 댓글