728x90
반응형
도메인 영역의 코드를 작성하다 보면 한 애그리거트
로 기능 구현이 불가능할 때가 존재한다.
바로 결제 금액 계산 로직인데, 실 결제 금액을 계산할 때는 아래 정보들이 필요하다.
- 상품 애그리거트 : 구매하는
상품의 가격
필요 - 주문 애그리거트 : 상품별
구매 개수
필요 - 할인 쿠폰 애그리거트 : 쿠푠별로 지정한
할인 금액
이나 비율에 따라 주문 총 금액을할인하는 정보
필요 - 회원 애그리거트 :
회원 등급에 따른 추가 할인 정보
필요
그렇다면 실제 결제 금액을 계산해야하는 주체는 어떤 애그리거트 일까?
- if. 주문 애그리거트에서 필요한 데이터를 모두 가지게 한다면? (계산 책임을 주문 애그리거트에 할당)
- 할인 정책이 변경되었을 때 주문 애그리거트가 갖고 있는 구성 요소와 관련이 없음에도 결제 금액 계산 책임이 주문 애그리거트에 있기 때문에 해당 코드를 수정해야 한다.
한 애그리거트에 넣기 애매한 도메인 기능은 억지로 특정 애그리거트에 구현하면 안된다. 자신의 책임 범위를 넘어서는 기능을 구현하기 때문이다.
- 코드가 길어짐
- 외부에 대한 의존이 높아짐
- 코드를 복잡하게 만들어 수정을 어렵게 만드는 요인이 됨
- 애그리거트의 범위를 넘어서는 도메인 개념이 애그리거트에 숨어들어 명시적으로 드러나지 않음
!! 도메인 기능(결제 금액 계산 로직)을 별도 서비스로 구현하자
!!
도메인 서비스(Domain Service)
- 도메인 영역에 위치한 도메인 로직을 표현할 때 사용
- 계산 로직 : 여러 애그리거트가 필요한 계산 로직이나, 한 애그리거트에 넣기에는 다소 복잡한 계산 로직
- 외부 시스템 연동이 필요한 도메인 로직 : 구현하기 위해 타 시스템을 사용해야 하는 도메인 로직
계산 로직과 도메인 서비스
- 도메인 서비스를 이용해서 도메인 개념을 명시적으로 드러낸다.
- 응용 영역의 서비스가 응용 로직을 다룬다면 도메인 서비스는 도메인 로직을 다룬다.
- 상태 없이 로직만 구현
// 도메인 서비스 구현
class DiscountCalculationService {
fun calculateDiscountAmounts(
orderLines: List<OrderLine>,
coupons: List<Coupon>,
grade: MemberGrade
) {
val couponDiscount = coupons.stream()
.map(coupon -> calculateDiscount(coupon))
.redouce(Money(0), (v1, v2) -> v1.add(v2))
val membershipDiscount = calculateDiscount(orderer.getMember().getGrade())
return couponDiscount.add(membershipDiscount)
}
private fun calculateDiscount(coupon: Coupon) = ..
private fun calculateDiscount(grade: MemberGrade) = ..
}
이렇게 되면 할인 계산 서비스
를 사용하는 주체는 애그리거트가 될 수도 있고 응용 서비스가 될 수도 있다.
// 아래 경우 사용 주체가 애그리거트가 됨
class Order {
fun calculateAmounts(disCalSvc: DiscountCalculationService, grade: MemberGrade) {
val totalAmounts = getTotalAmounts()
// discount 관련 로직을 Order에서 직접 구현하지 않고, 도메인 객체(disCalSvc)를 이용한다.
val discountAmounts
= disCalSvc.calculateDiscountAmounts(this.orderLines, this.coupons, grade)
this.paymentAmounts = totalAmounts.minus(discountAmounts)
}
}
- 애그리거트 객체에 도메인 서비스를 전달하는 것은
응용 서비스 책임
이다.
애그리거트 메서드를 실행할 때 도메인 서비스를 인자로 전달하지 않고 반대로
도메인 서비스의 기능을 실행할 때 애그리거트를 전달하기도 한다.
// 계좌 이체는 두 계좌 애그리거트가 관여, 한 애그리거트는 출금 / 한 애그리거트는 입금
// 이를 위한 도메인 서비스
class TransferService {
fun transfer(fromAcc: Account, toAcc: Account, amounts: Money) {
fromAcc.withdraw(amounts)
toAcc.credit(amounts)
}
}
- 도메인 서비스는 도메인 로직을 수행하지 응용 로직을 수행하지 않는다.
- 트랜잭션은 응용 로직에서 구현한다.
도메인 서비스 객체를 애그리거트에 주입하지 말자
- 애그리거트 메서드(Order.calculateAmounts)를 실행할 때 도메인 서비스 객체를 파라미터로 전달한다는 것은
애그리거트가 도메인 서비스에 의존
한다는 것을 의미 - 기술적으로 나은 것 같다는 착각을 할 수 있지만 의존한다고 의존 주입으로 처리하면 안된다.
- 프레임워크 기능을 사용하고 싶은 개발자의 욕심을 채우는 것에 불과...
class Order {
// 의존성 주입을 한다면?
@Autowired
private discountCalculationService: DiscountCalculationService
}
Why?
- 도메인 객체는 데이터(프로퍼티) + 메서드를 이용하여 개념적으로 하나의 모델이다.
- discountCalculationService 필드는 데이터 자체와 관련이 없다.
- Order 객체를 DB에 보관할 때 다른 필드와 달리 저장 대상도 아니다.
- Order 가 제공하는 모든 기능에서 discountCalculationService를 필요로 하는 것도 아니다.
- 일부 기능만 필요로 하기 때문에 굳이 의존 주입할 이유는 없다.
외부 시스템 연동과 도메인 서비스
외부 시스템이나 타 도메인과의 연동 기능도 도메인 서비스
가 될 수 있다.설문 조사 시스템
/ 사용자 역할 관리 시스템
이 분리되어 있다고 가정
- 설문조사를 생성할 때, 사용자가 생성 권한을 가진 역할인지 확인하기 위해 역할 관리 시스템과 연동 필요
- 설무 조사 도메인 입장에서는 사용자가 설문 조사 생성 권한을 가졌는지 확인하는 과정을 도메인 로직으로 볼 수 있음
- 이 도메인 로직은 아래의 도메인 서비스로 표현가능
// 역할 관리 시스템 연동 관점이 아니라 도메인 로직 관점에서 인터페이스 작성
interface SurveyPermissionChecker {
fun hasUserCreationPermission(userId: String)
}
응용 서비스는 이 도메인 서비스를 이용해서 생성 권한을 검사
class CreateSurveryService {
private permissionChecker: SurveyPermissionChecker
fun createSurvery(req: CreateSurveryRequest): Long {
validate(req)
// 도메인 서비스를 이용해서 외부 시스템 연동을 표현
if (!permissionChecker.hasUserCreationPermission(req.RequestorId)) {
throw NoPermissionException()
}
}
}
- SurveyPermissionChecker의 구현체는 인프라스트럭쳐 영역에 위치시킨다.
패키지 구조 예시
application
ㄴ OrderService
domain
ㄴ Order
ㄴ OrderRepository\<Interface>
ㄴ DiscountCalculationService\<Interface>
infra
ㄴ RuleBasedDiscountCalculationService (DIP)
출처) 도메인 주도 개발 시작하기 - 최범균
728x90
반응형
'프로그래밍 노트 > 도메인 주도 설계(DDD)' 카테고리의 다른 글
[도메인 주도 설계] 이벤트 활용하기 (0) | 2023.08.22 |
---|---|
[도메인 주도 설계] 도메인 모델과 바운디드 컨텍스트 (0) | 2023.08.21 |
[도메인 주도 설계] 표현 영역과 응용 영역(Presentation Layer, Application Layer) (2) | 2023.08.14 |
[도메인 주도 설계] 애그리거트에 대해 (3) | 2023.08.07 |
[도메인 주도 설계] 아키텍처에 관하여 (0) | 2023.07.23 |