2. 아키텍쳐 개요

아키텍쳐

아키텍쳐를 설계할 때 전형적으로 표현, 응용, 도메인, 인프라스트럭쳐 네 영역으로 구성한다.

  • 표현(UI) 영역 : 사용자의 요청을 받아 응용 영역에 전달하고 처리 결과를 다시 사용자에게 보여주는 역할을 한다.

  • 응용 영역 : 시스템이 사용자에게 제공해야 할 기능을 구현한다. 응용 영역은 기능을 구현하기 위해 도메인 영역의 도메인 모델을 사용한다. (응용 서비스는 로직을 직접 수행하기 보다 도메인 모델에 로직 수행을 위임한다.)

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

  • 인프라스트럭쳐 영역 : 구현 기술에 대한 것을 다룬다. 이 영역은 RDBS 연동 처리, 메시징 큐에 메시지 전송하거나 수신하는 기능을 구현한다. 인프라스트럭쳐 영역은 논리적인 개념을 표현하기 보다 실제 구현을 다룬다.

계층 구조 아키텍쳐

(상위) 표현 → 응용 → 도메인 → 인프라스트럭쳐 (하위)

계층 구조는 그 특성상 상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않는다. 하지만 구현의 편리함을 위해 계층 구조를 유연하게 적용한다.

하지만 표현, 응용, 도메인 계층이 상세한 구현 기술을 다루는 인프라스트럭쳐 계층에 종속된다. 인프라스트럭쳐 영역에 종속적인 코드는 다음과 같은 문제점이 있다.

  1. 한 가지의 기능(한 개의 영역)만 테스트하기 어렵다.

  2. 구현 방식을 변경하기 어렵다. (기능 확장의 어려움)

DIP

고수준 모듈은 의미 있는 단일 기능을 제공하는 모듈이며 고수준 모듈의 기능을 구현하려면 여러 하위 기능이 필요하다. 저수준 모듈은 하위 기능을 실제로 구현한 것이다.

고수준 모듈이 제대로 동작하려면 저수준 모듈을 사용해야 한다. 그런데, 고수준 모듈이 저수준 모듈을 사용하면 계층 구조 아키텍쳐에서의 두 가지 문제(구현 변경과 테스트가 어려움)가 발생한다.

DIP는 이 문제를 해결하기 위해 저수준 모듈이 고수준 모듈에 의존하도록 바꾼다. 이것은 추상화한 인터페이스를 통해 실현한다.

DIP를 적용하면 저수준 모듈이 고수준 모듈에 의존하게 된다. 고수준 모듈이 저수준 모듈을 사용하려면 고수준 모듈이 저수준 모듈에 의존해야 하는데, 반대로 저수준 모듈이 고수준 모듈에 의존한다고 해서 이를 DIP(Dependency Inversion Principle, 의존 역전 원칙)라고 부른다.

DIP를 적용하면 앞서 다른 영역이 인프라스트럭쳐 영역에 의존할 때 발생했던 두 가지 문제인 구현 교체가 어렵다는 문제와 테스트가 어려운 문제를 해소할 수 있다.

구현 기술 문제 해결

고수준 모듈은 더 이상 저수준 모듈에 의존하지 않고 추상화한 인터페이스에 의존한다. 실제 사용할 저수준 구현 객체는 의존 주입을 이용해서 전달받을 수 있다.

테스트 문제 해결

고수준 모듈이 저수준 모듈에 직접 의존했다면 저수준 모듈이 완성되기 전까지 테스트를 할 수 없다. 하지만 인터페이스를 사용해서 실제 구현 대신 Mock이나 Stub 같은 테스트 목적의 대용 객체를 사용해서 거의 모든 상황을 테스트할 수 있다.

⚠ DIP를 적용할 때 하위 기능을 추상화한 인터페이스는 고수준 모듈의 관점에서 도출해야 한다.

DIP와 아키텍쳐

인프라스트럭쳐 계층이 가장 하단에 위치하는 계층형 구조와 달리 아키텍쳐에 DIP를 적용하면 인프라스트럭쳐영역이 응용 영역과 도메인 영역에 의존하는 구조가 된다. 따라서 도메인과 응용 영역에 주는 영향을 최소화 하면서 구현 기술을 변경하는 것이 가능하다.

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

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

요소

설명

고유의 식별자를 갖는 객체로 자신의 라이프사이클을 갖는다. 주문,회원,상품과 같이 도메인의 고유한 개념을 표현한다. 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공한다.

개념적으로 하나인 도메인 객체의 속성을 표현할 때 사용한다. 주소나 금액 같은 타입이 밸류 타입이다. 엔티티 속성이나 다른 밸류 타입의 속성으로 사용된다.

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

도메인 모델의 영속성을 처리한다. (DBMS 테이블에서 엔티티 객체를 로딩하거나 저장하는 기능을 제공)

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

도메인 모델 Entity vs DB 테이블 Entity

도메인 모델의 Entity는 단순히 데이터를 담는 데이터 구조라기 보다 데이터와 함께 기능을 제공하는 객체이다.

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

애그리거트

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

    주문이 애그리거트의 예이다. 주문이라는 도메인 개념은 주문, 배송지 정보, 주문자, 주문 목록, 총 결제 금액의 하위 모델로 구성되는데 이 하위 개념을 표현한 모델을 하나로 묶어서 주문이라는 상위 개념으로 표현할 수 있다.

애그리거트를 사용하면 관련 객체를 묶어서 군집 단위로 모델을 바라볼 수 있다. 이를 통해 큰 틀에서 도메인 모델을 관리할 수 있다.

애그리거트는 군집에 속한 객체들을 관리하는 루트 엔티티를 갖는다. 루트 엔티티는 애그리거트가 속해 있는 엔티티와 밸류 객체를 이용해서 애그리거트가 구현해야 할 기능을 제공한다. 이는 애그리거트 내부 구현을 숨겨서 애그리거트 단위로 구현을 캡슐화할 수 있도록 돕는다.

리포지터리

도메인 객체를 지속적으로 사용하려면 RDBMS, NoSQL, 로컬 파일과 같은 물리적인 저장소에 보관해야 한다. 이를 위한 도메인 모델이 리포지토리(repository)이다. 엔티티, 밸류가 요구사항에서 도출되는 도메인 모델이라면 리포지토리는 구현을 위한 도메인 모델이다.

리포지토리는 애그리거트 단위로 객체를 저장하고 조회하는 기능을 정의한다. 도메인 모델을 사용해야 하는 코드는 리포지토리를 통해 도메인 객체를 구한 뒤에 도메인 객체의 기능을 실행하게 된다.

모듈 구성

아키텍쳐 각 영역은 별도 패키지에 위치한다. 도메인이 크면 하위 도메인으로 나누고 각 하위 모메인 마다 별도 패키지를 구성한다. 도메인 모듈은 도메인에 속한 애그리거트를 기준으로 다시 패키지를 구성한다. 각 애그리거트와 모델과 리포지터리는 같은 패키지에 위치시킨다. 도메인이 복잡하면 도메인 모델과 도메인 서비스를 별도 패키지에 위치시킬 수도 있다.

Last updated

Was this helpful?