Lined Notebook

[DDD] Domain Driven Design 아키텍처

by HeshAlgo

DDD

아키텍처를 설계할때 전형적인 영역이 Presentation(표현), Application(응용), Domain(도메인), Infrastructure(인프라스트럭처) 네가지 영역으로 나눠진다.

 

1. 4가지 영역

1) Presentation(표현)

- 사용자의 요청을 받고 해석해서 Application(응용) 영역에 전달하고  Application(응용)영역의 처리 결과를 다시 사용자가 이해할 수 있는 형식으로 변환해서 응답하는 역할

Presentation Flow

 

2) Application(응용)

- 사용자에게 제공해야할 기능을 구현한다.

ex) 주문 등록, 주문 취소, 상품 상세 조회 등...

이 기능들을 구현하기 위해 도메인 영역의 도메인 모델을 사용한다.

즉, 응용 서비스에서 로직을 직접 수행하기 보다는 도메인 모델에 로직 수행을 위임한다.

 

3) Domain(도메인)

- 핵심 로직을 구현한다. (Business Logic)

 

4) Infrastructure(인프라스트럭처)

- 구현 기술에 대해 다루는 영역

 ex) RDBMS 연동, 메시징 큐에 메시지를 전송하거나 수신 하는 기능, Rest API 호출 등...

 

 

 

2. 계층 구조 아키텍처

- 상위 계층에서 하위 계층으로의 의존만 존재해야 한다.

기본 아키텍처(왼쪽), 유연 구조 아키텍처(오른쪽)

계층 구조를 엄격하게 적용하면 상위 계층은 바로 아래의 계층에만 의존을 가져야 하지만 구조의 편리함을 위해 계층 구조를 유연하게 적용 가능

 

하지만 이런 상위 계층에 존재 하는 것들이 Infrastructure(인프라스트럭처)에 종속 된다는 점입니다.

 

할인 금액이란 주제로 1가지 예를 들어보겠습니다.

할인 금액 계산 로직이 복잡해지면 객체 지향으로 로직을 구현하는 것보다 룰 엔진을 사용하는 것이 더 알맞을 때가 있습니다. 

public class CalculateDiscountService {
    private DroolsRuleEngine ruleEngine;
    
    public CalculateDiscountService() {
    	ruleEngine = new DroolsRuleEngine();
    }
    
    public Money calculateDiscount(List<OrderLine> orderLines, String customerId) {
        Customer customer = findCustomer(customerId);
        
        MutalbeMoney money = new MutableMoney(0);
        List<?> facts = Arrays.asList(customer, money);
        
        facts.addAll(orderLines);
        ruleEngine.evaluate("discountCalculation", facts);
        return money.toImmutableMoney();
    }
}

위 코드 문제없이 작동은 하겠지만 2가지의 문제를 안고 있습니다.

 

첫번째로 CalculateDiscountService 테스트의 어려움

- CalculateDiscountService를 테스트 하려면 RuleEngine이 완벽하게 동작해야한다.

- RuleEngine 클래스와 관련 설정 파일을 모두 만든 이후에 비로소 CalculateDiscountServic가 올바르게 동작하는지 확인 가능

 

두번째로 구현 방식을 변경하기 어려움

코드만 보면 CalculateDiscountService가 Drools에 의존하지 않는다고 생각할 수 있지만 아닙니다!!

Drools의 세션이름변경 한다면 CalculateDiscountService의 코드도 함께 변경해야 하는 등

실제로는 Drools라는 Infrastructure(인프라스트럭처) 영역의 기술에 완전히 의존하고 있습니다.

이런 상황에서 Drools가 아닌 다른 구현 기술을 사용하려면 코드의 많은 부분을 고쳐야 합니다.

 

Infrastructure(인프라스트럭처)에 의존하면 '테스트의 어려움'과 '기능 확장의 어려움' 두가지 문제가 발생 합니다.

이러한 문제를 해소하기위해 DIP를 적용해야 합니다.

 

3. DIP (Dependency Inversion Principle)

여기서 CalculateDiscountService가 고수준 모듈에 해당 됩니다.

고수준 모듈의 기능을 구현하려면 여러 하위 기능이 필요합니다. 

예를 들어 JPA를 이용해 고객 정보를 가져오고 그 고객 정보를 가지고 Drools의 룰을 적용해야 하는데 이 2가지의 하위 저수준 모듈이 필요합니다.

 

그런데, 고수준 모듈이 저수준 모듈을 사용하면 앞서 말한 두가지 문제(구조변경과 테스트)가 발생합니다.

DIP는 이 문제를 해결하기 위해 저수준 모듈이 고수준 모듈에 의존하도록 바꿉니다. (추상화 인터페이스)

 

CalculateDiscountService의 입장에서 봤을때 룰 적용을 Drools로 구현 했는지, 자바로 직접 구현했는지 중요하지 않습니다.

단지, '고객 정보와 구매 정보에 룰을 적용해서 할인 금액을 구한다'는 것이 중요하다. 

이를 추상화한 인터페이스는 다음과 같다.

public interface RuleDiscounter {
    public 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.apply(customer, orderLines);
    }
}

보시다 시피 CalculateDiscountService는 Drools에 의존하는 코드를 포함하고 있지 않습니다. 

RuleDiscounter가 룰을 적용한다는 것만 알 뿐입니다.

룰 적용을 구현한 클래스는 RuleDiscounter 인터페이스를 상속받아 구현합니다.

public class DroolsRuleDiscounter implements RuleDiscounter {
    private KieContainer kContainer;
    
    public DroolsRuleDiscounter() {
        KieServieces ks = LieServices.Factory.get();
        kContainer = ks.getKieClasspathContainer();
    }
    
    @Override
    public Money applyRule(Customer customer, List<OrderLine> orderLines) {
        KieSession kSession = kContainer.newKieSession("discountSession");
        try {
            ...코드 생략
            kSession.findAllRules();;
        } finally {
            kSession.dispose();
        }
        return money.toImmutableMoney();
    }
    
}

이렇게 CalculateDiscountService, RuleDiscounter(interface), DroolsRuleDiscounter를 이용해 DIP를 적용할 수 있습니다.

DIP 적용 구조

더이상 CalculateDiscountService는 더 이상 Drools기술 구현에 의존 하지 않는다.

RuleDiscounter 인터페이스에만 의존할 뿐이다.

 

기능을 직접 구현한 DroolsRuleDiscounter는 저수준 모듈에 속하게 되고

CalculateDiscountService, RuleDiscounter는 고수준 모듈에 속하게 된다.

 

이처럼 저수준 모듈이 고수준 모듈에 의존하게 되는데  이를 DIP(Dependency Inversion Principle, 의존 역전 원칙)이라고 부른다.

 

 

'DDD' 카테고리의 다른 글

[DDD] Repository의 조회 기능(JPA 중심)  (0) 2021.04.21
[DDD] Aggregate  (0) 2021.04.11
[DDD] Domain Driven Design 개요  (0) 2021.04.06

블로그의 정보

꾸준히 공부하는 개발 노트

HeshAlgo

활동하기