본문 바로가기

programming/DDD

[DDD] chapter2

반응형

Chapter 2

2.1 네 개의 영역

표현 영역

  • HTTP 요청을 응용 영역이 필요로 하는 형식으로 변환하여 응용 영역에 전달.
  • 응용 영역의 응답을 HTTP로 변환하여 전달.

응용 영역

  • 사용자에게 제공해야할 기능을 구현.
public class CancelOrderService {
    @Transactional
    public void cancelOrder(String orderId){
        Order order = findOrderById(orderId);
        if(order == null) throw new OrderNotFoundException(orderId);
        order.cancel();
    }
}
  • 위의 예시처럼 응용 서비스는 로직을 직접 수행하는 것이 아니라 도메인 모델에 로직 수행을 위임한다.

도메인 영역

  • 도메인 모델을 구현한다.
  • 도메인의 핵심 로직을 구현한다.

인프라스트럭처 영역

  • 구현 기술에 대한 기능을 개발한다.
  • RDBMS 연동 처리한다.

2.2 계층 구조 아키텍처



  • 계층 구조는 상위 계층에서 하위 계층으로의 의존만 존재한다.

계층 종속시 발생하는 문제점

import java.util.Arrays;

public class CalculateDiscountService {
    private DroolsRuleEngine ruleEngine;
    
    public CalculateDiscountService(){
        ruleEngine = new DroolsRuleEngine();
    }
    
    public Money calculateDiscount(List<OrderLine> orderLines, String customerId){
        Customer customer = findCustomer(customerId);
        
        MutableMoney money = new MutableMoney(0);
        List<?> facts = Arrays.asList(customer, money);
        facts.addAll(orderLines);
        ruleEngine.evalute("discountCalculation", facts);
        return money.toImmutableMoney();
    }
}
  • CalculateDiscountService만 테스트 하기 어렵다.
  • 구현 방식 변경이 어렵다.
    • 특정 Entity나 Type Value와 관련된 기능 수정이 매우 어렵다.

DIP

고수준 모듈과 저수준 모듈

  • 고수준 모듈: 의미 있는 단일 기능을 제공하는 모듈
    • CalculateDiscountService는 가격 할인 계산 이라는 기능을 구현한다.
  • 저수준 모듈: 고수준 모듈을 위한 하위 기능을 구현한다.
    • JPA를 사용하여 고객 정보를 읽어오는 모듈, Drools로 룰을 실행하는 모듈.
  • 고수준 모듈이 저수준 모듈을 사용하면 앞서 말한 두개의 문제가 발생한다.

인터페이스의 활용

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);
    }
}
  • CalculateDiscountService에는 Drools에 의존하는 코드가 없다.
    • 의존하지 않음
public class DroolsRuleDiscounter implements RuleDiscounter{
    private KieContainer kContainer;
    public DroolsRuleDiscounter(){
        KieService ks = KieService.Factory.get();
        kContainer = ks.getKieClasspathContainer();
    }
    
    @Override
    public Money applyRules(Customer customer, List<OrderLine> orderLines){
        
    }
}
  • 이렇게 저수준 모듈로 고수준 모듈로 구현한다.
  • 이렇게 되면 위와 같은 아래와 같은 구조를 가진다.


  • 위처럼 고수준 모듈을 인터페이스로 정의하고, 저수준 모듈을 해당 인터페이스의 구현체로 개발하면 저수준 모듈이 고수준 모듈에 의존하게 된다.
  • DIP는 해당 현상처럼 의존성이 반전되어 Dependency Inversion Principle 이라 불리는 것이다.
  • 사용할 저수준 객체만을 변경하면 수정사항은 객체 생성 코드밖에 되지 않는다.
  • 테스트 코드가 쉽다.

DIP의 주의 사항

  • DIP를 인터페이스와 구현 클래스로 분리하는 정도로 받아들이면 안된다. (뜨끔❗)
    • DIP의 핵심은 고수준 모듈이 저수준 모듈에 의존하지 않도록 하기 위함이다.
  • 관점을 무조건 고수준 모듈에 두어야 한다.
    • ex) CalculateDiscountService의 입장에서 할인 금액을 구하기 위한 엔진 기술은 생각할 필요 없다. 단지 어떤 값이 들어가고 어떤 값이 도출 되는 지가 중요하다.

DIP와 아키텍쳐



  • DIP를 적용한 인프라 구조에서는 인프라 스트럭처 구현체만 변경한다면 고수준 인터페이스는 변경할 필요가 없다.

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

도메인 영역의 구성요소

  • Entity: 고유의 식별자를 가지는 객체, 고유의 도메인 개념을 표현한다.
  • Value: 고유의 식별자를 갖지 않는 객체, 개념적으로 하나인 값을 표현할 때 사용된다. 배송지 주소를 표현하기 위한 주소나,금액이 이에 해당한다.
  • Aggregate: 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다.
    • Order entity, OrderLine value, Order value 객체들을 하나의 ‘주문’ aggregate로 묶을 수 있다.
  • Domain service: 특정 엔티티에 속하지 않는 도메인 로직.
    • 할인 금액 계산이라는 기능은 상품, 쿠폰, 회원 등급, 구매 금액 등 다양한 조건을 이용해서 구현하게 되는데, 이렇게 도메인 로직이 여러 Entity와 Value를 필요로 하면 도메인 서비스에서 해당 로직을 구현한다.ㅇ

Entity와 Value

Domain Entity와 DB Entity의 차이점

  • 도메인은 메소드까지 제공한다.
  • 도메인은 Value type을 사용해 종속 개념을 명확하게 표현할 수 있다.

애그리거트

  • 애그리거트: 관련 객체를 하나로 묶은 군집
    • 도메인 모델이 복잡해지면 전체적인 구조를 파악하기 힘들기에 이 부분을 해결하는데 유용하다.


Root Entity

  • 군집에 속한 객체를 관리하는 엔티티.
  • 애그리거트에 속해 있는 엔티티와 밸류 객체를 이용해 기능을 제공한다.
  • 각 애그리거트의 모든 기능은 Root Entity를 통해 제공된다. 따라서 모든 도메인의 기능은 Root Entity의 로직을 거쳐야한다. (이게 진짜 대박이네)

레포지터리 (이부분은 제가 새로 알게 된 부분만을 정리했습니당!)



  • Repository의 역할만을 생각했을 때 Infra Structure에 속할 것이라고 생각했다.
    • 그러나 해당 Repository를 고수준의 인터페이스로 정의하고 구현체만을 Infra Structure로 취급하는 부분이 매우 인상 깊었다.
    • 위의 구조가 좋았던 이유는 말 그대로 Infra Structure만 수정되면 해당 Repository를 사용하는 객체의 큰 변화가 없다는 장점이 있다.

요청 처리 흐름



  • 해외 자료에서도 응용계층에서 Infrastructure를 쓰는 구조는 무조건 나타나는군…
  • Spring boot의 controller, service, dao, repository의 개념과 비슷하다.

인프라스트럭처 개요

  • 도메인 객체의 영속성 처리, 트랜잭션, SMTP 클라이언트, Rest 클라이언트 등의 기능을 구현한다.
  • 도메인 영역과 인프라 영역에서 정의한 인터페이스를 인프라스트럭처 영역에서 구현하는 것이 좋다.
  • 응용영역에서 인프라스트럭처에 대한 의존성을 무조건 없애야할 필요는 없다.
    • Transactional 같은 경우 Spring에서 제공하는 어노테이션을 사용하는 것이 더 편리하다.
    • @Entity와 같은 어노테이션을 도메인 모델 클래스에 사용하는 것이 더 편리하다.
      • 그럼 어노테이션이 인프라스트럭처 영역인가…?
    • DIP의 장점을 해치지 않는 범위에서 응용 영역과 도메인 영역에서 구현 기술에 대한 의존을 가져가는 것도 나쁘지 않다. (상황에 따라)

모듈 구성



  • 도메인 별로 UI 계층까지 따로 만든다는 것이 조금 놀랐다.
  • 생각해보면 controller단, service단 이런식으로 나누던 것을 각 기능별로 나눈 것이라고 생각되기도 한다.
  • 애그리거트 기준으로 패키지를 구현하고 Repository와 같은 인터페이스도 domain 폴더 안에 넣는다는 것 또한 인상 깊었다.
반응형

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

[DDD] chapter 6  (0) 2024.02.10
[DDD] chapter 5  (1) 2024.02.07
[DDD] chapter 4  (0) 2024.02.03
[DDD] chapter 3  (0) 2024.01.18
[DDD] chapter 1  (0) 2024.01.13