Originally posted by **HoFe-U** August 23, 2023
# 도메인 주도 개발(Domain-Driven-Design)
> 이 책을 읽게된 경위는 먼저 기본적인 아키텍처 구조를 layered 밖에 모르고 JPA 를 공부하면서
Entity 나 Domain 쪽에대한 내용이 나왔기때문에 해당 부분에 대해 학습을 하고자 이책을 읽습니다.
>
## 1. 도메인 모델 시작하기
**도메인(Domain) 이란?**
- 소프트웨어로 해결하고자 하는 문제 영역, 즉 도메인(Domain) 에 해당한다.
- 하나의 도메인은 여러개의 하위 도메인으로 나뉘어 질 수 있다.
## 2. 네 개의 영역(아키텍처)
> 일단 아키텍처란 무엇일까??
**아키텍처**
→ 시스템 혹은 소프트웨어 구조의 상호작용 방식을 결정하는 체계적인 설계
>
>
> 어떤걸 생각하며 작성해야 할까?
>
> - 보안적 측면
> - 유지보수성
> - 패턴과 프레임워크
>
> 등을 고려하면서 작성해야 할것같다.
>
**그럼 아키텍처를 설계하기위한 필수요소들은 무엇이 있을까?**
**Application** **Presentation**
**Domain** **Infrastructure**
![structure](https://user-images.githubusercontent.com/88470887/262562418-132bfe83-0554-4853-91d3-a22cd5c1e513.png
)
위의 4가지 영역이 필요하다.
상단의 그림처럼 구성되어 있는데 이를 하나씩 알아보고자 한다.
### Presentation 영역
> 상단의 그림을 보면 Presentation 영역(UI 영역 이라고도 불림)은 사용자의 요청을 받아 응용 영역에 전달하고 영역의 처리결과를 다시 사용자에게 보여주는 역할을 한다.
>
기본적인 UI 부분이나 API end-point 부분이 해당영역을 대신한다고 볼 수 있다.
그러니까 Presentation 영역이 우리가 흔히 mvc 모델에서 말하는 controller 의 영역이라고 할 수 있다.
### Application 영역
> 상단의 그림을 보면 Application 영역은 사용자의 요청을 Presentation 영역에서 전달받아
처리하는 역할을 한다.
>
기본적으로 주문 생성, 주문 취소 등 비즈니스 로직을 처리하는 역할을 한다.
### Domain 영역
> 상단의 그림에서 Application 영역이 Domain 으로 넘어가는 것을 볼 수 있다.
Domain 영역이 `주문취소` 를 실행한다면 이를 직접적으로 로직을 수행하는것이 Domain 영역이다.
>
### InfraStructure 영역
> 상단의 그림에는 나와 있지않지만 InfraStructure 영역은 구현 기술에 대한 것을 다룬다.
이 영역은 RDBMS 연동을 처리하거나 Message 처리를 한다.
>
즉 데이터베이스의 Repository 영역, 외부 API 영역은 여기에 해당한다고 볼 수 있다.
(그 외 Redis, Kafka 도 마찬가지다)
## 3. 계층 구조 아키텍처 (Layered Architecture)
> 위의 4개의 영역을 구성할때 많이 쓰는 방법이 Layered Architecture 인데
이 구조는 특성상 상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에
의존하지 않는 특징있다.
>
자 이때 구현의 편리함을 위해 아래와 같이 구성을 하면
![presentation](https://user-images.githubusercontent.com/88470887/262567212-d6c8b9c6-aa5a-4846-bde8-82e93e1d9f79.png
)
위의 그림을 보게 되면 편의를 위해서 infraStructure 에 의존적인걸 볼 수있다.
이렇게 되면 2가지 문제가 발생하는데 특정 Service 가 DB 모듈에만 의존하고 있으면
**DB모듈을 변경시에** 문제가 발생한다.
1. 테스트의 어려움
2. 기능 확장의 어려움
그럼 위와같은 2가지의 어려움이 발생할때 어떻게 해결할 수 있을까??
### DIP(Dependency Injection Principal)
> 일단 DIP 을 이해하기 전에 모듈의 종류 부터 이해하고자 한다.
>
- 고수준 모듈
- 일반적으로 ‘주문 취소’, ‘가격 할인 계산’ 등 비즈니스로직의 하나의 로직을 처리할 수 있으면 고수준의 모듈로 볼 수 있다.
- 저수준 모듈
- 상위의 비즈니스로직을 처리하는거 즉 `RDBMS 는 JPA` 로 `Drools 로 룰을 적용` 등이 저수준의 모듈이라고 할 수 있다.
자 이제 2가지 모듈에 대한 정의를 알았으니 앞서 있던 문제를 해결해 보고자 한다.
1. 테스트의 어려움
2. 기능 확장의 어려움
위의 2가지문제를 해결하기 위해서는 DIP 를 통해
```css
저수준 모듈이 고수준의 모듈을 의존하도록 변경하면된다.
```
우리가 잘아는 추상화를 통해 injection 을 받으면된다.
```css
public class CalculateDiscountService {
private DroolsRuleEngine ruleEngine;
public Money calculateDiscount(OrderLine orderLines, String customerId) {
Customer customer = findCusotmer(customerId);
MutableMoney money = new MutableMoney(0);
facts.addAll(orderLines);
ruleEngine.evalute("discountCalculation", facts);
return money.toImmutableMoney();
}
}
```
위와같은 Service 가 있으면 해당 Service 는 `DroolsRuleEngine` 에 의존적인데 이를 아래와 같이 변경하는것이다.
```java
public interface RuleDiscounter {
public Money applyRules(Customer customer, List orderLines);
}
```
추상화를 시켜서 RuleDiscounter 를 만들고
```java
public class CalculateDiscountService {
private CustomerRepository customerRepository;
private RuleDiscounter ruleDiscounter;
public Money calculateDiscount(OrderLine orderLines, String customerId) {
Customer customer = customerRepository.findCusotmer(customerId);
return ruleDiscounter.applyRules(customer, orderLines);
}
}
```
service 코드를 위와같이 변경하고 기존의 `DroolsRuleDiscounter` 가 `RuleDiscounter` 를 의존하게 변경을하면 된다.
```java
public class DroolsRuleDiscounter implements RuleDiscounter{
private KieContainer kContainer;
@Override
public void applyRules(Customer customer, List orderLines) {
...
}
}
```
그리고 기존의 `DroolsRuleDiscounter` 가 `RuleDiscounter` 을 상속받게 하면 된다.
이렇게되면 `CalculateDiscountService` 는 `Drools` 에 의존하는 코드가 없고 단지 `RuleDiscounter` 가 룰을 적용한다는 사실만 알고만 있으면 되게 만든다.
즉 고수준의 모듈이 `RuleDiscounter` 와 `CalculateDiscountService` 가 되는거다.
다시한번 정리하면 기존의 고수준 모듈이 저수준 모듈을 사용하려면 고수준 모듈이 저수준 모듈에 의존해야하는데
이를 반대로 저수준 모듈이 고수준 모듈을 의존한다고 해서 이를 DIP 라고 부른다.
![module](https://user-images.githubusercontent.com/88470887/262571355-79959f16-3f7c-4615-8b3f-376353173d52.png
)
### DIP 주의사항
DIP를 잘못 생각하면 단순히 인터페이스와 구현 클래스를 분리하는 정도로 받아들일 수 있다. DIP의 핵심은 고수준 모듈이 저수준 모듈에 의존하지 않도록 하기 위함인데 DIP를 적용한 결과 구조만 보고 아래 그림과 같이 저수준 모듈에서 인터페이스를 추출하는 경우가 있다.
![interface](https://user-images.githubusercontent.com/88470887/262579110-893c601b-b855-4511-8ec1-e8408d0b1a91.png
)
위와같은 경우가 잘못된 경우라고 볼 수 있다.
이유는 위의 구조는 구현 기술을 다루는 인프라스트럭처 영역에 의존하고 있다.
여전히 고수준 모듈이 저수준 모듈에 의존하고 있는것이다.
정리하자면 DIP 를 적용할 때 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출하고
`CalculateDiscountService` 에서는 할인 금액을 구하기 위해 룰 엔진을 사용하는지 직접 연산을 하는지는 중요하지 않아진다.
단지 규칙에 따라 할인 금액을 계산하는것이 중요하다.
즉 ‘**할인 금액 계산**’을 추상화한 인터페이스는 저수준 모듈이 아닌 고수준 모듈에 위치한다.
![high-value](https://user-images.githubusercontent.com/88470887/262580000-c7f3403f-9ed1-40b0-9424-d469f9f0ea5b.png
)
## 4. 도메인 영역의 주요 구성요소
> 앞에서 네 영역에 대해 설명하면서 도메인 영역은 도메인의 핵심 모델을 구현한다고 말했는데
도메인 영역의 모델은 도메인의 주요 개념을 표현하며 핵심 로직을 구성한다.
이제 도메인 영역의 주요 구성요소에 대해 알아보고자 한다.
>
1. **Entity (엔티티 or 엔터티) :**
- 도메인 모델의 핵심 요소로 유일한 식별자(ID)를 가지고 있는 객체
- 데이터베이스에 영속적으로 저장되며, 비즈니스 규칙과 데이터를 함께 포함
- 주문(Order) , 고객(Customer) 등이 예시
2. **Value (Value Object (밸류, 밸류 오브젝트 )) :**
- **Immutable** 하며, 여러 속성을 하나로 묶어 표현하는 객체
- Entity 와 달리 식별자가 없고, 동등성(Equality) 비교를 통해 동일성을 판단.
- 주소(Address), 화폐 금액(Money) 등이 예시.
3. **Aggregate( 애그리거트 ) :**
- 관련된 Entity 와 Value Object 의 그룹을 하나의 단위로 취급하는 개념
- 하나의 Aggregate 는 Root Entity 를 통해 내부 구조를 다룬다.
- 즉 **Order entity, OrderLine Value, Orderer Value** 를 **‘Order’ Aggregate** 로 묶을 수 있다.
4. **Repository(리포지터리 or 레포지토리) :**
- 도메인 객체(Domain 및 값 객체) 를 저장하고 조회하는 Interface.
- 데이터베이스와 상호작용을 캡슐화하며, 도메인 로직과 infrastructure 을 분리.
- OrderRepository 가 예시일수 있다.
5. **Domain Service (도메인 서비스) :**
- 특정 Entity 에 속하지 않은 도메인 로직을 제공
- 즉 ‘할인 금액 계산’ 은 상품, 쿠폰, 회원 등급, 구매 금액 등 다양한 조건을 이용해서 구현하는데 이렇게 되면 여러 Entity 와 Value 를 필요로 하면 Domain Service 에서 로직을 구현
- 복잡한 비즈니스 규칙이나 계산을 다루는데 사용됩니다.
Layer 로 보면 아래처럼 볼 수 있다.
```java
[ Domain Layer ]
│
│ ┌─────────────────────────┐
│ │ Entity │
│ ├─────────────────────────┤
│ │ - id │
│ │ - properties │
│ └─────────────────────────┘
│ ▲ ▲
│ │ │
│ ┌─────────┐ ┌───────────┐
│ │ Value │ │ Aggregate │
│ │ Object │ ├───────────┤
│ │ │ │ - id │
│ └─────────┘ │ - entity │
│ │ - value │
│ └───────────┘
│ ▲
│ │
│ ┌─────────────────┐
│ │ Repository │
│ ├─────────────────┤
│ │ - save() │
│ │ - findById() │
│ └─────────────────┘
│ │
│ ┌─────────────────┐
│ │ Domain Service │
│ ├─────────────────┤
│ │ - processOrder()│
│ │ - calculateTotal│
│ └─────────────────┘
│
```
위에서는 주요구성 요소를 간단하게 알아봤는데 이제 세부적으로 한번 쪼개보고자 한다.
### 엔티티와 벨류
> 우리가 Entity 하면 흔히 말하는 DB 에서의 Entity 라고 많이들 생각하는것 같다
하지만 실상은 다르다고 볼 수 있다.
>
**DB Entity 와 Domain Entity 같나?**
**다르다**
→ 이유는 Domain 모델의 Entity는 데이터와 함께 Domain 기능을 함께 제공하는 점이 있기때문이다.
```java
public class Order {
// 주문 도메인 모델의 데이터
private OrderNo number;
private Orderer orderer;
private ShippingInfo shippingInfo;
...
// 도메인 모델 엔티티는 도메인 기능도 함께 제공
public void changeShippingInfo(ShippingInfo newShippingInfo) {
verifyNotYetShipped();
setShippingInfo(newShippingInfo);
Events.raise(new ShippingInfoChangedEvent(number, newShippingInfo));
}
}
```
상단의 코드를 보면 도메인 모델의 Entity 는 데이터와 함께 Domain 기능을 함께 제공한다.
즉 단순히 데이터를 담고있는것이 아니라 데이터와 함께 기능을 제공하는 객체라고 할 수 있다.
두 번째는 도메인 모델의 엔티티는 두 개이상의 데이터가 개념적으로 하나인 경우 밸류 타입을 이용해서 표현할 수 있다.
상단의 코드를 보면 Orderer 는 Value 임으로 다음과 같이 주문자 이름과 이메일을 데이터를 표현할 수 있다.
아래와 같이 말이다.
```java
public class Orderer {
private String name;
private String email;
...
}
```
그리고 Value Object 경우 불변으로 구현할 것을 권장하는데 이는 Value Type 데이터를 변경할 때는 객체 자체를 완전히 교체한다는 것을 의미한다.
### 애그리거트
> 도메인이 커질수록 개발할 도메인 모델도 커지면서 많은 Entity 와 Value 가 나온다. 이때 Entity 와 Value 가 많아질수록 모델은 더 복잡해 진다. 이를 해결하기위한게 Aggregate 이다.
>
애그리거트는 관련된 객체를 하나로 묶은 군집이다.
주문 이라는 도메인 개념은 `주문` , `배송지 정보` , `주문자`,`주문 목록`, `총 결제 금액`의 하위 모델로 구성된다. 이 하위 개념을 표현한 모델을 하나로 묶어서 `주문`이라는 상위 개념으로 표현할 수 있다.
![aggregate](https://user-images.githubusercontent.com/88470887/262591717-8140cd9f-6aab-4750-9ecb-193ce2193754.png
)
즉 군집에 속한 객체를 관리하는 Root Entity 를 같는다고 볼 수 있다.
이때 Root Entity 는 애그리거트에 속해 있는 Entity 와 Value 객체를 이용해서 애그리거트가 구현해야 할 기능을 제공한다.
### 리포지터리
> 도메인 객체를 지속적으로 사용하려면 RDBMS, NOSQL, File 과 같은 물리적인 저장소를 활용해서 도메인 객체를 저장해야 한다. 이를 위한 모델이 Repository 이다.
>
Repository 는 일반적으로 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다.
예시는 아래와 같다.
```java
public interface OrderRepository {
Optional findById(OrderNo id);
void save(Order order);
}
```
`OrderRepository` 의 메서드를 보면 대상을 찾고 저장하는 단위가 애그리거트 루트인 `Order` 인 것을 알 수 있다.
`Order` 는 애그리거트에 속한 모든 객체를 포함하고 있으며 결과적으로 애그리거트 단위로 저장 및 조회한다.
이때 도메인 모델 관점에서는 `OrderRepository` 는 도메인 객체를 영속화하는 데 필요한 기능을 추상화한 것으로 고수준 모듈에 속한다.
특정 기술을 이용해서 OrderRepository 를 구현한 클래스는 저수준 모듈로 인프라스트럭처 영역에 속한다.
### 인프라스트럭처 개요
> 인프라스트럭처(infrastructure) 는 표현 영역, 응용 영역, 도메인 영역을 지원한다. 도메인 객체의 영속성 처리, 트랜잭션, SMTP 클라이언트, REST 클라이언트 등 다른 영역에서 필요로 하는 프레임워크, 구현 기술, 보조 기능을 지원한다.
앞서 DIP 에서 도메인 영역과 응용 영역에서 인프라스트럭처의 기능을 직접 상요하는것 보다 이 두 영역에서 정의한 인터페이스를 인프라스트럭처 영역에서 구현하는 것이 시스템을 더 유연하고 테스트하기 쉽게 만든다.
>
**그럼 위에서 말한대로 무조건적으로 인프라스트럭처에 대한 의존을 없애야할까 ? ? ?**
→ 아니다. 구현의 편리함을 버리고서 DIP 가 주는 장점을 갖는 다면 모순인게 DIP의 장점을 해치지 않는 범위 에서 기술에 대한 의존을 가져가는것은 나쁘지 않다고 본다.
뭐 큰 예시가 있다면 `@Transactional` 일 것이다.
`@Transcational` 로 처리하면 한 줄로 트랜잭션을 처리할 수 있는데 스프링에 대한 의존성을 없애려고 복잡한 설정을 사용할 필요가 없는것이다.
### 모듈 구성
> 패키지 구성 규칙에 정답이 있는것은 아니지만 영역별로 모듈이 위치할 패키지를 구성할 수 있다.
>
한단의 그림을 보면 특정 도메인 별로 기능들을 분리한것을 볼수있다.
![domain-1](https://user-images.githubusercontent.com/88470887/262599127-27747bb5-912a-415b-ad65-8519df9b6af7.png
)
다른 예시의 경우에는 도메인에 속한 애그리거트를 기준으로 다시 패키지를 구성할 경우.
예를 들어 카탈로그 하위 도메인이 상품 애그리거트와 카테고리 애그리거트로 구성될 경우 구성될 수 있다.
![domain-2](https://user-images.githubusercontent.com/88470887/262599904-cc76070c-f373-422b-8bf5-281991685a97.png
)
도메인이 복잡해지면 도메인 모델과 도메인 서비스를 다음과 같이 별도 패키지 위치시킬 수 있다.
- com.myshop.order.domain.order : 애그리거트 위치
- com.myshop.order.domain.service : 도메인 서비스 위치
응용 서비스도 다음과 같이 구분할 수 있다.
- com.myshop.catalog.application.product
- com.myshop.catalog.application.category
정리하면 모듈 구조를 얼마나 세분화해야 하는지에 대한 정해진 규칙은 없지만 한 패키지에 너무많은 타입이 몰려
코드를 찾을때 불편할 정도만 아니면 된다. 화자의 생각은 10~15 개 미만으로 타입 개수를 유지하려면 좋다고하는데 아직까지 15 개를 넘어갈 정도는 경험해보지 못한것 같다.
Discussed in https://github.com/FeGwan-Training/FeGwan/discussions/42