[DDD] Chap 02 아키텍처 개요

2021. 8. 14. 20:52DDD

이번 장에선 도메인 아키텍처, DIP, 도메인 모델 구성에 대해서 알아보겠습니다.

 

표현 영역

클라이언트의 받은 HTTP 요청을 응용 계층에 필요로 하는 형식으로 파싱해서 전송합니다.

반대로 응용 서비스가 리턴한 결과를 JSON 형식으로 파싱해서 브라우저로 전달합니다.

Controller의 역할을 수행하는 영역입니다.

응용 계층

도메인 모델을 이용해서 사용자에게 제공할 기능을 구현합니다.

단, 로직 수행은 도메인 모델에게 위임합니다.

Service의 역할을 수행합니다.

도메인 계층

도메인의 핵심 로직을 구현한 영역입니다.

인프라 스트럭처 계층

RDBMS 연동, SMTP, Kafka 등 구현 기술의 실제 구현을 다루는 영역입니다.

Repository, DAO의 역할을 수행합니다.

 

각 계층은 상위 계층이 하위 계층에 의존한 구조인데 이러한 구조는 '테스트의 어려움'과 '확장의 어려움'을 야기합니다.

예를 들어서 인프라 스트럭처 계층에서 'Drools'라는 룰 엔진을 사용하고 상위 계층에서 이에 의존하여 Drools에 특화된 로직만 사용하게 된다면 어떻게 될까요?

테스트시 사전에 Drools의 안전한 작동이 보장돼야만 상위 계층의 테스트가 가능합니다.

또한, Drool를 다른 기술로 바꿨을 때 상위 계층의 코드까지 바꿔야 해서 확장시 수정해야할 코드가 많아집니다.

이러한 문제를 해결한 방법이 DIP 입니다.

 


DIP(Dependency Inversion Principle)

의존 역전 원칙은 기존의 고수준 모듈이 저수준 모듈에 의존하는 구조를 반대로 저수준 모듈이 고수준 모듈에 의존하도록 바꾸는 것입니다.

아래의 그림은 DIP를 적용한 의존관계입니다.

RuleDiscounter라는 인터페이스를 두고 저수준 모듈에서 이를 구현하는 객체를 생성하는 방식으로 의존관계를 맺었습니다.

요구사항 변경시 고수준 모듈의 변경없이 저수준 모듈의 구현 클래스만 새로 추가하면 되기 때문에 확장에 용이합니다.

DIP의 핵심은 고수준 모듈이 저수준 모듈에 의존하지 않도록 하는 것이라고 정리하면 되겠습니다.

 


도메인 모델의 구성 요소

  1. 엔티티
  2. 밸류
  3. 애그리거트
  4. Repository
  5. Domain Service

 

1. 엔티티

1장에서 설명한 엔티티 객체입니다.

고유한 식별자를 가진 객체이며 도메인 모델의 데이터와 관련 기능을 함께 제공합니다.

 

2. 밸류

1장에서 설명한 밸류 객체입니다.

고유 식별자가 없고 엔티티 객체의 코드 간결화와 가독성을 위해 사용되는 immutable 객체입니다.

 

3. 애그리거트

도메인이 커질수록 Entity와 Value도 많아지면서 도메인 모델간의 관계가 복잡해집니다.

개발자가 상위 수준에서 시스템 전체를 이해하기위해 사용하는 것이 Aggregate입니다.

객체간의 관계가 아닌 애그리거트간의 관계로 도메인 모델을 더 큰 틀로 관리할 수 있게 됩니다.

간단히 말해서 특정 도메인에 관련된 Entity와 Value들의 묶음입니다.

3장에서 자세히 살펴보겠지만, 애그리거트는 내부 객체들을 관리하는 단 하나의 애그리거트 Root를 가집니다.

 

4. Repository

도메인 객체를 DB에 저장하기 위해 기능을 정의해놓은 도메인 모델입니다.

애그리거트 루트를 인자로 받아 애그리거트 단위로 도메인 객체를 저장/조회/수정/삭제하는 기능을 정의합니다.

아래의 예시는 레포지토리를 정의한 인터페이스입니다.

public interface OrderRepository {  //Repository
    //애그리거트 루트를 인자로 받아, 애그리거트 단위로 조작하는 기능 정의
    public Order findByNumber(OrderNumber number);
    public void save(Order order);
    public void delete(Order order);
}
public class CancelOrderService {  //Application 계층
	private OrderRepository orderRepository;  //DI로 구현 객체를 전달하자
    
    public void cancel(OrderNumber number) {  //도메인 모델에게 기능 구현을 위임
    	Order order = orderRepository.findByNumber(number);
        if(order == null) throw new NoOrderException(number);
        order.cancel();
    }
}

모듈 구조는 아래와 같습니다.

Order는 애그리거트 루트, OrderRepository는 주문관련 애그리거트를 DB에 저장하기 위한 기능을 정의한 Repository입니다.

DIP 설계 원칙에 맞게 인프라 스트럭처의 JpaOrderRepository가 도메인 모델의 <<OrderRepository>>를 구현하게 하여,

저수준 모듈이 고수준 모듈을 구현하는 방식으로 의존 관계를 매핑했습니다.(확장에 용이)

 

5. Domain Service

Domain Service는 어느 하나의 엔티티에 속하지 않고 여러 객체의 상호작용을 통해 구현하는 로직을 구현해야할 때 사용합니다.

예를 들어 '할인 금액 계산'이라는 service를 구현해야한다면 상품, 쿠폰, 회원 등급, 구매 금액 등의 다양한 조건들을 이용해야하는데 이렇게 어느 하나의 엔티티에 속하지 않고 여러 엔티티와 밸류를 필요로할 경우 Domain Service에 로직을 구현합니다.

 


인프라 스트럭처 계층

인프라 스트럭처 계층은 표현, 응용, 도메인 영역에서 사용하는 기능들을 구체적으로 구현합니다.

도메인 객체의 영속성 처리, 트랜잭션, SMTP, REST 클라이언트, 프레임워크 등 상위 모듈이 필요로 하는 기술을 구현합니다.

저수준 모듈인 인프라 스트럭처에서 별도로 구현하는 이유는 확장에 유연하고 테스트하기 쉬운 환경을 제공하기 위함입니다.

하지만 예외적으로 어느정도 DIP의 장점을 해치지 않는 선에서 Infrastructure 계층에 의존하도록 하는 것도 코드 간결화를 위해 좋은 선택이 될 수도 있습니다.

@Transactional, @Entity, @Table과 같이 JPA관련 어노테이션들은 코드 한 줄로 트랜잭션 처리나 매핑설정이 가능합니다.

위의 어노테이션이 없다면 일일이 xml 매핑 설정을 통해 구현해야합니다.

구현의 편리함을 위해서 DIP의 장점(확장에 용이)을 포기하는 것도 개발자의 관점에선 필요합니다.

 


도메인형으로 패키지 구성하기

아래의 블로그글을 참고해주세요.

 

https://cheese10yun.github.io/spring-guide-directory/

 

Spring Guide - Directory - Yun Blog | 기술 블로그

Spring Guide - Directory - Yun Blog | 기술 블로그

cheese10yun.github.io

 


정리

- 도메인 아키텍처 구조: Presentation - Application - Domain - Infra Structure

- DIP: 고수준 모듈이 저수준 모듈에 의존하지 않도록 하는 것

- 도메인 모델 구조: Entity, Value, Aggregate, Repository, Domain Service

 

 

 

Ref.

- 'DDD START! 도메인 주도 설계 구현과 핵심 개념 익히기' - 최범균

- https://cheese10yun.github.io/spring-guide-directory/

- https://private-space.tistory.com/92