표현, 응용, 도메인, 인프라스트럭처는 아키텍처를 설계할 때 출현하는 전형적인 네 가지 영역이다.
표현 영역
응용 영역
도메인 영역
인프라 스트럭처
표현 -> 응용 -> 도메인 -> 인프라스트럭처
계층 구조는 그 특성상 상위 계층에서 하위 계층으로의 의존만 존재하고, 하위 계층은 상위 계층에 의존하지 않는다.
응용 영역과 도메인 영역은 DB나 외부 시스템 연동을 위해 인프라스트락처의 기능을 사용해야 한다. 그래서 이런 계층 구조를 사용하는 것은 직관적으로 이해하기 쉽다.
하지만 표현, 응용, 도메인 계층이 보다 상세한 구현 기술을 다루는 인프라 스트럭처 계층에 종속되는 것은 문제가 생길 수 있는 일이다.
할인 정책을 결정하는 RateDiscountPolicy를 이용한 OrderService 클래스를 작성하는 상황이다.
public class OrderService{
private RateDiscountPolicy rateDiscountPolicy;
public OrderService(){
this.rateDiscountPolicy = new RateDiscountPolicy;
}
public Money calculateDiscount(...){
return rateDiscountPolicy.calculate(price, member);
}
}
OrderService가 잘 작동한다고 할 때, 위 코드의 문제는 무엇일까?
1. 테스트하기 어렵다.
2. 구현을 변경하기 어렵다.
억까로 느껴진다면 먼저 DIP를 적용한 코드를 보고, SOLID 원칙을 공부하길 추천한다.
프로그래머는 추상화에 의존해야하고, 구체화에 의존하면 안된다는 원칙이다.
좋은 객체 지향 설계를 위한 SOLID 원칙 중 하나다.
책에서는 고수준, 저수준 모듈이라는 말을 많이 사용한다.
나는 이 말이 딱히 와닿지 않아 고수준 => 추상화된, 저수준 => 구체화된으로 바꾸어 말하겠다.
앞선 코드에서 추상화된 모듈이 구체화된 모듈을 사용하면 구현 변경과 테스트가 어렵다는 문제점이 생긴다는 것을 알았다.
이 코드에 DIP 원칙을 적용해 추상화에 의존하도록 해서 문제를 해결해보자.
추상화에 의존하도록 하는 비밀은 인터페이스에 있다.
public interface DiscountPolicy{
...
}
public class OrderService{
private DiscountPolicy discountPolicy;
public OrderService(DiscountPolicy discountPolicy){
this.discountPolicy = discountPolicy;
}
public Money calculateDiscount(...){
return discountPolicy.calculate(price, member);
}
}
이렇게 하면 이제 OrderService에는 구체화된 클래스인 RateDiscountPolicy에 의존하는 코드가 없다.
실제 RateDiscountPolicy 객체는 외부에서 생성자를 통해서 전달받는다.
Discountpolicy discountPolicy = new RateDiscountPolicy();
OrderService orderService = new OrderService(discountPolicy);
이 것을 DI (Dependency Injection, 의존 관계 주입)이라고 한다.
이런 방법을 사용하면 구현 기술을 변경하더라도 OrderService를 변경할 필요가 없고,
RateDiscountPolicy를 완성하지 않아도 테스트를 할 수 있다.
DB 테이블의 엔티티 vs 도메인 모델의 엔티티
가장 큰 차이점: 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 함께 제공
도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화해서 데이터가 임의로 변경되는 것을 막는다.
도메인 모델이 복잡해지면 개발자가 전체 구조를 큰 틀에서 관리하기 힘들 수 있다.
도메인 모델에서 전체 구조를 이해하는 데 도움을 주는 것이 애그리거트다.
애그리거트는 군집에 속한 객체를 관리하는 루트 엔티티를 갖는다.
루트 엔티티는 애그리거트에 속해 있는 엔티티와 밸류 객체를 이용해서 애그리거트가 구현해야 할 기능을 제공한다.
엔티티나 밸류가 요구사항에서 도출되는 도메인 모델이라면 리포지터리는 구현을 위한 도메인 모델이다.