지난 글에서 가장 기본적인 계층 구조의 아키텍처 구성에 대해 알아보았습니다. 이번 글에서는 그러한 계층 구조 아키텍처를 사용했을 때의 문제점과 이를 해결하기 위한 DIP(Dependency Inversion Principle) 의존성 역전 원칙을 알아보도록 하겠습니다.
예를 들어 어떠한 도메인의 가격 계산 규칙 기능이 있습니다. 할인 금액 계산 로직이 복잡해지게 되면 객체 지향으로 로직을 구현하는 것보다 룰 엔진을 사용하는 것이 더 알맞을 때가 있습니다. 다음의 예시 코드는 Drools라는 룰 엔진을 사용해서 로직을 수행하는 인프라스트럭처 영역의 코드입니다.
public class DroolsRulerEngine { private KieContainer kContainer; public DroolsRuleEngine(){ KieServices ks = KieServices.Factory.get(); kContainer = ks.getKieClasspathContainer(); } public void evaluate(String sessionName, List<?> facts){ KieSession kSession = kContainer.newKieSession(sessionName); try{ facts.forEach(x -> kSession.insert(x)); kSession.fireAllRules(); } finally { kSession.dispose(); } } }
응용 영역에서는 이러한 인프라스트럭처의 코드를 다음과 같이 사용합니다.
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.evaluate("discountCalculator", facts); return money.toImmutableMoney(); } }
이러한 코드는 두 가지듸 문제를 가지고 있습니다.
1. CalculateDiscountService 만 테스트 하기 어렵다
2. 구현 방식을 변경하기 어렵다.
그 이유는 CalculateDiscountService를 테스트하려면 RuleEngine이 완벽하게 동작하는 상태이어야 합니다. RuleEngine 과 관련된 모든 설정파일, Confing 파일을 만든 후에 비로소 올바르게 동작되는지 여부를 확인해 볼 수 있습니다.
두 번째 문제점인 구현 방식을 변경하기 어려운 부분은 'discountCalculation' 과 같이 Drools의 세션 이름을 변경하면 CalculateDiscountService의 코드도 함께 변경해야 합니다. 이처럼 Drools라는 인프라스트럭처에 의존하게 되므로 구현방식을 쉽게 변경할 수 없습니다.
이러한 두 문제를 해결하기 위해 사용하는 방법이 바로 DIP를 적용하는 것입니다.
DIP
DIP는 저수줌 노듈이 고수준 모듈에 의존하도록 바꿔줍니다. 이렇게 하기 위해서는 추상화한 인터페이스를 사용해야 합니다. CalculateDiscountService 처럼 응용서비스 입장에서는 할인 룰을 Drools로 구현했는지, 자바로 구현했는지, 아니면 외부 모듈을 사용했는지는 중요하지 안씁니다. 다만 비즈니스의 요구사항대로 '고객 정보와 구매 정보에 룰을 적용하여 할인 금액을 구한다'는 것이 중요할 뿐입니다. 이를 추상화한 인터페이스로 나타내면 아래와 같습니다.
public interface RuleDiscounter{ public Money applyRules(Customer customer, List<OrderLine> orderLines; }
이를 Service 에 적용해 보겠습니다.
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 에 의존하는 코드를 포함하고 있지 안흣빈다. 다만, RuleDiscounter가 룰을 적용한다는 것만 알 뿐입니다. 실제 RuleDiscounter으이 구현 객체는 생성자를 통해 전달받게 됩니다.
룰 적용을 구현한 클래스는 RuleDiscounter 인터페이스를 상속받아 구현합니다.
public class DroolsRulerEngine implements RuleDiscounter { private KieContainer kContainer; public DroolsRuleEngine(){ KieServices ks = KieServices.Factory.get(); kContainer = ks.getKieClasspathContainer(); } @Override public Money applyRule(Customer customer, List<OrderLine> orderLines){ KieSession kSession = kContainer.newKieSession(sessionName); try{ facts.forEach(x -> kSession.insert(x)); kSession.fireAllRules(); } finally { kSession.dispose(); } return money.toImmutableMoney(); } }

이렇게 DIP를 적용하면 저수준 모듈이 고수준 모듈에 의존하게 됩니다. 고수준 모듈이 저수준 모듈을 사용하려면 고수준 모듈이 저수준 모듈에 의존ㅇ해야 하는데 반대로 저수준 모듈이 고수준 모듈에 의존하다고 해서 이를 DIP(Dependency Inversion Principle) 의존성 역전의 원칙이라고 부릅니다.
의존성 역전의 원칙 구조를 사용하게 되면 응용 영역이 인프라스트럭처 영역에 의존할 때 발생했던 두 가지 문제인 구현 교체가 어렵다와 테스트가 어려운 문제를 해결할 수 있게 됩니다.
'Architecture' 카테고리의 다른 글
MVC 패턴 알아보기 (0) | 2023.11.03 |
---|---|
Layered Architecture 알아보기 (1) | 2023.11.02 |
응용 서비스는 어떻게 구현해야 하는가 (0) | 2023.03.25 |
백엔드 아키텍처 개요 (0) | 2023.01.16 |