책책책

[DDD START!] 웹 애플리케이션 아키텍처 설계 - DIP 적용

Deveun 2021. 8. 12. 02:23

아키텍처는 크게 4개의 영역으로 구분되는 계층구조로 이루어졌다.

 

o 표현계층: 사용자의 요청을 응용 영역에 전달, 응용 영역의 처리 결과를 다시 사용자에게 보여주는 역할. Json <--변환--> Object)

o 응용계층: 사용자에게 제공할 기능 구현. 직접 구현보다는 도메인 모델에 로직 수행을 위임.

o 도메인계층도메인의 핵심 로직을 구현. ex. 주문 도메인의 '결제 완료', '주문 총액 계산', '배송지 변경' 같은 로직

o 인프라스트럭쳐계층: 상세한 구현 기술을 다룸. ex. DB연동, HTTP Client, Kafka ...

 

위 아키텍처는 상위 -> 하위계층으로만 의존을 하도록 되어있는데, 이 경우에 두가지 문제점이 발생한다.

(1) 테스트의 어려움 - 상위 계층을 테스트하기 위해서는 하위 계층이 구현되어있어야한다.

(2) 기능 확장의 어려움 - 하위 계층에 수정사항이 생기면 이를 의존하고 있는 상위계층까지 수정해야한다.

 

 

의존역전원칙 (DIP: Dependency  Inversion Principle)

: 위의 문제를 해결하기 위해서, 저수준 모듈이 고수준모듈에 의존하도록 바꾸는 것을 말한다.

고수준모듈(Service)에서는 어떤 구체적인 기술들로 저수준 모듈을 구현했는지 그닥 중요하지 않다. 오히려 저수준 모듈이 수정되더라도 영향을 받지 않도록 구현부분에 의존하지 않아야한다. ==> 추상화 인터페이스

 

인터페이스를 사용하면 아까 말한 두가지의 문제점은 다음과 같이 해결된다.

(1) 테스트의 어려움 -> Mock, Stub같은 대용객체를 생성하여 고수준모듈에서 사용 

(2) 기능 확장의 어려움 -> 저수준모듈을 고수준모듈에 의존주입(DI)

 

 

예를 들어보자.

"가격 할인 계산" 이라는 기능을 위해서는 고객 정보를 가져오고, 고객에 맞는 룰을 적용해야 한다는 하위 기능을 구현해야한다. 이 때, 구현하고자 하는 하위 기능이 고수준 모듈, 실제 하위 기능을 구현한 부분이 저수준 모듈이다.

추상화한 인터페이스를 통해 저수준모듈을 주입받음으로써 고수준모듈은 더이상 구현기술에 의존하지 않는다.


[DIP 적용 구현코드]

 

- CalculateDiscountService.java

public class CalculateDiscountService {

    private CustomerRepository customerRepo;
    private RuleDiscounter ruleDiscounter;

    // 서비스에서 사용할 저수준모듈(로직구현클래스) DI
    // 추상화 Interface사용으로 의존 X
    public CalculateDiscountService(CustomerRepository customerRepo, RuleDiscounter ruleDiscounter) {
        this.customerRepo = customerRepo;
        this.ruleDiscounter = ruleDiscounter;
    }

    // 가격할인계산 <= 고객정보를 구한다. 룰을 이용해서 할인 금액을 구한다.
    public Money calculateDiscount(List<OrderLine> orderLines, String customerId) {
        Customer customer = customerRepo.findCustomer(customerId);
        return ruleDiscounter.applyRules(customer, orderLines);
    }
}

 

- RuleDiscounter.java

: 서비스의 하위기능 수행을 위한 인터페이스

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

 

- DroolsDiscounter.java / ABCRuleDiscounter.java

: 인터페이스를 구현한 부분. Service에서 목적에 맞게 주입하여 사용.

public class DroolsDiscounter implements RuleDiscounter {
    // 생성자부분 생략

    @Override
    public Money applyRules(Customer customer, List<OrderLine> orderLine) {

        Money discountedMoney = new Money();
        // Drools로 가격할인 계산 구현부분.
        // 로직 생략..
        return discountedMoney;
    }
}
public class ABCRuleDiscounter implements RuleDiscounter {
    // 생성자부분 생략

    @Override
    public Money applyRules(Customer customer, List<OrderLine> orderLine) {

        Money discountedMoney = new Money();
        // ABC 자체로직으로 가격할인 계산 구현부분.
        // 로직 생략..
        return discountedMoney;
    }
}

(CustomerRepository은 RuleDiscounter 부분과 비슷하여 생략한다.)

 

[패키지 구조]

 

 

[참고서적] DDD START! 도메인 주도설계 구현과 핵심 개념 익히기, 최범균 저자