https://product.kyobobook.co.kr/detail/S000001810495

 

도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지 | 최범균 - 교보문고

도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지 | 가장 쉽게 배우는 도메인 주도 설계 입문서!이 책은 도메인 주도 설계(DDD)를 처음 배우는 개발자를 위한 책이다. 실제 업무에 DDD를

product.kyobobook.co.kr

Chapter 1_도메인 모델 시작하기: https://inhyeok-blog.tistory.com/55
Chapter 2_아키텍처 개요 : https://inhyeok-blog.tistory.com/56
Chapter 3_애그리거트 : https://inhyeok-blog.tistory.com/57
Chapter 4_리포지터리와 모델 구현 : 
Chapter 5_스프링 데이터 JPA를 이용한 조회 기능 : 
Chapter 6_응용 서비스와 표현 영역 : 
Chapter 7_도메인 서비스 : 
Chapter 8_애그리거트 트랜잭션 관리 :
Chapter 9_도메인 모델과 바운디드 컨텍스트 : 
Chapter 10_이벤트 : 
Chapter 11_CQRS : 


도메인 객체 모델이 복잡해 지면 전반적인 구조나 큰 수준에서 도메인 간의 관계를 파악하기 어려워 진다. 이럴 때 상위 수준에서 모델을 이해해야 전체적인 구조를 유지한 채로 코드를 추가/변경할 수 있다. 이때 사용하는 개념이 애그리거트이다.

아래의 사진을 보면 복잡하게 엔티티와 벨류가 흩어져 있다.

이를 애그리거트로 묶으면 도메인을 단순하게 만들어서 이해하고 관리하기 쉬워진다.

한 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 갖는다. 예를들어 주문 애그리커트를 만들려면 Order, OrderLine, Orderer와 같은 관련 객체를 함께 생성한다. 또한 ShippingInfo는 만들었지만 Order는 만들지 않는 일은 없다.

 

애그리거트는 명확한 경계를 가진다. 각각의 애그리거트는 자기 자신을 관리할 뿐 다른 애그리거트를 관리하지 않는다. 그런데 직접 구조를 만들다 보면 경계를 설정하는 것이 항상 어렵다. 저자는 경계를 만드는 규칙을 설명한다. 도메인 규칙에 따라 함께 생성되는 요소는 같은 애그리거트일 확률이 높고, 사용자 요구사항에 따라 함께 변경되는 요소도 같은 애그리거트일 확률이 높다.

A가 B를 갖는다로 설계되는 요구사항이 있어도 같은 애그리거트가 아닐 수 있다. 좋은 예시가 Product와 Review이다. 이 둘은 한 애그리거트 처럼 보이지만, 함께 생성되거나 변경되지 않는다. 게다가 Product를 변경하는건 상품 담당자지만, Review를 생성하고 변경하는건 고객이다.

처음 도메인 모델을 만들기 시작하면 큰 애그리거트로 보인 것들이 많지만, 경험이 쌓이면 애그리거트는 점점 작아진다. 작가는 애그리거드가 하나의 엔티티 객체만 갖는 경우가 많았으며, 두개 이상의 엔티티로 구성되는 경우는 드물다고 언급한다.

애그리거트 루트

애그리거트 루트는 애그리거트에 속한 모든 객체가 일관된 상태를 유지할 수 있도록 애그리거트 전체를 관리한다.

  • 애그리거트 루트의 핵심은 일관성이다. 이를 위해서 애그리거트 루트는 도메인 기능을 구현한다. 프로그래밍을 하면서 경험적으로 대부분 알고 있겠지만, 상태를 변경하는 지점을 한군데로 모았을 때 우리는 일관성을 더 잘 유지할 수 있다. 이는 애그리거트 루트에서 일관성을 철저하게 지켰을 때에 유효한 명제이다. 애그리거트 루트에서 일관성을 유지하려면 2가지를 습관적으로 적용해야 한다.
    • set method는 private으로 만들 것
    • 벨류는 불변타입으로 만들 것
  • 애그리거트 루트의 기능 구현 애그리거트 루트가 구성요소의 상태만 참조해서 모든 로직을 수행하는 것은 아니다. 일부의 기능 실행은 위임하기도 한다.
  • 트랜젝션 범위는 작을 수록 좋다. 그리고, 한 트랜젝션에서는 한 애그리거트만 수정해야 한다. 애그리거트는 서로 독립적인데, 같은 트랜잭션으로 묶이게 되면 결합도가 높아진다. 이는 향후 수정비용의 증가로 이어지므로, 애그리거트는 다른 애그리거트의 상태를 변경하지 말아야 한다. 부득이하게 두 애그리거트를 수정해야 한다면 애그리거트에서 직접 수정하지 말고 어플리케이션에서 수정하자.

리포지토리와 애그리거트

애그리거트는 개념적으로 하나이므로, 애그리거트에 속하는 엔티티와 벨류는 원자적으로 저장되거나 롤백되어야 한다. RDBMS는 트렌젝션을 통해 이를 보장할 수 있고, 몽고DB와 같은 저장소는 한 페이지에 전에 애그리거트를 저장하는 방식으로 일관성을 유지할 수 있을 것이다.

 

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

애그리거트간의 통신은 종종 발생한다. 책에서는 다른 애그리거트를 참조할 때 ID를 통해서 조회하는 방식을 제안한다. 

지금까지는 특정 엔티티가 다른 엔티티를 참조할 때 @OneToMany와같은 매핑 함수를 활용했다. 하지만 이는 다음과 같은 문제를 야기할 수 있다.

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

이중 작가는 가장 큰 문제로 편한 탐색 오용을 지적한다. 쉬운 탐색은 한 애그리거트가 다른 애그리거트의 상태를 쉽게 변경하도록 한다. 이는 앞서 언급한 트렌젝션의 범위를 넓힌다는 문제도 있고, 엔티티의 생명주기를 다른 엔티티에게 위임하는 꼴이 될 수도 있다. 또한 직접참조는 애그리거트간의 의존 결합도를 높여서 결과적으로 애그리거트의 변경을 어렵게 만든다.

두 번째 문제는 애그리거트 직접 참조는 성능과 관련된 여러 고민을 해야 한다는 것이다. Lazy로딩과 Eager로딩을 고민하는 등, 프로그램을 복잡하게 한다.

마지막은 확장의 어려움이 있다. 초기에는 단일 서버, DBMS를 활용하지만 트레픽이 많아지만 도메인별로 시스템을 분리(Micro Service)하게 된다. 그리고 이때 서로다른 DBMS를 활용하게 되면 더이상 JPA와 같은 단일 기술을 사용할 수 없음을 의미한다. 

 

이러한 문제를 회피하기 위해 작가는 ID를 이용한 애그리거트 참조를 제안한 것이다. 하지만 이 글을 읽는 대부분이 ID참조를 선택하면 성능을 포기해야 하는것이 아니냐는 의문이 들 수 있다.

ID참조의 성능문제에 대한 해결책으로 작가는 조회 전용 쿼리를 사용하는 것을 추천한다. 이는 따로 DAO객체를 만들어서 조회 메서드에서 조인을 이용해 한 번의 쿼리로 필요한 데이터를 로딩하는 방법을 말한다. 

만약 애그리거트마다 서로 다른 저장소를 사용하면 한 번의 쿼리로 관련 애그리거트를 조회할 수 없다. 이럴때는 캐시나 조회 전용 저장소를 따로 구상해야 한다. 이 방법은 코드가 복잡해지는 단점이 있지만, 시스템의 처리량을 높일 수 있다는 장점이 있다. 

 

애그리거트 간 집합 연관

애그리거트간에 M:N, 1:N, N:1등의 관계를 가질 수 있다. 이때 요구사항을 잘 확인해서 연간관계를 설정해야 한다. 우리는 의존성을 최소화하고 복잡하고 어려운 코드와 성능 문제를 최대한 회피해야 한다. 이를 위해서 필요 없는 의존을 없애야 한다. 예컨데, 카테고리와 상품 목록을 M:N으로 모델링 했다고 해도 카테고리별 상품을 조회할 때 해당 상품이 가지고있는 모든 카테고리를 보여주지 않는다. 모든 카테고리를 보게 되는 것은 상품의 세부 화면이다. 즉, 개념적으로는 상품과 카테고리의 양방향 M:N 연관이 존재하지만 실제 구현에서는 상품에서 카테고리로의 단방향 M:N 연관만 적용하면 되는 것이다.

 

*@ElementCollection이라는 어노테이션으로 JPA에서 Value Collection을 구현할 수 있다. 예컨데 Product에서 Set<CategoryId> categoryIds라는 변수를 통해 1:N 관계를 구현하려면 @ElementCollection을 사용할 수 있는 것이다.

 

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

애그리거트가 갖고있는 데이터를 이용해서 다른 애그리거트를 생성해야 한다면 애그리거트에 팩토리 메서드를 구현하를 것을 고려해보자. 예를 들어 신고당한 Store는 Product을 등록할 수 없다는 규칙이 생겼다고 가정해보자. 그럼 응용 서비스에서 해당 Store가 유효한지 확인하고 Product를 만들 것이다. 하지만 이 방식은 중요한 도메인 로직처리가 응용 서비스에 노출되게 된다. 이를 해결하기 위해서 Store객체에 createProduct 메서드를 추가해서 유효성 검사를 하게 하면 응집력이 높아진다.

+ Recent posts