: 여기서 CalculateDiscountService는 고수준 모듈이다. 고수준 모듈은 의미 있는 단일 기능을 제공하는 모듈로 가격 할인 계산이라는 기능을 구현, 고수준 모듈의 기능을 구현하려면 여러 하위 기능이 필요한데, 이 두 기능이 하위 기능이다. 저수준 모듈은 하위 기능을 실제로 구현한 것으로 인프라스트럭처에 해당한다. 고수준 모듈이 제대로 동작하려면 저수준 모듈을 사용해야 하는데, 고수준 모듈이 저수준 모듈을 사용하면 앞서 계층 구조 아키텍처에서 언급했던 두 가지 문제, 구현 변경과 테스트가 어렵다는 문제가 발생한다.
: DIP는 이 문제를 해결하기 위해 저수준 모듈이 고수준 모듈에 의존하도록 바꿔줌, 고수준 모듈을 구현하려면 저수준 모듈을 사용해야 하는데, 반대로 저수준 모듈이 고수준 모듈에 의존하도록 하려면 어떻게 해야할까, 바로 인터페이스를 사용하면 된다.
: 고수준 모듈 입장에서는 룰을 무엇으로 구현했는지는 중요하지 않다. 단지 기능을 제공할 수만 있다면 충분하다. 그러므로 이를 추상화하여 인터페이스를 생성한다.
export interface RuleDiscounter {
Money applyRules(customer: Customer, orderLines: OrderLine[])
}
TypeScript
복사
: CalculateDiscountService가 RuleDiscounter를 사용하도록 변경하면 더이상 Drools에 의존하는 코드가 존재하지 않는다. 단지 ‘룰을 이용한 할인 금액 계산’을 추상화한 RuleDiscounter 인터페이스에 의존할 뿐이다. ‘룰을 이용한 할인 금액 계산’은 고수준 모듈의 개념이므로 RuleDiscounter는 고수준 모듈에 속한다. DroolsRuleDiscounter는 고수준의 하위 기능인 RuleDiscounter를 구현한 것이므로 저수준 모듈에 속한다.
: DIP를 적용하면 저수준 모듈이 고수준 모듈에 의존하게 된다. 고수준 모듈이 저수준 모듈을 이용하려면 고수준 모듈이 저수준 모듈에 의존해야하는데, 반대로 저수준 모듈이 고수준 모듈에 의존한다고 해서 이를 DIP, 의존성 역전 원칙이라고 한다. DIP를 적용하면 앞의 다른 영역이 인프라스트럭처에 의존할 때 발생했던 두 가지 문제를 해결할 수 있다.
구현 기술 교체 문제
: 고수준 모듈은 더 이상 저수준 모듈에 의존하지 않고 구현을 추상화한 인터페이스에 의존하게 된다. 실제 사용할 저수준 구현 객체는 단순히 의존성 주입을 사용해서 전달받을 수 있다. 구현 기술을 변경하더라도 CalculateDiscountService를 변경할 필요 없이 저수준 구현 객체를 생성하는 코드만 변경하면 된다.
테스트
: CustomerDiscountService는 CustomerRepository, RuleDiscounter에 의존하므로 테스트 시에 기존 방법의 경우, 저수준 모듈이 만들어지기 전까지 테스트가 불가능했다. 하지만 인터페이스로 변경되고서는 대용 객체(Mock)를 사용해서 테스트를 진행할 수 있다
1. DIP 주의사항
: DIP를 잘못 이해한 경우, 단순히 인터페이스와 구현 클래스를 분리하는 정도로 받아들일 수 있다. DIP의 핵심은 고수준 모듈이 저수준 모듈에 의존하지 않도록 하기 위함
: DIP를 적용하고자할 때, 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출해야한다. CalculateDiscountService 입장에서 봤을 때 할인 금액을 구하기 위해 룰 엔진을 사용하는 지, 직접 연산하는지는 중요하지 않음, 단지 규칙에 따라 할인 금액을 계산한다는 것이 중요, 즉 할인 금액 계산을 추상화한 인터페이스는 저수준 모듈이 아닌 고수준 모듈에 위치
2. DIP와 아키텍처
: 인프라스트럭처 영역은 구현 기술을 다루는 저수준 모듈이고 응용 영역과 도메인 영역은 고수준 모듈, 인프라스트럭처 계층이 가장 하단에 위치하는 계층형 구조와 달리 아키텍처에 DIP를 적용하면 인프라스트럭처 영역이 응용 영역과 도메인 영역에 의존하게 된다.
: 인프라스트럭처에 위치한 클래스가 도메인이나 응용 영역에 정의한 인터페이스를 상속받아 구현하는 구조가 되므로 도메인과 응용 영역에 대한 영향을 주지 않거나 최소화하면서 구현 기술을 변경하는 것이 가능해진다.
: 인프라스터럭처 영역의 EmailNotifier 클래스는 응용 영역의 Notifier를 상속받고 있음, 주문 시 통지 방식에 SMS를 추가하라는 요구사항이 들어왔을 때, 응용 영역의 OrderService를 변경할 필요 없이, 두 통지 방식을 함께 제공하는 Notifier 구현 클래스를 인프라스트럭처에 구현하고 의존하게 하면 됨
DIP를 항상 적용할 필요는 없다. 구현 기술에 따라 완벽한 DIP를 적용하기 보다는 구현 기술에 의존적인 코드를 도메인에 일부 포함하는 게 효과적일 때도 있다. 또는 추상화 대상이 잘 떠오르지 않을 때도
있다. 이럴 때는 무조건 DIP를 적용하려고 시도하지 말고 DIP의 이점을 얻는 수준에서 적용 범위를 검토해 보자.