기술 서적 정리/DDD Start

[도메인 주도 개발 시작하기] 2. 아키텍처 개요

wampy 2022. 9. 5.

2.1 네 개의 영역

표현 영역 (Controller)

  • HTTP 요청을 응용 영역이 필요로 하는 형식으로 변환해서 응용 영역에 전달하고 응용 영역의 응답을 HTTP 응답으로 변환하여 전송한다.
  • HTTP 요청 파라미터로 전송한 데이터 → (응용 서비스가 요구하는 형식의 객체타입으로 변환하여 전달) → 응용 서비스 리턴 → (JSON 형식으로 변환하여 HTTP 응답으로 전송

응용 영역 (Service)

  • 표현 영역을 통해 사용자의 요청을 전달받는 응용 영역은 시스템이 사용자에게 제공해야 할 기능을 구현
    • e.g 주문 등록, 주문 취소, 상품 상세 조회
  • 기능을 구현하기 위해 도메인 영역의 도메인 모델을 사용
  • 주문 취소 기능을 제공하는 응용 서비스 → 주문 도메인 모델을 사용해서 기능을 구현
    • 로직을 직접 수행하기보다는 도메인 모델에 로직 수행을 위임

도메인 영역 (Domain)

  • 도메인 모델을 구현
  • 도메인의 핵심 로직을 구현
    • e.g 배송지 변경, 결제 완료, 주문 총액 계산

인프라스트럭처 영역 (Repository)

  • 구현 기술에 대한 것을 다룸
  • RDBMS 연동을 처리하고, 메시징 큐에 메시지를 전송하거나 수신하는 기능을 구현하고, 몽고DB나 레디스와 의 데이터 연동 ....
  • 논리적인 개념을 표현하기보다는 실제 구현을 다룸

2.2 계층 구조 아키텍처

표현 → 응용 → 도메인 → 인프라스트럭처

  • 인프라스트럭처의 기술에 의존하면 '테스트 어려움'과 '기능 확장의 어려움'이라는 두 가지 문제가 발생

2.3 DIP

가격 할인 계산 CalculateDiscountService [고수준]

고객 정보를 구한다 → RDMBS에서 JPA로 구한다 [저수준]

룰을 이용해서 할인 금액을 구한다 → Drools로 룰을 적용한다 [저수준]

  • 고수준 모듈 : 의미 있는 단일 기능을 제공하는 모듈
    • 고수준 모듈의 기능을 구현하려면 여러 하위 기능이 필요
  • 저수준 모듈 : 하위 기능을 실제로 구현

고수준 모듈이 제대로 동작하려면 저수준 모듈을 사용해야 한다.

→ 구현 변경과 테스트가 어려워짐

→ 저수준 모듈이 고수준 모듈에 의존하도록 바꾸자! (D.I.P)

추상화한 인터페이스 활용

public interface RuleDiscounter {
    Money applyRules(Customer customer, List<OrderLine> orderLines);
}

고객 정보와 구매 정보에 룰을 적용해서 할인 금액을 구한다

public class CalculateDiscountService {
    private RuleDiscounter ruleDiscounter;

    public CalculateDiscountService(RuleDiscounter ruleDiscounter) {
        this.ruleDiscounter = ruleDiscounter;
    }

    // 가격 할인 계산
    public Money calculateDiscount(List<OrderLine> orderLines, String customerId) {
        // 고객 정보를 구한다
        Customer customer = findCustomer(customerId);

        // 룰을 이용해서 할인 금액을 구한다
        return ruleDiscounter.applyRules(customer, orderLines);
    }
}
  • Drools에 의존하는 코드가 없이 RuleDiscounter가 룰을 적용한다는 사실만 알 뿐임
  • 룰 적용을 구현한 클래스는 RuleDiscounter 인터페이스를 상속받아 구현
public class DroolsRuleDiscounter implements RuleDiscounter{
    private KieContainer kieContainer;

    public DroolsRuleEngine() {
        KieServices ks = KieService.Factory.get();
        kContainer = ks.getKieClasspathContainer();
    }

    @Override
    public Money applyRules(Customer customer, List<OrderLine> orderLines) {
        KieSession kSession = kContainer.newKieSession("discountSession");
        try {

        } finally {

        }
        return money.toImmutableMoney();
    }
}

DIP 주의사항

  • 단순히 인터페이스와 구현 클래스를 분리하는 정도가 아님
  • DIP의 핵심은 고수ㅜㄴ 모듈이 저수준 모듈에 의존하지 안호돍 하기 위함

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

  • 엔티티
    • 고유의 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다.
    • 주문, 회원, 상품과 같이 도메인의 고유한 개념을 표현한다.
    • 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공한다.
  • 밸류
    • 고유의 식별자를 갖지 않는 객체로 주로 개념적으로 하나인 값을 표현할 때 사용된다.
    • 배송지 주소를 표현하기 위한 주소나 구매 금액을 위한 금액과 같은 타입이 밸류 타입이다.
    • 앤티티의 속성으로 사용할 뿐만 아니라 다른 밸류 타입의 속성으로도 사용할 수 있다.
  • 애그리거트
    • 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다.
    • 주문과 관련된 Order 엔티티, OrderLine 밸류, Orderer 밸류 객체를 주문 애그리거트로 묶을 수 있따.
  • 리포지터리
    • 도메인 모델의 영속성을 처리한다.
  • 도메인 서비스
    • 특정 엔티티에 속하지 않은 도메인 로직을 제공한다.

2.4.1 엔티티와 밸류

실제 도메인 모델의 엔티티와 DB 관계형 모델의 엔티티는 다르다

  • 도메인 모델의 엔티티
    • 데이터와 함께 도메인 기능을 함께 제공한다는 점이다
    • 단순히 데이터를 담고 있는 데이터 구조라기보다는 데이터와 함께 기능을 제공하는 객체
    • 도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화해서 데이터가 임의로 변경 되는 것을 막음
    • 두 개 이상의 데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현할 수 있음

2.4.2 애그리거트

  • 관련 객체를 하나로 묶은 군집
  • e.g 주문 -> 주문, 배송지 정보, 주문자, 주문 목록, 총 결제 금액
  • 애그리거트는 군집에 속한 객체를 관리하는 루트 엔티티를 가짐
    • 루트 엔티티는 애그리거트에 속해 있는 엔티티와 밸류 객체를 이용해서 애그리거트가 구현해야 할 기능을 제공
    • 애그리거트를 사용하는 코드는 애그리거트 루트가 제공하는 기능을 실행하고 애그리거트 루트를 통해서 간접적으로 애그리거트 내의 다른 엔티티나 밸류 객체에 접근
    • e. g 애그리거트 루트 Order

2.4.3 리포지터리

  • 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의

댓글