임대일

DDD(Domain-Driven Design) 도메인 주도 설계: DDD 계층 구조(Layered Architecture) 본문

개발팁

DDD(Domain-Driven Design) 도메인 주도 설계: DDD 계층 구조(Layered Architecture)

limdae94 2024. 7. 15. 15:51
 “유일한 진실을 말하자면, 객체지향 판 ‘스파게티 코드’는, 당연히, ‘라쟈냐 코드’이다. (과하게 많은 계층)”
- 로버트 월트만 -

“In the one and only true way. The object-oriented version of ‘Spaghetti code’ is, of course, ‘Lasagna code’. (Too many layers).”
- Roberto Waltman -

1. DDD 이해

DDD(Domain-Driven Design, 도메인 주도 설계)는 도메인 모델을 기반으로 복잡한 소프트웨어 시스템을 설계하고 개발하는 방법론이다. DDD는 소프트웨어 시스템이 해결하려는 문제 공간을 깊이 이해하고, 여러 이해관계자들과 긴밀히 협력하여 도메인 모델을 기반으로 소프트웨어를 개발하는 것을 강조한다.

 

도메인(Domain)의 사전적 정의는 "영역, 집합"으로 DDD에서는 다음과 같은 의미를 갖는다. 소프트웨어로 해결하고자 하는 문제의 영역, 실세계에서 사건이 발생하는 집합 혹은 영역이라고 바라볼 수 있다. 도메인이 무엇인지보다는 도메인에 포함되는 것이 무엇인가 정의를 내리는 것이 더 중요하다. 도메인에 포함되는 개념은 시스템의 대상 분야가 무엇인지에 따라 크게 달라진다.

 

쇼핑몰로 예를 들어보자. 쇼핑몰에서는 손님들이 주문하는 도메인(Order Domain), 직원이 상품을 관리하는 도메인(Manage Domain), 결제를 담당하는 도메인(Payment Domain)이 있다. 손님이 상품을 주문하고 결제를 수행하는 과정에서 각 도메인들은 서로 상호작용이 반드시 필요하다. 이처럼 여러 도메인들이 상호작용하며, 비즈니스 도메인별로 나누어 설계하는 것을 도메인 주도 설계라고 한다.

 

도메인과 객체의 차이

객체(Objcet)는 실세계에 존재하거나 생각할 수 있는 것을 의미한다. 자바에서의 객체는 속성과 기능을 가진 프로그램 단위로 실세계에 존재하는 사물 혹은 개념을 정의한다. 속성은 흔히 말하는 멤버 변수, 파라미터를 의미하고 기능은 메서드를 의미한다.

 

이처럼 객체와 도메인은 서로 넓은 의미에서 개념적으로 동일하다고 볼 수 있지만 포함하는 범위가 서로 다르다. 객체는 추상화 또는 구체화할 수 있는 특정 요소만을 표현하고 도메인은 사용자가 사용하는 모든 것을 설명할 수 있다. 일반적으로 도메인을 비즈니스 로직으로 표현하는 데 집중한다. 도메인은 비즈니스의 문제 영역을 나타내며, 도메인 모델은 이 문제 영역을 해결하기 위한 설계이다.

 

객체와 도메인 비교를 위해 예를 들어 "고양이가 사과를 먹는다" 라고 가정하자.

 

객체: "고양이"와 "사과"는 각각 객체가 될 수 있으며, "먹는다"는 고양이가 수행하는 메소드(행위)로 표현된다.

  • 객체의 속성: 고양이(이름, 나이 등), 사과(색깔, 크기 등)
  • 객체의 행위: 고양이가 먹다, 뛰다 등

객체 지향 관점:

  • 객체: 고양이, 사과
  • 메소드: 먹다
  • 고양이.먹다(사과)

도메인: 도메인은 비즈니스의 문제 영역을 나타내며, 도메인 모델은 이 문제 영역을 해결하기 위한 설계이다.

  • 도메인 모델: 고양이, 사과, 먹다와 같은 개념들이 포함된다. 도메인 모델은 이들 개념 간의 관계와 규칙을 정의한다.
  • 도메인 이벤트: "고양이가 사과를 먹는다"와 같은 사건이 발생할 수 있다.

도메인 주도 설계 관점:

  • 도메인 개념: 고양이, 사과, 먹다
  • 도메인 이벤트: "고양이가 사과를 먹는다"는 도메인 이벤트로 정의할 수 있으며, 비즈니스 로직에서 중요한 사건을 나타낸다.

정리하자면 객체는 현실 그대로를 표현하고, 도메인은 위의 문장을 읽는 사용자가 바라보는 관점에 따라 각각을 구분하거나 전체라고 할 수 있다. 이처럼 도메인은 명확하게 정해져 있지 않고 사용자에 따라 또는 사용자가 바라보는 관점에 따라 계속 변경될 수 있다. 도메인은 사용자가 누구인지, 어떻게 사용하는지에 따라 동일한 요소(Element)이더라도 계속 바뀔 수 있고 형태가 고정되어 있지 않다. 예를 들어 어느 사용자가 "고양이", "사과를 먹는다" 두 가지 관점으로 바라보고 있다면 해당 문장에서의 도메인은 두 개라고 할 수 있는 것이다. 

 

DDD의 핵심 목표는 각각의 도메인은 서로 철저히 분리하고 높은 응집력(High coheision)낮은 결합도(Loosly Coupling)변경과 확장에 용이한 설계이다.

 

2. DDD 의 핵심 개념

 

  • 도메인(Domain):
    • 도메인은 소프트웨어 시스템이 해결하려는 문제 공간이다.
    • 도메인은 비즈니스 로직과 규칙을 포함한다.
    • 도메인은 여러 개의 서브도메인으로 구성될 수 있다.
  • 서브도메인(Subdomain):
    • 도메인의 하위 개념으로, 도메인을 더 작은 단위로 나눈 부분이다.
    • 핵심 서브도메인(Core Subdomain): 비즈니스에서 가장 중요한 부분.
    • 지원 서브도메인(Supporting Subdomain): 핵심 서브도메인을 지원하는 부분.
    • 일반 서브도메인(Generic Subdomain): 다른 여러 도메인에서 일반적으로 사용되는 부분.
  • 도메인 모델(Domain Model):
    • 도메인 전문가와 개발자가 공유하는 비즈니스 지식을 소프트웨어로 표현한 모델.
    • 엔티티, 밸류 오브젝트, 애그리게이트, 서비스 등으로 구성된다.
  • 엔티티(Entity):
    • 고유 식별자를 가진 객체.
    • 도메인 모델의 중요한 부분을 나타내며, 상태를 가지는 객체이다.
  • 밸류 오브젝트(Value Object):
    • 고유 식별자가 없는 객체.
    • 불변 객체이며, 값 자체로 비교된다.
    • 예: 돈, 날짜, 주소.
  • 애그리게이트(Aggregate):
    • 관련된 엔티티와 밸류 오브젝트의 집합.
    • 트랜잭션의 경계를 정의한다.
    • 애그리게이트 루트(Aggregate Root): 애그리게이트의 진입점이 되는 엔티티로, 애그리게이트의 모든 변경은 애그리게이트 루트를 통해서만 이루어져야 한다.
  • 리포지토리(Repository):
    • 애그리게이트를 영구 저장소와 연결하는 인터페이스.
    • 도메인 객체를 저장하고 조회하는 책임을 가진다.
  • 도메인 서비스(Domain Service):
    • 특정 엔티티에 속하지 않는 도메인 로직을 처리하는 서비스.
    • 복잡한 비즈니스 로직을 캡슐화한다.
  • 애플리케이션 서비스(Application Service):
    • 도메인 객체를 사용하여 비즈니스 로직을 구현하는 서비스.
    • 트랜잭션 관리, 인프라스트럭처와의 통신 등을 처리한다.
  • 도메인 이벤트(Domain Event):
    • 도메인 내에서 발생하는 사건.
    • 다른 도메인에 알려야 할 중요한 변경 사항을 나타낸다.
  • 팩토리(Factory):
    • 복잡한 객체 생성 로직을 캡슐화하는 클래스.
    • 엔티티나 애그리게이트를 생성하는 책임을 가진다.

 

 

3. DDD 계층 구조

Layered Architecture는 계층으로 구분된 아키텍처를 의미한다. Layered Architecture의 목표는 각각의 Layer는 하나의 관심사에만 집중할 수 있도록 하는 것이다. 일반적으로 3 계층 혹은 4 계층으로 나누어 사용한다. 아래 그림은 4 계층으로 분리된 DDD 계층 구조이다.

 

3계층으로 나눌 때는 표현 계층(Presentiation Layer) - 서비스 계층(Business Layer) - 영속성 계층(Persistence Layer)으로 구분한다.

Layered Architecture

 

Layered Architecture를 올바르게 구현하기 위한 두 가지 중요한 규칙이 있다.

  1. 위의 계층에서 아래 계층에는 접근이 가능하지만 아래에서 위로는 접근이 불가능하다는 것이 전제이다.
  2. 한 계층의 관심사와 관련된 어떤 것도 다른 계층에 배치되어서는 안 된다.

 

3.1 표현 계층

  • 표현 계층(Presentation Layer)은 Controller에 해당한다. 
  • 사용자의 요청을 해석하고 응답하는 일을 책임지는 계층이다.
  • 클라이언트로부터 요청을 받고 응답을 반환하는 API를 정의한다.
  • 사용자에게 UI를 제공하거나 클라이언트에 응답을 다시 보내는 역할을 하는 모든 클래스가 포함된다.

 

3.2 응용 계층

  • 응용 계층(Application Layer)는 Service 에 해당한다.
  • 비즈니스 로직을 정의하고 정상적으로 수행될 수 있도록 도메인 계층과 인프라스트럭처 계층을 연결해주는 역할을 담당하는 계층이다.
  • 응용 계층은 많은 정보를 가지고 있지 않게 유지하는 것이 중요하다.
  • 실질적인 데이터의 상태 변화 등의 처리는 도메인 계층에서 진행할 수 있도록 위임하는 것이 중요하다.

응용 계층에서 포함되는 기능들은 다음과 같다.

 

1. 트랜잭션의 단위

2. DTO 변환

3. 엔티티 조회, 저장

간단하게 설명하자면, 엔티티를 리포지토리(Repository)로부터 검색하고, 변경 내용을 저장하는 기능(Persistence)을 호출한다. 구현은 인프라 계층(Infra Layer)에서 수행된다.

 

4. 엔티티 조회, 저장

사용자가 특정 URL에 대한 권한이 있는지 정도의 인가는 Presentation Layer에서 수행한다. 그러나 URL만으로 판단이 어렵거나 DB에서 데이터와 대조가 필요한 경우에는 Application Layer에서 수행한다.

 

5. 파라미터 검증

Presentation Layer에서도 수행할 수 있지만 주로 요청 방식에 따라 달라지는 "형식"에 대한 검증을 하고, Application Layer에서는 "논리적인" 오류를 검증한다.

 

3.3 도메인 계층

  • 도메인 계층(Domain Layer)은 Model에 해당한다.
  • 비즈니스 규칙, 정보에 대한 실질적인 도메인에 대한 정보를 가지고 있으며 이 모든 것을 책임지는 계층이다.
  • 엔티티를 활용하여 도메인 로직이 실행되며, 업무 상황을 반영하여 상태를 제어하는 역할에 집중하는 계층이다.

 

3.4 인프라 계층

  • 인프라 계층(Infrastructure Layer)은 Repository에 해당한다.
  • DB, 메시징 시스템 등 외부와의 통신을 담당하는 계층이다.
  • 인프라 계층에서 얻어온 정보를 응용 계층 혹은 도메인 계층에 전달하는 것이 주 목적이다.

각각의 도메인을 4 계층으로 분리된 Layer로 만드는 것을 DDD(Domain-Driven Desgin)의 핵심 설계 방식이다.

 

 

4. Layered Architecture 구현

 

GitHub - spring-projects/spring-petclinic: A sample Spring-based application

A sample Spring-based application. Contribute to spring-projects/spring-petclinic development by creating an account on GitHub.

github.com

 

스프링의 대표적인 예제 프로젝트 Spring Pet Clinic으로 Layered Architecture를 패키지로 구성해보면 아래 그림과 같다. 해당 프로젝트는 도메인 지향적인 설계가 된 패키지이지만, 코드 구성의 두 가지 방법이 서로 어떻게 다른지 확인하자.

Application Layer, 즉 Service가 없고 거의 모든 것이 Controller에서 수행된다는 점을 참고하자.

 

 

5. Layered Architecture의 장단점

Layered Architecture 장점

  • 각 레이어를 Loosely Coupling된 형태로 구축하면서, 각각 자신의 관심사에만 집중할 수 있다.
  • 핵심 비즈니스 로직을 순수하게 유지함으로써 유지보수와 확장성 측면에서 이득을 얻을 수 있다.
  • 각 레이어에 서로 다른 추상화 수준을 가진 상태와 행동을 위치시킴으로써 코드 재사용성을 높일 수 있다.

Layered Architecture 단점

  • 서비스가 커질수록 복잡도가 증가하여 확장성이 떨어진다.
  • 레이어로 분리된 관심사 외에 다른 관심사가 생길 경우 패키지 분리 및 코드 배치가 어렵다.

Layered Architecture는 작고 단순하며 확장성보다는 일관성을 가져가는 것이 목표인 어플리케이션이나 웹 사이트에 적합한다. 또한 단순성 및 구현 용이성으로 인해 어플리케이션 시작점으로 사용하기에 적절하다.