‘표현’, ‘응용’, ‘도메인’, ‘인프라스트럭처’는 아키텍처를 설계할 때 출현하는 전형적인 네 가지 영역이다.
public class OrderService {
@Transactional
public void cancelOrder(String orderId) {
Order order = getOrderById(orderId);
order.cancel();
}
// ...
}
아래 코드는 가격 할인을 담당하는 응용 계층이 인프라 계층인 RuleEngine
클래스를 의존하는 예시이다.
public class CalculateDiscountServie {
private final DroolsRuleEngine ruleEngine;
// ...
public Money calculateDiscount(OrderLine orderLines, String customerId) {
Customer customer = findCustomer(customerId);
MutableMoney money = new MutableMoney(0);
List<?> facts = Arrays.asList(customer, money);
facts.addAll(orderLines);
ruleEngine.evalute("discountCalculation", facts);
return money.toImmutableMoney();
}
CalculateDiscountService
를 테스트하려면 RuleEngine
이 완벽하게 동작해야 하기에 테스트가 번거롭다.caculateDiscount()
메서드 안의 코드는 RuleEngine
클래스를 사용하기위해 필요한 코드들이기 때문에 다른 구현 기술을 사용하려면 이 코드들을 다 고쳐야 한다.가격 할인 도메인 예제를 계속해서 살펴보자.
CalculateDiscountService
public interface RuldDiscounter {
Money applyRules(Customer customer, List<OrderLine> orderLines);
}
public class CalculateDiscountServie {
private final RuleDiscounter ruleDiscounter;
public CalculateDiscountService(RuleDiscounter ruleDiscounter) {
this.ruleDiscounter = ruleDiscounter;
}
public Money calculateDiscount(OrderLine orderLines, String customerId) {
Customer customer = findCustomer(customerId);
return ruleDiscounter.applyRules(customer, orderLines);
}
CaculateDiscountService
가 Drools이라는 구현을 알지 못하게 RuleDiscounter
라는 인터페이스를 의존하도록 변경했다.
RuleDiscounter
의 구현 객체는 생성자를 통해 외부에서 주입 받는다.RuleDiscounter
를 구현한 대역 객체를 사용해 고수준 모듈의 테스트를 쉽게 진행할 수 있다.무조건 DIP를 적용할 필요는 없다. 구현 기술에 따라 구현 기술에 의존적인 코드를 도메인 일부에 포함하는 게 효과적일 때도 있고, 추상화 대상이 잘 떠오르지 않을 때도 있다. 이런 경우 DIP의 이점을 얻는 수준에서 적용 범위를 검토해 보자.
도메인 영역을 구성하는 요소는 다음과 같다.
요소 | 설멍 |
---|---|
엔티티 | 고유의 식별자를 갖는 객체로 자신의 라이프 사이클을 갖고 도메인의 고유한 개념을 표현한다. 도메인 모델의 데이터를 포함하며 관련된 기능을 함께 제공한다. |
밸류 | 식별자를 갖지 않는 객체로 주로 개념적인 하나의 값을 표현할 때 사용된다. 엔티티의 속성 또는 다른 밸류 타입의 속성으로도 사용할 수 있다. |
애그리거트 | 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다. |
리포지터리 | 도메인 모델의 영속성을 처리한다. |
도메인 서비스 | 특정 엔티티에 속하지 않은 도메인 로직을 제공한다. 도메인 로직이 여러 엔티티와 밸류를 필요로 하면 도메인 서비스에서 로직을 구현한다. |
DB 테이블의 엔티티와 도메인 모델의 엔티티는 같은 것이 아니다.
public class Order {
private OrderNo number;
private Orderer orderer;
private ShippingInfo shippingInfo;
// ...
public void changeShippingInfo(ShippingInfo newInfo) {
// ...
}
// ...
}
XXXRepository
는 도메인 객체를 영속화하는 고수준 모듈이다.
XXXRepository
를 구현한 클래스를 둔다.@Transactional
을 사용하지 않는다면 복잡한 스프링 설정을 사용해야 한다.