2. 아키텍처 개요

네 개의 영역과 계층 구조

 

아키텍처를 설계할 때 출현되는 전형적인 영역은 '표현', '응용', '도메인', '인프라스트 럭처'의 네 영역이다.

그리고 이 영역들을 계층 구조로 표현할 때 의존관계를 살펴보면 다음 그림과 같다.

 

 

하지만 구현의 편리함을 위해 계층 구조를 유연하게 적용한다. 예를 들어, 응용 계층은 도메인 계층에 의존하지만 외부 시스템과의 연동을 위해 인프라 스트럭처 계층에 의존하기도 한다. 다음 그림과 같이 말이다.

 

 

짚고 넘어가야할 것이 있는데 바로 응용, 도메인 계층이 상세한 구현 기술을 다루는 인프라 스트럭처 계층을 의존한다는 점이다.

응용 계층과 도메인 계층은 정책으로부터 도출된 고수준 계층인 반면 인프라 스트럭처는 저수준 계층이다.

 

 

위의 그림은 Mybatis를 통해 엔티티를 DB에 CURD하기 위해 설계된 구조이다. 만약 Mybatis에서 Jpa로 엔티티 저장 방식을 변경한다고 가정하면 응용, 도메인 계층 역시 의존하고있는 인프라 스트럭처 계층의 영향을 받아 코드 변경이 이루어진다.

 

클린아키텍처에 따르면 저수준 정책의 변경은 고수준 정책에 영향을 줘서는 안된다.

이러한 문제를 해결하기 위해서는 DIP 적용이 필요하다.

 

 

DIP와 아키텍처

 

위의 그림은 아직 DIP (의존성 역전)를 적용하지 않았으며, 응용 계층의 SomeService가 인프라 스트럭처계층의 MybatisSomeRepository를 의존하고있는 구조다.

 

 

 

위의 그림은 DIP를 적용했으며, SomeRepository를 추상화시켜 인터페이스로 정의하고 SomeService가 같은 계층 내의 SomeRepository를 의존한다. SomeService는 SomeRepository의 구현체에 대해서 아무것도 모르며 이 말은 즉슨, SomeService가 MybatisSomeRepository를 의존하지 않는것으로 볼 수 있다.

반면, 인프라 스트럭처 계층의 MybatisSomeRepository는 응용계층의 SomeRepository 인터페이스를 의존하는 구현체이다.

 

 

DIP 주의사항

 

간혹 DIP를 잘못이해하여 단순히 인터페이스와 구현 클래스를 분리하는 정도로만 받아들일 수 있다. 아래의 그림과 같이 말이다.

 

 

DIP는 고수준 모듈이 저수준 모듈을 의존하지 않도록 하기 위함임을 명심해야한다.

 

 

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

 

도메인 영역은 도메인의 핵심 모델을 구현한다.

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

 

요소 설명
엔티티(Entity) 고유의 식별자를 갖는 객체로 자신의 라이프사이클을 갖는다. 주문(Order), 회원(Member), 상품(Product)과 같이 도메인의 고유한 개념을 표현한다. 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공한다.
밸류(Value) 고유의 식별자를 갖지 않는 객체로 주로 개념적으로 하나인 도메인 객체의 속성을 표현할 때 사용된다. 배송지 주소를 표현하기 위한 주소(Address)나 구매 금액을 위한 금액(Money)과 같은 타입이 밸류 타입이다. 엔티티의 속성으로 사용될 뿐만 아니라 다른 밸류 타입의 속성으로도 사용될 수 있다.
애그리거트(Aggregate) 애그리거트는 관련된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다. 예를 들어, 주문과 관련된 Order 엔티티, OrderLine 밸류, Orderer 밸류 객체를 '주문' 애그리거트로 묶을 수 있다.
리포지터리
(Repository)
도메인 모델의 영속성을 처리한다. 예를 들어, DBMS 테이블에서 엔티티 객체를 로딩하거나 저장하는 기능을 제공한다.
도메인 서비스
(Domain Service)
특정 엔티티에 속하지 않는 도메인 로직을 제공한다. '할인 금액 계산'은 상품, 쿠폰, 회원 등급, 구매 금액 등 다양한 조건을 이용해서 구현하게 되는데, 이렇게 도메인 로직이 여러 엔티티와 밸류를 필요로 할 경우 도메인 서비스에 로직을 구현한다.

 

엔티티

 

도메인 모델의 엔티티는 단순히 데이터를 담고 있는 데이터 구조하기보다는 데이터와 함께 기능을 제공하는 객체이다. 도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화하여 데이터가 임의로 변경되는 것을 막는다.

 

public class Order {
    // 주문 도메인 모델의 데이터
    private OrderNo number;
    private Orderer orderer;
    private ShippingInfo shippingInfo;
    ...
    
    // 도메인 모델 엔티티는 도메인 기능도 함께 제공
    public void changeShippingInfo(ShippingInfo newShippingInfo) {
        // 배송정보 변경로직
    }
}
 

 

 

밸류

 

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

 

public class Orderer {
    private String name;
    private String email;
    
    ...
}

 

 

애그리거트

 

도메인이 커질수록 개발할 도메인 모델도 커지면서 많은 엔티티, 밸류가 출현되는데 애그리거트는 커져버린 도메인 모델의 전체적인 구조를 파악하는데 도움을 준다.

 

애그리거트는 관련 객체를 하나로 묶은 군집이다. (주문, 배송지정보, 주문자 등 여러 하위 모델이 존재할 때 주문이라는 상위 모델로 묶어 표현할 수 있다.)

 

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

 

반대로 말하면 주문 애그리거트는 루트 엔티티인 Order를 통하지 않고는 주문 에그리거트가 구현해야할 ShippingInfo를 변경할 수 있는 방법을 제공하지 않는다.

애그리거트의 더 상세한 내용은 3장에서 살펴보자.

 

 

리포지터리

 

엔티티나 밸류가 정책의 요구사항으로 부터 도출되는 도메인 모델이라면 리포지터리는 구현을 위한 도메인 모델이다.

 

 

응용 계층에서 주문 취소에 대한 로직을 구현하기 위해 Order 도메인 모델을 사용함과 동시에 Order 도메인 객체를 구해야하기 때문에 도메인 객체를 영속화하는데 필요한 기능을 추상화한 OrderRepository를 사용한다. OrderRepository의 구현체는 인프라 스트럭처 계층에 존재한다.

 

 

무조건 인프라 스트럭처 의존을 없애는것이 좋을까?

 

결론부터 말하면 무조건 인프라 스트럭처에 대한 의존을 없애는 것이 좋은 것은 아니다.

예를 들어, 스프링을 사용할 경우 응용 서비스는 트랜잭션을 처리하기 위해 트랜잭션을 직접 구현하지 않고 스프링이 제공하는 @Transactional을 사용하는 것이 편리하다.

 

구현의 편리함은 DIP가 주는 장점(변경의 유연함)만큼 중요하기 때문에 DIP의 장점을 해치지 않는 범위에서 응용 영역과 도메인 영역에서 구현 기술에 대한 의존을 가져가는 것이 현명하다. 인프라 스트럭처에 대한 의존을 완전히 갖지 않도록 시도하는 것은 자칫 구현을 더 복잡하고 어렵게 만들 수 있기 때문이다.

 

 

모듈의 패키지 구성을 정리해보자

 

아키텍처의 각 영역은 별도 패키지에 위치시킨다. 만약 도메인이 크면 하위 도메인으로 나누고 하위 도메인 마다 별도 패키지를 구성한다.

 

 

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

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

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

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

모듈 구조를 얼마나 세분화해야 하는지에 대해 정해진 규칙은 없다. 단지, 한 패키지에 너무 많은 타입이 몰려서 코드를 찾을 때 불편한 정도만 아니면 된다.

 

 

'📚 Book > DDD Start!' 카테고리의 다른 글

7. 도메인 서비스  (0) 2020.08.12
6. 응용서비스와 표현영역  (0) 2020.08.09
3. 애그리거트  (4) 2020.07.19
1. 도메인 모델 시작  (0) 2020.07.01