Layered Architecture
Layered Architecture는 각 구성 요소들이 '관심사의 분리(Separation of Concerns)'를 달성하기 위해 '책임'을 가진 계층으로 분리한 아키텍처이다.
하나의 계층에 관심사가 여러 개가 존재한다면 해당 계층의 응집도가 떨어지고 결합도가 높아진다. 관심사의 분리를 통해 재사용성과 유지보수성을 높일 수 있다.
구성
- Presentation
- 사용자의 요청을 받고 해석해서 Application 영역에 전달하거나 처리 결과를 받아서 사용자가 이해할 수 있는 형식으로 변환해서 응답
- Application
- 사용자에게 제공해야 할 기능을 구현
- 이 기능들을 구현하기 위해 도메인 영역의 도메인 모델을 사용한다.
- 즉, 도메인 모델에 로직 수행을 위임한다.
- Domain
- 핵심 로직 담당(Business Logic)
- Infrastructure
- 구현 기술에 대해 다루는 영역
- ex) RDMS 연동, 메시징 큐에 메시지 전송 또는 수신 기능
계층 구조를 엄격하게 적용하면 상위 계층은 바로 아래의 계층에만 의존을 가져야 하지만 구조의 편리함을 위해 계층 구조를 유연하게 적용 가능하다.
하지만 이런 상위 계층에 존재하는 것들이 Infrastructure(인프라스트럭처)에 종속된다는 점입니다.
public class CalculateDiscountService {
private DroolsRuleEngine ruleEngine;
public Money calculateDiscount(OrderLine orderLines, String customerId) {
Customer customer = findCustomer(customerId);
// Drools 의존
MutableMoney money = new MutableMoney(0);
List<?> facts = Arrays.asList(customer, money);
facts.addAll(orderLines);
ruleEngine.evalute("discountCalculation", facts);
return money.toImmutableMoney();
}
...
}
위의 서비스 클래스는 2가지 문제점이 있다.
1. 테스트의 어려움
- CalculateDiscountService를 테스트하려면 RuleEngine이 완벽하게 동작해야 한다.
- RuleEngine 클래스와 관련 설정 파일을 모두 만든 이후에 비로소 CalculateDiscountServic가 올바르게 동작하는지 확인 가능하다.
2. 기능 확장의 어려움
- Domain 영역(Drools)이 Infrastructure 영역에 완전히 의존하고 있다.
- 이런 상황에서 Drools가 아닌 다른 구현 기술을 사용하려면 코드의 많은 부분을 수정해야 한다.
즉, 도메인 계층이 직접적으로 인프라스트럭처 레이어에 의존적이게 되면 '테스트의 어려움'과 '기능 확장의 어려움' 두 가지 문제가 발생 합니다. 이러한 문제를 해소하기 위해 DIP를 적용해야 한다.
DIP(Dependency Invention 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는 더 이상 Drools기술 구현에 의존하지 않는다. RuleDiscounter 인터페이스에만 의존할 뿐이다.
기능을 직접 구현한 DroolsRuleDiscounter는 저수준 모듈에 속하게 되고 CalculateDiscountService, RuleDiscounter는 고수준 모듈에 속하게 된다.
이처럼 저수준 모듈이 고수준 모듈에 의존하게 되는데 이를 DIP(Dependency Inversion Principle, 의존 역전 원칙)이라고 부른다.
출처
https://hesh1232.tistory.com/153 [꾸준히 공부하는 개발 노트:티스토리]