728x90

3.1 애그리거트

  • 백 개 이상의 테이블을 한 장의 ERD에 모두 표시하면 개별 테이블 간의 관계를 파악하느라 큰 틀에서 데이터구조를 이해하는 데 어려움을 겪게 되는 것처럼, 도메인 객체 모델이 복잡해지면 개별 구성요소 위주로 모델을 이해하게 되고 전반적인 구조나 큰 수준에서 도메인 간의 관계를 파악하기 어려워진다.

  • 애그리거트는 모델을 이해하는데 도움을 줄 뿐만 아니라 일관성을 관리하는 기준도 된다.
  • 애그리거트는 관련된 모델을 하나로 모았기 때문에 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 갖는다.
    • 예시
      • 주문 애그리거트
        • Order(root), Orderer, OrderLine, ShippingInfo
      • 위 애그리거트 상황에서, Order를 생성할 경우 ShippingInfo를 필수로 생성되어야 한다.
  • 애그리거트는 경계를 갖는다. 애그리거트는 독립된 객체 군이며 각 애그리거트는 자기 자신을 관리할 뿐 다른 애그리거트를 관리하지 않는다.
  • ’A가 B를 갖는다’로 설계할 수 있는 요구사항이더라도, 하나의 애그리거트로 묶으면 안되는 경우가 존재한다.
    • 예시) 상품과 리뷰의 관계
      • 상품과 리뷰의 경우 리뷰가 변경되더라도 상품이 변경되지 않아도 되며, 반대또한 마찬가지 이므로 독립적인 관계이다.

3.2 애그리거트 루트

  • 애그리거트에 속한 모든 객체가 일관된 상태를 유지하려면 애그리거트 전체를 관리할 주체가 필요한데, 이 책임을 지는 주체가 바로 애그리거트 루트 엔티티이다.

일관된 상태란?

  • 총 금액인 totalAmounts를 갖고 있는 Order 엔티티
  • 개별 구매 상품의 개수인 quantity와 금액인 price를 갖고 있는 OrderLine 밸류

위 상황인 경우 구매할 상품의 개수를 변경하게 되면 OrderLine의 quantity를 변경하고 더불어 Order의 totalAmounts도 변경해야한다. 이러한 상태를 일관된 상태 라고 한다.

3.2.1 도메인 규칙과 일관성

  • 불필요한 중복을 피하고 애그리거트 루트를 통해서만 도메인 로직을 구현하게 만들려면 도메인 모델에 대해 다음의 두 가지를 습관적으로 적용해야 한다.
    • 단순히 필드를 변경하는 set메서드를 공개(public) 범위로 만들지 않는다.
    • 밸류 타입은 불변으로 구현한다.
public class Order {
	private ShippingInfo shippingInfo;
	
	public void changeShippingInfo(ShippingInfo newShippingInfo) {
		verifyNotYetShipped();
		setShippingInfo(newShippingInfo);
	}
	
	// set 메서드는 접근 허용 범위가 private이다.
	private void setShippingInfo(ShippingInfo newShippingInfo) {
		this.shippingInfo = newShippingInfo;
		
		// 불변이므로 아래와같은 코드를 사용할 수 없게 된다.
		// this.shippingInfo.setAddress(newShippingInfo.getAddress());
	}
}

3.2.2 애그리거트 루트의 기능 구현

  • 애그리거트 루트는 애그리거트 내부의 다른 객체를 조합해서 기능을 완성한다.
  • 애그리거트 루트가 구성요소의 상태만 참조하는 것이 아닌 기능 실행을 위임 하기도 한다.

3.2.3 트랜잭션 범위

  • 애그리거트 내부에서 다른 애그리거트의 상태를 변경하는 기능을 실행하면 안된다 이것은 애그리거트가 자신의 책임 범위를 넘어 다른 애그리거트의 상태까지 관리하는 꼴이 된다. 애그리거트는 최대한 독립적이어야 하는데 한 애그리거트가 다른 애그리거트의 기능에 의존하기 시작하면 애그리거트 간 결합도가 높아지며 결합도가 높아질수록 향후 수정 비용이 증가 한다.
  • 한 트랜잭션에서 한 개의 애그리거트를 변경하는 것을 권장하지만, 다음 경우에는 한 트랜잭션에서 두 개 이상의 애그리거트를 변경하는 것을 고려할 수 있다.
    • 팀 표준 : 팀이나 조직의 표준에 따라 서비스 의 기능을 한 트랜잭션으로 실행해야하는 경우
    • 기술 제약 : 기술적으로 이벤트 방식을 도입할 수 없는 경우 한 트랜잭션에서 다수의 애그리거트를 수정해서 일관성을 처리
    • UI 구현의 편리 : 운영자의 편리함을 위해

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

  • 애그리거트는 개념상 완전한 한 개의 도메인 모델을 표현 하므로 객체의 영속성을 처리하는 리포지터리는 애그리거트 단위로 존재한다.

하나의 리포지터리로 애그리거트 단위를 컨트롤하려면 어떻게 해야할까??

  • FetchType을 Lazy로 관계를 설정? (효율성)
  • QueryDsl과 같은 라이브러리로 필요한 필드만 조회?

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

  • 애그리거트 간의 참조는 필드를 통해 쉽게 구현할 수 있다. (그림 3.6 참고)
    • 필드를 이용한 애그리거트 참조는 다음 문제를 야기할 수 있다
      • 편한 탐색 오용
        • 한 애그리거트 내부에서 다른 애그리거트 객체에 접근할 수 있으면, 다른 애그리거트의 상태를 쉽게 변경할 수 있게 된다.
      • 성능에 대한 고민
        • 객체의 지연로딩과 즉시로딩에 대한 고민
      • 확장 어려움
        • 각 도메인마다 서로 다른 DB를 사용하게된다면, 단일 기술을 사용할 수 없다.

  • 애그리거트 간 ID를 통해 간접 참조를 하여 구현할 수 있다. (그림 3.7 참고)
    • ID를 이용한 애그리거트 간접 참조는, 위 필드를 이용한 애그리거트 참고의 단점을 일부분 해소할 수 있다.
      • 편함 탐색 오용 → 애그리거트의 경계를 명확히 하여 애그리거트 간 물리적인 연결을 제거하기 때문에 모델의 복잡도를 낮춰준다.
      • 성능에 대한 고민 → 응용 서비스에서 필요한 애그리거트를 로딩하므로 애그리거트 수준에서 지연 로딩을 하는것과 동일한 결과를 만든다.
      • 확장의 어려움 → 애그리거트별로 다른 구현 기술을 사용하는것이 가능하다.

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

  • ID를 이용한 애그리거트 참조는 지연 로딩과 같은 효과를 만드는데 지연 로딩과 관련된 대표적인 문제인 N+1 문제가 발생한다.
    • JPQL, QueryDSL 등 라이브러리를 통한 한방쿼리를 통해 N + 1 문제를 해결할 수 있다
  • 애그리거트마다 서로 다른 저장소를 사용하면 한 번의 쿼리로 관련 애그리거트를 조회할 수 없다. 이때는 조회 성능을 높이기 위해 캐시를 적용하거나 조회 전용 저장소를 따로 구성한다.

3.5 애그리거트 간 집합 연관

  • 1-N
    • 개념적으로는 애그리거트 간에 1-N 연관이 있더라도 성능문제 때문에 애그리거트 간의 1-N 연관을 실제 구현에 반영하지 않는다.
    //public class Category {
    //	private Set<Product> products; // 1-N 관계
    //}
    
    public class Product {
    	private CategoryId ctegoryId;
    }
  • N-M
    • N-M 연관을 구현하려면 조인 테이블을 사용한다.

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

  • 애그리거트가 갖고 있는 데이터를 이용해서 다른 애그리거트를 생성해야 한다면 애그리거트에 팩토리 메서드를 구현하는것을 고려해야 한다.
  • Store 애그리거트가 Product 애그리거트를 생성할 때 많은 정보를 알아야 한다면 Store 애그리거트에서 Product 애그리거트를 직접 생성하지 않고 다른 팩토리에 위임하는 방법도 있다.
public class Store { 
	public Product createProduct(ProductId productId, ProductInfo pi) {
    	if (isBlocked()) {
        	throw new StoreBlockedExeption(); 
        } return ProductFactory.create(newProductId.getId(), pi); 
    } 
 }
반응형

'Study > DDD' 카테고리의 다른 글

Chapter 2 아키텍처 개요  (0) 2024.05.27
DDD 용어 정리  (0) 2024.05.17
728x90

2.1 네 개의 영역

  • 표현 영역
    • Controller 부분
    • 사용자와 상호 작용하는 영역
    • HTTP 요청 ↔ 응용 영역이 서로 통신할 수 있도록 요청과 응답을 변환
  • 응용 영역
    • Service 부분
    • 시스템이 사용자에게 제공해야 할 기능을 구현
    • 기능을 구현하기 위해 도메인 영역의 도메인 모델을 사용
  • 도메인 영역
    • Entity, Value 부분
    • 도메인의 핵심 로직을 구현
      • ex) Order 도메인의 핵심 기능은 ‘배송지 변경’, ‘결제 완료’ … 등이 있으며 해당 로직을 구현한다.
  • 인프라스트럭처 영역
    • Repository, Utils 부분
    • 도메인 로직을 지원하고 구현을 외부 시스템과 연결하는 기술적 구조
      • ex) DB 연결, STMP 메일발송, 메세지큐 … 등

2.2 계층 구조 아키텍처

  • 상위 계층에서 하위 계층으로 의존만 존재하고 하위 계층에서 상위 계층에 의존하지 않는다.

2.3 DIP

  • DIP 란?
    • DIP(Dependency Inversion Principle, 의존성 역전 원칙)은 객체 지향 설계 원칙 중 하나로, 고수준 모듈이 저수준 모듈에 의존해서는 안 되고, 추상화를 통해 저수준 모듈이 고수준 모듈을 의존
  • 장점
    • 저수준 모듈을 교체 하기가 용이
    • 테스트코드를 구현하기에 용이
      • 저수준 모듈의 구현체가 없는 추상화(Interface)만 존재하는 경우에도 테스트 코드 작성이 가능하다 (Mock 이용)

2.3.1 DIP 주의사항

  • 단순히 인터페이스와 구현 클래스를 분리하는 것은 잘못된 사용
  • 결과 구조만 보는것이 아닌 모듈의 관점에서 생각하여 추상화를 만들어야 한다.
    • ex)
      1. cacluateDiscountService → RuleEngine ← DroolsRuleEngine (잘못된 사용)
      2. cacluateDiscountService → RuleDiscounter ← DroolsRuleDiscounter (good)

2.3.2 DIP와 아키텍처

  • DIP를 적용하게 되면, 응용 영역과 도메인 영역의 코드 수정 없이 인프라스트럭처부분만 코드를 추가/수정 및 변경하여 요구사항을 충족시킬 수 있다.

2.4 도메인 영역의 주요 구성요소

구성요소 설명 예시
엔티티(Entity) 고유 식별자를 가지며, 상태와 동작을 포함하는 객체 User, Order, Product
값 객체(Value Object) 고유 식별자가 없으며, 불변 객체로 주로 여러 속성을 하나의 개념으로 묶을 때 사용 Address, Money, DateRange
애그리게잇(Aggregate) 관련된 엔티티와 값 객체의 그룹으로, 일관된 변경을 보장하기 위한 경계를 정의하고 루트 엔티티를 통해 접근 Order (루트: Order, 구성 요소: OrderLine)
리포지토리(Repository) 애그리게잇을 영구 저장소에서 조회하고 저장하는 메커니즘을 제공, 인터페이스와 구현체로 구성 OrderRepository, UserRepository
도메인 서비스(Domain Service) 특정 엔티티에 속하지 않는 도메인 로직을 캡슐화하는 서비스 PricingService, PaymentService

2.4.1 엔티티와 벨류

  • 도메인 모델의 엔티티 vs DB 테이블의 엔티티
    • 도메인 모델의 엔티티는 단순 데이터만 담고 있는 구조가 아닌 데이터와 함께 도메인 기능을 제공
    • 도메인 모델 두개이상의 데이터가 개념적으로 하나인 경우Value 타입을 이용해서 표현할 수 있다.
      • ex) Order → name, email 을 Orderer라는 Value타입 객채를 만들어 관리 가능

2.4.2 애그리거트

  • 지도를 볼 때 매우 상세하게 나온 대축척 지도를 보면 큰 수준에서 어디에 위치하고 있는지 이해하기 어려우므로 큰 수준에서 보여주는 소축척 지도를 함께 봐야 현재 위치를 보다 정확하게 이해할 수 있다. 이와 비슷하게 도메인 모델도 개별 객체뿐만 아니라 상위 수준에서 모델을 볼 수 있어야 전체 모델의 관계와 개별 모델을 이해하는데 도움이 된다.
    • ex) ‘주문’, ‘배송지 정보’, ‘주문자’, ‘주문 목록’, ‘총 결제 금액’의 하위 모델들을 ‘주문’이라는 상위 개념으로 표현할 수 있다.

2.5 요청 처리 흐름

2.6 인프라스트럭처 개요

  • 표현 영역, 응용 영역, 도메인 영역을 지원한다.
  • DIP에서 언급한 내용처럼 인프라스트럭처의 기능을 직접 사용하는것보다 인터페이스를 만들어서 사용하게 되면 시스템을 더 유연하고 테스트하기 쉽게 만들어준다. 하지만 무조건 인프라스트럭처에 대한 의존을 없애게 되면 오히려 더 복잡하고 어려운 코드를 유도할 수 있다.
    • ex) 인프라 스트럭처를 직접 사용하지 않기 위해 @Transaction 어노테이션을 사용하지 않고 개발을 하려고 하면 한줄로 처리할 수 있는 코드를 복잡하고 개발시간만 더 늘어날 뿐이다.

2.7 모듈 구성

  • 도메인이 크면 하위 도메인으로 나누고 각 하위 도메인 마다 별도 패키지를 구성한다.
  • 모듈 구성에 대해서는 정답이 없으며, 한 패키지에 10~15개 미만으로 타입 개수를 유지하려고 노력한다.

반응형

'Study > DDD' 카테고리의 다른 글

Chapter3. 애그리거트  (0) 2024.06.03
DDD 용어 정리  (0) 2024.05.17
728x90

작성 목적

해당 게시글은 DDD관련하여 공부를 하던 중 개인적으로 모르거나 햇갈리는 용어에 대해 리마인드 하기 위해 요약한 내용입니다.

잘못 이해하고 있는 내용이 있으면 댓글로 남겨주시면 감사하겠습니다.

(DDD관련 학습을 진행하는 동안 지속적으로 업데이트 할 예정)

 

용어

  • 도메인 주도 설계(DDD(Domain-Driven Design))
    • 복잡한 소프트웨어를 설계 및 개발하는 과정에서 핵심적인 비지니스 개념과 비지니스 논리 중심으로 설계하는 방법
  • 도메인 전문가
    • 특정 분야나 사업에 대해 깊이있는 지식과 경험을 가진 사람
  • 개념모델 vs구현 모델
    • 개념모델 : "무엇"을 할 것인지에 대한 전략적 설계를 제공
    • 구현모델 : "어떻게" 실행할 것인지에 대한 실용적인 가이드를 제공
  • 유비쿼터스 언어 (보편언어)
    • 팀원 모두가 동일하게 이해하고 표현하는 공통적인 언어

참고 자료

반응형

'Study > DDD' 카테고리의 다른 글

Chapter3. 애그리거트  (0) 2024.06.03
Chapter 2 아키텍처 개요  (0) 2024.05.27

+ Recent posts