애그리거트

dbstmd·2023년 11월 28일
0

DDD

목록 보기
3/8
post-thumbnail

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

애그리거트는 개념상 완전한 한 개의 도메인 모델을 표현하므로 객체의 영속성을 처리하는 리포지토리는 애그리거트 단위로 존재한다. 리포지터리는 애그리거트 전체를 저장소에 영속화해야 한다.

Order와 OrderLine을 물리적으로 각각 별도의 DB테이블에 저장한다고 해서 Order와 OrderLine을 위한 리포지터리를 각각 만들지 않는다. Order가 애그리거트 루트고 OrderLine은 애그리거트에 속하는 구성요소이므로 Order를 위한 리포지터리만 존재한다.

Order 애그리거트를 저장할 때 애그리거트 루트와 매핑되는 테이블뿐만 아니라 애그리거트에 속한 모든 구성요소를 위한 테이블에 데이터를 저장해야 한다.

  • 영속성이란?
    JPA를 이해하는데 가장 중요한 용어이며, 엔티티를 영구 저장하는 환경이라는 뜻을 가지고 있다. DB에 저장을 한다는 것이 아니라 영속성 컨텍스트를 통해서 엔티티를 영속화 한다는 뜻입니다.

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

한 객체가 다른 객체를 참조하는 것처럼 애그리거트도 다른 애그리거트를 참조한다.

JPA를 사용하면 @ManyToOne, @OneToOne과 같은 애노테이션을 이용해서 연관된 객체를 로딩하는 기능을 제공하고 있으므로 필드를 이용해서 다른 애그리거트를 쉽게 참조할 수 있다. 하지만 필드를 이용한 애그리거트 참조는 다음의 문제를 야기할 수 있다.

  • 편한 탐색 오용
  • 성능에 대한 고민
  • 확장 어려움

가장 큰 문제는 편리함을 오용할 수 있다는 것이다. 다른 애그리거트의 상태를 쉽게 변경할 수 있게 된다.

public class Order {
	private Orderer orderer;

	public void changeShippingInfo( ... ) {
		...
        if(useNewShippingAddrAsMemberAddr){
			// Member의 Address를 변경한다. 
			orderer.getCusotmer().changeAddress(newShippingInfo.getAddress());
        }
	}
}

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

두 번째 문제는 애그리거트를 직접 참조하면 성능과 관련된 여러 가지 고민을 해야 한다. JPA를 사용할 경우 참조한 객체를 지연로딩과 즉시로딩의 두 가지 방식으로 로딩할 수 있다.

세 번째 문제는 확장이다. 초기에는 단일 서버에 단일 DBMS로 서비스를 제공하는것이 가능하다. 문제는 사용자가 몰리기 시작하면서 도메인별로 시스템을 분리하기 시작한다. 이 과정에서 하위 도메인마다 서로 다른 DBMS를 사용할 가능성이 높아진다. 이는 더 이상 다른 애그리거츠 루트를 참조하기 위해 JPA와 같은 단일 기술을 사용할 수 없음을 의미한다.

이를 해결할 수 있는 방법이 ID를 이용해서 다른 애그리거트를 참조하는 것이다. 이는 애그리거트의 경계를 명확히 하고 애그리거트 간 물리적인 연결을 제거하기 때문에 모델의 복잡를 낮춰준다.

public class Order {
	private Orderer orderer;

	public void changeShippingInfo( ... ) {
		...
		// Member의 Address를 변경한다. 
		Customer customer = customerRepository.findById(order.getOrderer().getCustomerid());
		customer.changeAddress(newShippingInfo.getAddress());
	}
}

또한 애그리거트별로 다른 구현 기술을 사용하는 것도 가능해진다. 또한, 각 도메인을 별도 프로세스로 서비스하도록 구현할 수도 있다.

3.4.1 참조와 조회 성능

다른 애그리거트를 ID로 참조하면 참조하는 여러 애그리거트를 읽어야 할 때 조회속도가 문제될 수 있다. 이런 경우엔 join을 이용하자.

애그리거트마다 서로 다른 저장소를 사용하는 경우에는 한 번의 쿼리로 관련 애그리거트를 조회할 수 없다. 이때는 조회 성능을 높이기 위해 캐시를 적용하거나 조회 전용 저장소를 따로 구성한다. 코드가 복잡해지는 단점이 있지만 시스템의 처리량을 높일 수 있다는 장점이 있다.

번외

지연 로딩 (Lazy Loading):

  • 특징:
    연관된 엔터티가 실제로 사용될 때까지 데이터베이스에서 로딩을 미루는 전략입니다.
    연관된 엔터티에 접근할 때 비로소 데이터베이스에서 해당 엔터티를 가져옵니다.
  • 장점:
    성능 향상: 모든 연관 엔터티를 미리 로딩하지 않기 때문에 초기에 필요한 데이터만 로딩하여 성능이 향상될 수 있습니다.
  • 단점:
    N+1 문제: 여러 엔터티를 가져올 때 관련된 엔터티를 가져오는 쿼리가 추가로 발생할 수 있습니다.
Copy code
@Entity
public class Team {
    // ...
    @OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
    private List<Member> members;
}

즉시 로딩 (Eager Loading):

  • 특징: 연관된 엔터티가 필요한 순간이 아니더라도 즉시 데이터베이스에서 로딩하는 전략입니다. 한 번에 모든 연관 엔터티를 로딩합니다.
  • 장점:
    N+1 문제가 발생하지 않습니다.
  • 단점:
    모든 연관 엔터티를 로딩하므로 초기에 필요하지 않은 데이터까지 가져올 수 있어 성능에 영향을 줄 수 있습니다.
Copy code
@Entity
public class Team {
    // ...
    @OneToMany(mappedBy = "team", fetch = FetchType.EAGER)
    private List<Member> members;
}

로딩 전략은 @OneToMany 또는 @ManyToOne 어노테이션의 fetch 속성을 통해 설정할 수 있습니다. 기본값은 FetchType.LAZY로, 이는 지연 로딩을 나타냅니다. 필요에 따라 로딩 전략을 적절히 선택하여 성능 최적화를 수행할 수 있습니다.

profile
개인 학습용

0개의 댓글