
2024.09.05(금) 특강 정리
1. DDD의 개념과 등장 배경
소프트웨어를 설계할 때 고객의 요구사항을 정확히 이해하는 것이 중요하다.
요구사항을 잘못 이해하면 잘못된 기능을 만들고 수정하는 것도 어렵다.
그럼 이런 문제는 왜 발생할까?
과거에는 주로 기술 중심의 개발 방법론이 사용되었기 때문이다.
이러한 방법론은 기술적 요구사항을 중점적으로 다루지만,
비즈니스 측면에서 발생하는 다양한 요구사항을 효과적으로 반영하기에는 한계가 있었다.
특히, 비즈니스 전문가와 개발자 간의 소통이 원활하지 않으면, 최종 소프트에어가 비즈니스의 실제 요구를 충족시키지 못할 수 있었다.
이러한 문제점들을 해결하기 위해 나온 설계가 도메인 주도 설계(Domain Driven Design)이다.
'도메인'이란 소프트웨어로 해결하려는 문제의 영역을 의미한다. (회원, 상품, 주문 등)
또한, 하나의 도메인 내부에는 하위 도메인이 존재할 수 있다. (회원 프로필, 상품 상세, 주문 상품 등)
2. DDD의 핵심 가치
DDD는 소프트웨어 개발에서 도메인 지식이 가장 중요한 요소임을 인식하고, 이를 중심으로 소프트웨어를 설계한다.
도메인 전문가와 개발자가 협력하여 도메인 모델을 구축하고, 이를 통해 비즈니스 요구사항을 충실히 반영할 수 있다. 이렇게 하면 복잡한 소프트웨어 시스템도 효과적으로 관리할 수 있게 된다.
결국, 도메인 전문가, 이해관계자 그리고 개발자가 동일한 지식을 공유하고 직접 소통하여
비즈니스와 기술 간의 간극을 좁히고, 복잡한 시스템에서 발생할 수 있는 문제들을 효과적으로 해결하는 것에 목적을 두고 있다.
그럼 개발자와 도메인 전문가 간의 간극을 어떻게 좁힐 수 있을까?
코드를 작성할 때 도메인에서 사용하는 용어는 매우 중요하다. 도메인에서 사용하는 용어를 코드에 반영하지 않으면 해당 코드는 개발자에게 코드의 의미를 해석해야 하는 부담을 준다.
예를 들어 OrderState를 다음과 같이 구현했다고 가정해본다.
어떤 개발자가 주문 상태에 "결제 대기중", "상품 준비중", "배송 중", "배송 완료" 상테가 있다고 가정하고
STEP 1,2,3,4로 구현할 경우 비즈니스 로직을 아래와 같이 구성할 가능성이 높다.
도메인 주고 설계의 저자 에릭 에반스는 언어의 중요성을 강조하며 유비쿼터스 언어(Ubiquitous Language)라는 용어를 사용했다.
이 개념은 전문가, 관계자, 개발자가 도메인과 관련된 공통의 언어를 만들어 대화, 문서, 도메인 모델, 코드, 테스트 등 모든 곳에서 동일하게 사용하는 것을 의미한다.
이렇게 함으로서 용어의 모호함을 줄이고, 개발자는 도메인과 코드 사이에서 불필요한 해석 과정을 줄일 수 있었다.
시간이 지남에 따라 도메인에 대한 이해가 깊어지면, 새로 이해한 내용을 잘 표현할 수 있는 용어르 찾아 이를 공통의 언어로 만들어 함께 사용한다. 이렇게 새로 발견한 용어는 코드와 문서에도 반영하여 최신의 모델을 유지한다.
실무에서 "용어 사전"과 같은 단어장을 만드는 사례도 많다
3. 도메인 모델
도메인 모델은 DDD에서 핵심 개념을 표현하는 방법이다.
이는 특정 문제 영역(도메인)에 대한 지식, 규칙, 그리고 로직을 추상화하여 개념적으로 표현한 것이다.
예를 들어, 전자상거래(E-Commerce) 시스템에서 도메인 모델은 주문, 결제, 배송 같은 개념과 그들 간의 관계를 표현할 수 있다.
도메인 모델을 만들기 위해서는 핵심 구성 요소, 규칙, 기능을 파악해야 한다.
서비스의 요구사항을 분석하고 관련 기능들을 묶으면, 아래와 같은 구조를 만들 수 있다.
4. DDD의 구조와 용어 설명
도메인 모델은 크게 엔티티(Entity)와 벨류(Value)로 나뉘어 질 수 있다.
왜 두가지를 나누었고, 어떤 점이 다를까?
엔티티(Entity)
고유의 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다.
주문(Order), 회원(Member), 상품(Product)과 같이 도메인 고유한 개념을 표현한다.
엔티티는 단순히 데이터를 담고 있는 데이터 구조라기보다는 데이터와 함께 기능을 제공하는 객체이다.
도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화해서 데이터가 임의로 변경되는 것을 막을 수 있다.
벨류(Value)
고유의 식별자를 갖지 않는 객체로 주로 개념적으로 하나인 값을 표현할 때 사용된다.
배송지 주소를 표현하기 위한 주소(Address)나 구매 금액을 위한 금액(Money)와 같은 타입이 Value 타입이다.
엔티티의 속성으로 사용할 뿐만 아니라 다른 Value 타입의 속성으로도 사용할 수 있다.
▶식별자는 다양한 방식으로 생성할 수 있다. (시퀀스, DB의 자동 증가 컬럼, UID, 특정 규칙에 의한 고유한 값 등)
도메인 서비스 (Domain Service)는 특정 엔티티에 속하지 않는 도메인 로직을 담당한다.
예를 들어 "할인 금액 계산"은 상품, 쿠폰, 회원 등급, 구매 금액 등 다양한 조건을 고려하여 이루어지는데,
이 로직의 주체가 명확하지 않을 때가 있다. 이처럼 여러 엔티티와 값이 필요한 도메인 로직은 도메인 서비스에서 구현할 수 있다.
만약 왼쪽 그림처럼 도메인 모델을 구성했을 때 어떤 도메인 모델이 어떤 역할을 하는 쉽게 판단이 되는가?
이처럼 개별 객체 단위에서 상위 객체의 개념을 파악하려면 시간이 오래 걸린다.
그리고 주요 도메인 개념 간의 관계를 파악하기 어렵다는 것은 곧 코드를 변경하고 확장하는 것이 어려워진다는 것을 의미한다.
이것이 애그리거트(Aggregate)가 필요한 이유이다.
애그리거트(Aggregate) 는 관련된 객체들을 모아 하나의 단위로 취급하는 개념이며
연관 도메인을 애그리거트로 묶어 하나의 군집으로 이해한다면 좀 더 상위 수준에서 도메인 모델 간의 관계를 파악할 수 있다. (오른쪽 그림)
예시)
루트 애그리거트의 메서드를 통해 간접적으로
OrderProduct(하위 엔티티)에 접근한다.
이를 통해 하위 엔티티들은 루트 엔티티의 라이프 사이클에 따르게 된다.
애그리거트에 속한 모든 객체가 일관된 상태를 유지하려면 애그리거트 전체를 관리할 주체가 필요하다.
그래서 애그리거트는 특정 도메인 군집에 속한 객체들을 관리하는 '루트 엔티티(Root Entity)'를 가지고 있다.
하나의 애그리거트에는 '반드시 하나의 루트 엔티티'가 있으며, 여러 개의 Entity와 Value 객체들이 포함될 수 있다.
루트 엔티티는 애그리거트 내의 엔티티와 벨류 객체를 활용해, 에그리거트가 수행해야 할 기능들을 제공한다.
애그리거트를 사용하는 코드는 애그리거트가 제공한느 기능을 실행하며, 루트 애그리거트를 통해 간접적으로 애그리거트 내의 다른 엔티티나 벨류 객체에 접근할 수 있다.
이 방식을 애그리거트의 내부 구현을 숨겨서, 애그리거트 단위로 캡슐화할 수 있도록 돕는다.
DDD도 Layered Architecture와 같이 표현, 응용, 도메인, 인프라 스트럭쳐 이렇게 4개의 계층으로 이루어져 있으며 고수준 모듈이 저수준 모듈에 의존하지 않는 구조로 개발이 필요하다.
만약 불가피하게 응용 계층에서 인프라 스트럭쳐의 코드를 사용할 경우 의존역전원칙(DIP)을 이용하여 하위 계층에 의존하지 않는 형태로 개발해야한다.
5. JPA에서의 DDD
DDD에서는 도메인 모델을 관리할 때 비즈니스 로직과 도메인 모델 간의 결합도를 낮추기 위해 리포지토리 패턴을 권장하고 있다.
리포지토리 패턴은 특정 도메인 모델을 관리하는 메서드를 리포지토리라는 이름의 클래스로 구성하여
"이 도메인 모델의 변경은 이 리포지토리만을 통해서만 가능하다"라고 제한하여 유연한 구조를 가져갈 수 있는 설계 패턴이다. 이는 Spring JPA에서의 JpaRepository와 매우 유사하기 때문에 JPA를 사용할 경우 별다른 구성없이 리포지토리 패턴을 사용할 수 있다.
또한, DDD에서는 도메인 모델이 애플리케이션의 핵심이다.
그리고 JPA는 @Entity 어노테이션을 통해 객체 지향적인 방식으로 데이터베이스와 상호작용할 수 있게 해주며, 도메인 객체를 Jpa 엔티티에 그대로 사용할 수 있게 한다.
또한 JPA Entity에 DDD를 적용할 경우, 데이터베이스 테이블에 대한 매핑을 하면서도 도메인 모델의 순수성을 유지할 수 있다. 그리고 JPA는 Persistence Context, Lazy Loading, Cascade, 연관 관계 매핑 등의 기능을 제공하여 DDD의 도메인 모델을 Spring에 쉽게 구현할 수 있게 한다.
결국 JPA는 객체 지향 설계를 지원하고, 도메인 모델을 중심으로 설계된 애플리케이션에서 효과적으로 사용할 수 있는 다양한 기능을 제공하기 때문에 DDD와 자연스럽게 어울릴 수 있다.
하나의 애그리거트에는 반드시 하나의 '루트 애그리거트'가 있다.
그리고 이 루트 애그리거트는 하위 애그리거트들을 관리한다.
그럼 실제 JPA에서는 이 루트 애그리거트와 하위 애그리거트를 어떻게 구현할 수 있을까?
정답은 JpaRepository를 '루트 애그리거트'에만 구현하는 것이다.
이렇게 하면, 루트 애그리거트가 하위 애그리거트들을 관리하고, 영속성 관련 로직도 루트 애그리거트에 집중할 수 있다.
Order (루트 애그리거트) → Order Product (하위 애그리거트) → Receipt (하위 애그리거트) 의 가격 정보를 수정하는 예시
6. MSA에서의 DDD
일부 사람들은 MSA가 보편화되면서 DDD가 주목받게 되었다고 한다. 왜 MSA에서 DDD가 부각되었을까?
그것은 모놀리식 애플리케이션을 MSA로 전환하는 과정에서 서비스를 식별하고 분석 및 설계하는데 DDD가 큰 도움을 주기 때문이다.
복잡한 비즈니스 로직이나 모놀리식 애플리케이션을 MSA로 전환할 때, 가장 중요한 것은 서비스의 경계를 명확히 나누는 것이다.
예를 들어 모놀리식 이커머스 플랫폼을 MSA로 전환할 때는 주문, 상품, 리뷰와 같은 도메인 개념을 기준으로 마이크로 서비스를 구축하게 된다.
DDD는 도메인 모델을 중심으로 비즈니스 로직을 정의하고, 이를 독립적인 도메인 경계로 나눈다.
따라서 MSA와 DDD를 함께 사용하면, 도메인 경게를 기반으로 각 도메인에 해당하는 마이크로서비스를 정의할 수 있다.
이렇게 하면 각 마이크로서비스는 단일 책임 원칙을 따르고, 특정 비즈니스 기능에 집중할 수 있게 된다.
자주 묻는 질문1
JpaRepository는 DB와 관련된 로직인데 인프라 영역인가요?
DDD에서는 jpaRepository도 DB에 의존하기 때문에 인프라스트럭처(InfraStructure) 영역에 속한다.
따라서, 의존성을 줄이기 위해 Repository 인터페이스는 도메인 영역에 위치시키고, 인프라스트럭처에 있는 JpaRepository가 이 인터페이스를 참조하도록 DIP(Dependency Inversion Principle)를 적용해야한다.
하지만, 리포지토리와 도메인 모델의 구현 기술이 거의 바뀌지 않는 경우가 많기 때문에 개발의 편의성을 위해 JpaRepository를 도메인 영역에 두는 것도 합리적인 선택이다. (Trade-Off)
자주 묻는 질문2
JPA의 엔티티와 DDD의 엔티티가 같은건가요?
아니다. JPA의 엔티티와 DDD의 엔티티는 개념적으로 다르다.
하지만 둘은 함께 사용할 수 있고, 실제로 많은 부분에서 겹친다.
기준 | JPA의 엔티티 | DDD의 엔티티 |
목적 | 데이터베이스와의 매핑 | 도메인 모델링 |
중점 | Persistence, CRUD | 비즈니스 로직, 캡슐화, 불변성 |
기준 | 데이터베이스 스키마와 맞춤 | 도메인 요구사항 및 비즈니스 규칙에 맞춤 |
식별자 | @Id 으로 정의된 식별자 | 고유 식별자, 비즈니스 식별자 |
참고
스파르타코딩클럽 Spring 심화 1기 DDD 특강 (남동현 튜터님)
https://dev-coco.tistory.com/166
'프로젝트 > 스프링심화1기' 카테고리의 다른 글
[중간발표] B2B2C SaaS 대기열 서비스 (5) | 2024.10.11 |
---|---|
Chapter 5. 팀 프로젝트 2주차 WIL (0) | 2024.10.07 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!