Today
-
Yesterday
-
Total
-
  • DDD - 02. 아키텍처 개요
    책 공부/DDD 도메인 주도 개발 시작하기 2024. 1. 16. 22:12

    응용 영역

    p. 63


    표현 영역을 통해 사용자의 요청을 전달받는 응용 영역은 시스템이 사용자에게 제공해야 할 기능을 구현한다.


    응용 영역은 로직을 직접 수행하기 보다는 도메인 모델(도메인 영역)에 로직 수행을 위임한다.




    인프라스트럭처 영역

    p. 64


    인프라스트럭처 영역은 구현 기술에 대한 것을 다룬다.


    이 영역은 RDBMS 연동을 처리하고, 메시징 큐에 메시지를 전송하거나 수신하는 기능을 구현하고(Messging Service), 몽고DB나 레디스와의 데이터 연동(NoSQL 연동)을 처리한다.


    이 영역은 SMTP를 이용한 메일 발송 기능을 구현하거나 HTTP 클라이언트(ex. RestTemplate, OpenFeign)를 이용해서 REST API를 호출하는 것도 처리한다.


    인프라스트럭처 영역은 논리적인 개념을 표현하기보다는 실제 구현을 다룬다.


    도메인 영역, 응용 영역, 표현 영역은 구현 기술을 사용한 코드를 직접 만들지 않는다.

    대신 인프라스트럭처 영역에서 제공하는 기능을 사용해서 필요한 기능을 개발한다.




    DIP

    p. 70


    고수준 모듈

    의미 있는 단일 기능을 제공하는 모듈


    고수준 모듈의 기능을 구현하려면 여러 하위 기능이 필요하다.


    고수준 모듈의 예를 들면 가격 할인 계산이라는 기능을 구현하는 CalculateDiscountService가 있을 수 있다.


    가격 할인 계산 기능을 구현하려면 고객 정보를 구해야 하고 룰을 실행해야 하는데 이 두 기능이 하위 기능이다.


    저수준 모듈

    하위 기능을 실제로 구현한 것


    JPA를 이용해서 고객 정보를 읽어오는 모듈과 룰을 실행하는 모듈이 저수준 모듈이 된다.





    고수준 모듈이 제대로 동작하려면 저수준 모듈을 사용해야 한다.


    고수준 모듈이 저수준 모듈을 사용하면 구현 변경테스트가 어렵다는 문제가 발생하므로 DIP를 적용하여 저수준 모듈이 고수준 모듈에 의존하도록 한다.




    DIP

    저수준 모듈이 고수준 모듈에 의존한다고 해서 이를 DIP (의존 역전 원칙)이라고 부른다.


    DIP를 적용하면

    1. 저수준 모듈이 고수준 모듈에 의존하게 된다.
    2. 고수준 모듈은 더 이상 저수준 모듈에 의존하지 않고 구현을 추상화한 인터페이스에 의존한다.
    3. 고수준 모듈 테스트시 저수준 모듈이 구현한 인터페이스를 모의(Mock) 객체로 생성하여 저수준 모듈의 구현 없이도 테스트가 가능해진다.



    DIP 주의사항

    DIP를 잘못 생각하면 단순히 인터페이스와 구현 클래스를 분리하는 정도로 받아들일 수 있다.


    DIP의 핵심고수준 모듈이 저수준 모듈에 의존하지 않도록 하기 위함인데,
    DIP를 적용한 결과 구조만 보고 저수준 모듈에서 인터페이스를 추출하는 경우가 있다.


    인터페이스를 도출하는 관점을 저수준 모듈이 아닌 고수준 모듈이 되도록 해야 한다.



    고수준 모듈 관점으로 인터페이스를 도출하면
    추상화한 인터페이스는 저수준 모듈이 아닌 고수준 모듈에 위치한다.





    DIP와 아키텍처

    인프라스트럭처 영역은 구현 기술을 다루는 저수준 모듈이고,
    응용 영역과 도메인 영역은 고수준 모듈이다.


    인프라스트럭처 계층이 가장 하단에 위치하는 계층형 구조와 달리
    아키텍처에 DIP를 적용하면 인프라스트럭처 영역이 응용 영역과 도메인 영역에 의존하는(상속 받는) 구조가 된다.




    도메인 영역의 주요 구성 요소

    p. 80


    도메인 영역을 구성하는 요소는 다음과 같다.


    • 엔티티 Entity
      고유의 식별자를 갖는 객체.
      자신의 라이프 사이클을 갖는다.

    • 밸류 Value
      고유의 식별자를 갖지 않는 객체.
      개념적으로 하나인 값을 표현할 때 사용한다.
      다른 밸류 타입의 속성으로도 사용할 수 있다.

    • 애그리거트 Aggregate
      연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다.
      예를 들어 주문과 관련된 Order 엔티티, OrderLine 밸류, Orderer 밸류 객체를 ‘주문’ 애그리거트로 묶을 수 있다.

    • 리포지터리 Repository
      도메인 모델의 영속성을 처리한다.

    • 도메인 서비스 Domain Service
      특정 엔티티에 속하지 않은 도메인 로직을 제공한다.
      도메인 로직이 여러 엔티티와 밸류를 필요로 하면 도메인 서비스에서 로직을 구현한다.



    엔티티와 밸류

    도메인 모델의 엔티티와 DB 관계형 모델의 엔티티는 같은 것이 아니다.


    이 두 모델의 가장 큰 차이점은 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 함께 제공한다는 점이다.


    또 다른 차이점은 도메인 모델의 엔티티는 두 개 이상의 데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현할 수 있다는 것이다.




    애그리거트 AGGREGATE

    도메인이 커질수록 개발할 도메인 모델도 커지면서 많은 엔티티와 밸류가 출현하게 되고 모델은 점점 더 복잡해진다.


    도메인 모델이 복잡해지면 개발자가 전체 구조가 아닌 한 개 엔티티와 밸류에만 집중하는 상황이 발생하고 그러면 큰 수준에서 모델을 이해하지 못해 큰 틀에서 모델을 관리할 수 없는 상황에 빠질 수 있다.


    도메인 모델에서 전체 구조를 이해하는 데 도움이 되는 것이 바로 애그리거트이다.


    애그리거트는 관련 객체를 하나로 묶은 군집이다.


    • 대표적인 예시 : 주문
    • 주문 도메인의 하위 모델 구성 예시
      • 주문
      • 배송지 정보
      • 주문자
      • 주문 목록
      • 총 결제 금액

    애그리거트 간의 관계로 도메인 모델을 이해하고 구현하게 되며,
    이를 통해 큰 틀에서 도메인 모델을 관리할 수 있다.


    애그리거트는 군집에 속한 객체를 관리하는 루트 엔티티를 갖는다.

    루트 엔티티는 애그리거트에 속해 있는 엔티티와 밸류 객체를 이용해서 애그리거트가 구현해야 할 기능을 제공한다.


    애그리거트를 사용하는 코드는 애그리거트 루트가 제공하는 기능을 실행하고 애그리거트 루트를 통해서 간접적으로 애그리거트 내의 다른 엔티티나 밸류 객체에 접근한다.


    이것은 애그리거트의 내부 구현을 숨겨서 애그리거트 단위로 구현을 캡슐화할 수 있도록 돕는다.


    <애그리거트 루트 엔티티 `Order` UML>

    애그리거트를 구현할 때는 고려할 것이 많다.


    애그리거트를 어떻게 구현했느냐에 따라 구현이 복잡해지기도 하고,
    트랜잭션 범위가 달라지기도 한다.
    또한 구현 기술에 따라 애그리거트 구현에 제약이 생기기도 한다.


    자세한 내용은 3장에서 다룸




    리포지터리

    엔티티나 밸류가 요구사항에서 도출되는 도메인 모델이라면,
    리포지터리는 구현을 위한 도메인 모델이다.


    리포지터리는 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다.


    예를 들면 주문 애그리거트를 위한 리포지터리는 다음과 같이 정의할 수 있다.

    public interface OrderRepository {
    	Order findByNumber(OrderNumber number);
    	void save(Order order);
    	void delete(Order order);
    }
    

    도메인 모델 관점에서 OrderRepository는 도메인 객체를 영속화하는 데 필요한 기능을 추상화 한 것으로 고수준 모듈에 속한다.


    기반 기술을 이용해서 ORderRepository를 구현한 클래스는 저수준 모듈로 인프라스트럭처 영역에 속한다.


    앞서 예시로 들었던 CancelOrderService, Order, OrderRepository, JpaOrderRepository를 가지고 모듈 구조를 작성해본다면 다음과 같이 구성해 볼 수 있다.



    리포지터리 구현에 대한 내용은 4장에서 다룬다




    요청 처리 흐름

    표현 영역은 사용자가 전송한 데이터 형식이 올바른지 검사하고 문제가 없다면 데이터를 이용해서 응용 서비스에 기능 실행을 위임한다.


    응용 서비스는 도메인 모델을 이용해서 기능을 구현한다.



    응용 서비스 구현에 대한 내용은 6장에서 다룬다.




    인프라스트럭처 개요

    인프라스트럭처는 표현 영역, 응용 영역, 도메인 영역을 지원한다.


    도메인 객체의 영속성 처리, 트랜잭션, SMTP 클라이언트, REST 클라이언트 등 다른 영역에서 필요로 하는 프레임워크, 구현 기술, 보조 기능을 지원한다.


    DIP에서 언급한 것처럼 도메인 영역과 응용 영역에서 인프라스트럭처의 기능을 직접 사용하는 것보다 이 두 영역에 정의한 인터페이스를 인프라스트럭처 영역에서 구현하는 것이 시스템을 더 유연하고 테스트하기 쉽게 만들어준다.



    하지만 무조건 인프라스트럭처에 대한 의존을 없앨 필요는 없다.


    예를 들어

    • 응용 서비스는 트랜잭션 처리를 위해 스프링이 제공하는 @Transactional을 사용하는 것이 편리하다.
    • 영속성 처리를 위해 JPA를 사용할 경우 @Entity나 @Table과 같은 JPA전용 애너테이션을 도메인 모델 클래스에 사용하는 것이 XML 매핑 설정을 이용하는 것보다 편리하다.


    응용 영역과 도메인 영역이 인프라스트럭처에 대한 의존을 완전히 갖지 않도록 시도하는 것은 자칫 구현을 더 복잡하고 어렵게 만들 수 있다.


    좋은 예가 스프링의 @Transactional 애너테이션이다.


    @Transactional을 사용하면 한 줄로 트랜잭션을 처리할 수 있는데 코드에서 스프링에 대한 의존을 없애려면 복잡한 스프링 설정을 사용해야 한다.


    의존은 없앴지만 특별히 테스트를 더 쉽게 할 수 있다거나 유연함을 증가시켜주지 못한다. 단지 설정만 복잡해지고 개발 시간만 늘어날 뿐이다.




    모듈 구성

    아키텍처의 각 영역은 별도 패키지에 위치한다.


    영역별로 모듈이 위치할 패키지를 구성해볼 수 있다.


    도메인이 크면 하위 도메인으로 구분하여 별도의 패키지를 구성한다.



    그리고 도메인 모듈은 도메인에 속한 애그리거트를 기준으로 다시 패키지를 구성한다.


    예를 들어 카탈로그 하위 도메인이 상품 애그리거트와 카테고리 애그리거트로 구성될 경우 다음과 같이 도메인을 두 개의 하위 패키지로 구성할 수 있다.



    애그리거트, 모델, 리포지터리는 같은 패키지에 위치시킨다.


    도메인이 복잡하면 도메인 모델도메인 서비스를 다음과 같이 별도 패키지에 위치시킬 수도 있다.

    • com.myshop.order.domain.order : 애그리거트 위치
    • com.myshop.order.domain.service : 도메인 서비스 위치

    응용 서비스도 다음과 같이 도메인 별로 패키지를 구분할 수 있다.

    • com.myshop.catalog.application.product
    • com.myshop.catalog.application.category

    책 정보

    책 클릭하면 구매처 안내 💁

Designed by Tistory / Custom by 얼거스