public class CancelOrderService {
@Transactional
public void cancelOrder(String orderId) {
Order order = findOrderById(orderId);
if(order == null)
throw new OrderNotFoundException(orderId);
order.cancel();
}
...
}
Order
, OrderLine
, ShippingInfo
와 같은 도메인 모델 구현, 여기서 '배송지 변경', '결제 완료'와 같은 핵심 로직 구현계층 구조의 아키텍처 구성
상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않음
구현의 편리함을 위해 상위 계층이 바로 아래 계층엠만 의존 가지지 않고 계층 구조를 유연하게 적용하기도 함
ex) 도메인과 응용 계층은 룰 엔진과 DB 연동을 위해 인프라스트럭처 모듈에 의존
주의할 점 : 표현, 응용, 도메인 계층이 상세한 구현 기술을 다루는 인프라스트럭처 계층에 종속됨
-> 테스트 어려움, 기능 확장의 어려움 발생
-> 이를 해결하는 것이 DIP
public interface RuleDiscounter {
Money applyRules(Customer customer, List<OrderLine> orderLines);
}
public class CalculateDiscountService {
private RuleDiscounter ruleDiscounter;
public Money calculateDiscount(List<OrderLine> orderLines, String customerId) {
Customer customer = findCusotmer(customerId);
return ruleDiscounter.applyRules(customer, orderLines);
}
...
}
public class DroolsRuleDiscounter implements RuleDiscounter{
private KieContainer kContainer;
public DroolsRuleEngine() {
KieServices ks = KieServices.Factory.get();
kContainer = ks.getKieClasspathContainer();
}
@Override
public Money applyRules(Customer customer, List<OrderLine> orderLines) {
KieSession kSession = kContainer.newKieSession("discountSession");
try {
...
kSession.fireAllRules();
} finally {
kSession.dispose();
}
return money.toImmutableMoney();
}
}
-> DIP를 잘못 적용한 예
요소 | 설명 |
---|---|
엔티티(ENTITY) | 고유의 식별자를 갖는 객체로 자신의 라이프사이클을 가짐. 도메인의 고유한 개념을 표현함. 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공. |
밸류(VALUE) | 고유의 식별자를 갖지 않는 객체로 주로 개념적으로 하나인 도메인 객체의 속성을 표현할 때 사용됨. 엔티티의 속성으로 사용할 뿐만 아니라 다른 밸류 타입의 속성으로도 사용 가능 |
애그리거트(AGGREGATE) | 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것. ex) 주문과 관련된 Order 엔티티, OrderLine 밸류, Orderer 밸류 객체를 '주문' 애그리거트로 묶을 수 있음 |
리포지터리(REPOSITORY) | 도메인 모델의 영속성을 처리함. ex) DBMS 테이블에서 엔티티 객체를 로딩하거나 저장하는 기능 제공 |
도메인 서비스(DOMAIN SERVICE) | 특정 엔티티에 속하지 않은 도메인 로직을 제공. 도메인 로직이 여러 엔티티와 밸류를 필요로 할 경우 도메인 서비스에서 로직을 구현함. |
public class CancelOrderService {
private OrderRepository orderRepository;
public void cancel(OrderNumber orderRepository) {
Order order = orderRepository.findOrderById(number);
if(order == null)
throw new OrderNotFoundException(orderRepository);
order.cancel();
}
...
}
리포지터리 인터페이스는 도메인 객체를 영속화하는 데 필요한 기능을 추상화한 것 -> 고수준 모듈 (도메인 모델 영역에 속함)
리포지터리를 구현한 클래스는 저수준 모듈 (인프라스트럭처 영역에 속함)
응용 서비스와 리포지터리의 관계
save(some)
, findById(id)
+ delete(id)
sk counts()
등도 제공@Transactional
애너테이션 사용)인프라스트럭처는 다른 영역에서 필요로 하는 프레임워크, 구현 기술, 보조 기능 지원
ex) 도메인 객체의 영속성 처리, 트랜잭션, SMTP 클라이언트, REST 클라이언트 등
인프라스트럭처에 대한 의존을 없애면(DIP) 시스템 더 유연하고 테스트 쉬워짐
하지만 DIP의 장점과 더불어 구현의 편리함을 생각하여 구현 기술에 대한 의존을 적절히 갖도록 해야 함
@Transactional
애너테이션 -> 스프링에 대한 의존 없애면 복잡한 스프링 설정 사용해야 함ui -> application -> domain <- infrastructure
도메인이 클 경우 하위 도메인으로 나누고 각 하위 도메인마다 별도 패키지 구성
도메인 모듈은 도메인에 속한 애그리거트를 기준으로 다시 패키지 구성
애그리거트, 모델, 리포지터리는 같은 패키지에 위치
- 모듈 구조에 대해 정해진 규칙은 없음
- 코드를 찾을 때 불편한 정도만 아니면 됨
- 가능하면 한 패키지에 10~15개 미만의 타입 개수 유지.