Search

유스 케이스를 구현하기 위한 ‘애플리케이션’ 서비스

Status
UPLOADING
Date
2024/04/25
Tags
DDD
애플리케이션 서비스는 도메인 객체를 서로 협조하게 해서 유스케이스를 구현해야 합니다.

1. 도메인과 서비스

도메인 주소 설계 (DDD) 에서 말하는 서비스는 다음과 같습니다.
도메인 서비스
어플리케이션 서비스

1-1. 도메인 서비스 예시

public class User { private Long id; private String name; private int age; } public class UserService { public isUserDuplicated(User user){ ... } }
Java
복사
도메인 서비스는 도메인을 위한 서비스 입니다. 도메인 객체를 만들다보면 필연적으로 도메인 객체로 나타내기에 부담스러운 로직들이 존재합니다.
에를 들어, User 도메인 객체가 있다고 가정해보겠습니다. 유저가 직접 자신의 이름와 동일한 유저가 이미 가입하는지 확인하는 유효성 검사를 가지고 있다고 생각하면 부담스럽습니다. 어쨌든 User가 직접 Repository를 참조해서 동일한 User 가 있는지 확인해야 하기 때문입니다.
이러한 경우에 도메인 서비스를 사용합니다. 도메인을 위해 중복 검사를 하는. 즉, 특정 역할에 대해 부자연스러움을 해결하도록 도와주는 것이 도메인 서비스입니다.

1-2. 하지만 남용한다면

UserService는 물론 User와 관련된 모든 비즈니스 로직을 처리할 수 있습니다. 하지만 도메인 객체와 관련된 모든 역할을 도메인 서비스에 정의한다면, 도메인은 오직 껍데기 (Getter, Setter) 만 가지고 있게 됩니다.
위와 같이 코드를 작성하게 되면 어떤 기능이 어떤 도메인 서비스에 위임되었는지 다 찾아봐야 합니다. 이러한 도메인 객체를 빈혈 도메인 객체라고 합니다.
그리고 빈혈 도메인 객체는 객체와 연관된 데이터와 행위를 모아둔다는 객체 지향 설계의 기본 원칙을 위반하는 아주 좋은 예시입니다.

1-3. 다시 강조하자면

DDD에서는 도메인 관련된 로직은 모조리 도메인 객체에 넣어야 합니다.
하지만 분명 상황에 따라서 하나의 도메인 서비스에 넣어야 하는지 판단이 애매할 때가 존재합니다.
1.
여러 조건에 의하여 해당 도메인 객체에서 로직을 수행할 수 없을 때
2.
두 개 이상의 도메인 간 처리 방향이 모호할 때
다음과 같은 상황이 있다고 가정해보겠습니다. "어떤 도메인 객체를 다른 도메인 객체로 전환하는 경우"
Payment라는 도메인을 가지고 Purchase라는 도메인으로 변환하는 경우 "하나 이상의 도메인 객체를 사용할 경우"
Item, User 도메인이 있고 두 도메인 정보를 이용해서 '유저가 이 아이템을 샀을 때의 잔액 계산하기'
무엇인가를 계산하거나, 상태를 변경한다면 도메인 서비스 클래스입니다.
PurchaseFeeCalculator(구매시 어떤 비용을 계산해주는 클래스)
UserAuthorizationSupport(유저 권한을 체크해주는 클래스)
PointUsePriorityResolver(포인트 사용 우선순위를 반환해주는 클래스)

2. 어플리케이션 서비스

핵심 도메인 모델과 상호 교류하며 이를 지원하기 위해 잘 조합된 컴포넌트의 집합
각각의 영역에 어떤 것들이 들어가는지는 어플리케이션마다 다르며, 사용하는 아키텍처가 무엇인지에 따라서도 달라진다
쉽게 말해 어플리케이션 서비스는 도메인 로직의 유스케이스를 담당합니다. 내부에서 다양한 로직을 엮어 사용자의 유스 케이스를 구현합니다.
참고로 유스 케이스는 기능 시나리오 혹은 기능 명세라고 생각할 수 있습니다.
예를 들어, 어떤 사용자가 당근마켓을 통해 물건을 중고거래 한다고 가정해보겠습니다.
1.
유저에 대한 인증을 진행
2.
트랙섹션 시작
3.
사용자의 지역을 기반으로 중고 물품을 조회
4.
중고 물품 판매자 정보를 조회
5.
채팅 시작
6.
중고 물품에 대한 결제 요청을 PG사에 전송
7.
거래 완료
이러한 흐름을 어플리케이션 서비스에서 담당합니다. 또한, 여러 애그리거트의 데이터를 묶어 전송해야 하는 경우에도 사용합니다. (여러 번 호출 할 필요가 없게)
스프링에서는 @Transactional과 같은 어노테이션을 사용해서 작업 단위를 명시하기도 합니다.

2-1. 어플리케이션 서비스에 대한 오해

저는 어플리케이션 서비스가 도메인 서비스를 포함한다고 생각했습니다. 하나의 어플리케이션은 프로젝트 안에 있는 코드라고 생각했습니다.
애플리케이션 서비스는 이용자의 문제를 해결하기 위한 것입니다. 우리가 어떤 프로젝트를 할 때, 문제점을 파악하고 이를 해결하기 위해 새로운 서비스를 론칭합니다. 도메인 서비스는 다른 프로젝트와 동일할 수 있습니다. 예를 들어, 유저를 생성하는 기능은 어떤 서비스나 포함하고 있습니다. 하지만 유저를 생성한 뒤 해당 유저가 어떤 결과를 도출 하는지는 서비스의 방향성에 따라 다릅니다.
따라서 '서비스의 방향성'이 곧 '애플리케이션 서비스'라고 생각합니다.
+애플리케이션 서비스는 매개 변수 혹은 반환값의 타입을 최대한 도메인에 의존적이지 않게 설게해야 합니다.
// Bad case public void registerAccount(Account account); // Good case public void registerAccount(CreateAccountRequest request);
Java
복사

Reference