[도메인주도개발] Ch.6 : 응용 서비스와 표현 영역
6.1 표현 영역과 응용 영역
도메인이 제 기능을 하기 위해서는 사용자와 도메인을 연결해주는 매개체가 필요하고, 이 역할을 해주는 것이 응용 영역과 표현 영역이다.
표현 영역
사용자의 요청을 해석한다. 사용자가 웹 브라우저에서 폼에 ID와 암호를 입력한 뒤에 전송 버튼을 클릭하면 요청 파라미터를 포함한 HTTP 요청을 포현 영역에 전달한다. 요청을 받은 표현 영역은 URL, 요청 파라미터 쿠키, 헤더 등을 이용해서 사용자가 실행하고 싶은 기능을 판별하고 그 기능을 제공하는 응용 서비스를 실행한다.
응용 영역
실제 사용자가 원하는 기능을 제공하는 것은 응용 영역에 위치한 서비스이다. 응용 서비스는 기능을 실행하는 데 필요한 입력 값을 메서드 인자로 받고 실행 결과를 리턴한다.
응용 서비스의 메서드가 요구하는 파라미터와 표현 영역이 사용자로부터 전달받는 데이터는 형식이 일치하지 않기 때문에 표현 영역은 응용 서비스가 요구하는 형식으로 사용자 요청을 변환한다. 응용 서비스를 실행한 뒤 표현 영역은 실행 결과를 사용자에게 알맞은 형식으로 응답한다. 예를 들어 HTML이나 JSON 형식으로 응답할 것이다.
6.2 응용 서비스의 역할
응용 서비스는 사용차가 요청한 기능을 실행하는데, 이런 요청을 처리하기 위해 리포지터리에서 도메인 객체를 가져와 사용한다. 응용 서비스는 트랜잭션 처리도 담당한다. 트랜잭션 외에 응용 서비스의 주요 역할로 접근 제어와 이벤트 처리도 있다.
응용 서비스는 주로 도메인 객체 간의 흐름을 제어하기 때문에 단순한 형태를 갖는다.
public Result doSomeFunc(SomeReq req){
// 1. 리포지터리에서 애그리거트를 구함.
SomeAgg agg = someAggRepository.findById(req.getId());
checkNull(agg);
// 2. 애그리거트의 도메인 기능을 실행한다.
agg.doFunc(req.getValue());
// 3. 결과를 리턴
return createSuccessResult(agg);
}
새로운 애그리거트를 생성하는 경우,
public Result doSomeCreation(CreateSomeReq req){
// 1. 데이터 중복 등 데이터의 유효성 검사
validate(req);
// 2. 애그리거트 생성
SomeAgg newAgg = createSome(req);
// 3. 리포지터리에 애그리거트 저장
someAggRepository.save(newAgg);
// 4. 결과 리턴
return createSuccessResult(newAgg);
}
만약 응용 서비스가 복잡하다면 응용 서비스에서 도메인 로직의 일부를 담당하고 있을 가능성이 높다. 이것은 좋지 못한데, 응용 서비스가 도메인 로직을 일부 구현하면 코드 중복, 로직 분산 등 코드 품질에 안 좋은 영향을 줄 수 있다.
6.2.1 도메인 로직 넣지 않기
도메인 로직을 응용 서비스에 구현해서는 안된다. 도메인 로직을 도메인 영역과 서비스에 분산해서 구현했을 경우 발생하는 문제는 다음과 같다.
1. 코드의 응집성이 떨어진다.
2. 여러 응용 서비스에서 동일한 도메인 로직을 구현할 가능성이 높아진다.
이는 코드의 변경을 어렵게 만든다. 즉 변경 용이성이 떨어지는데, 이것은 소프트웨어의 가치를 떨어뜨린다.
6.3 응용 서비스의 구현
이 절에서는 응용 서비스를 구현할 때 몇 가지 고려할 사항과 트랜잭션과 같은 구현 기술의 연동에 대해 살펴본다.
6.3.1 응용 서비스의 크기
응용 서비스에서 도메인 모델을 사용하는 경우가 있다. 이 경우 응용 서비스를 구현하는 방식은 두 가지가 있다.
1. 한 응용 서비스 클래스에 도메인의 모든 기능 구현하기
2. 구분되는 기능별로 응용 서비스 클래스를 따로 구현하기
도메인과 관련된 기능을 한 클래스에서 모두 구현하게 될 경우,
- 장점
- 각 기능에서 동일한 로직을 위한 코드 중복을 제거하기 쉽다.
- 단점
- 한 서비스 클래스의 크기(코드 수)가 커진다.
- 코드 크기가 커지면 연관성이 적은 코드가 한 클래스에 위치할 가능성이 높아지는데, 이것은 코드를 이해하는 데에 방해가 된다.
- 분리하는 것이 좋은 상황에도 습관적으로 기존에 존재하는 클래스에 억지로 끼워넣게 된다. 이것은 코드릐 품질을 낮추는 결과를 초래한다.
구분되는 기능별로 서비스 클래스를 구현하는 방식은 한 응용 서비스 클래스에서 1~3 개의 기능을 구현한다.
- 장점
- 코드 품질을 일정 수준으로 유지하는 데에 도움이 된다.
- 각 클래스별로 필요한 의존 객체만 포함하므로 다른 기능을 구현한 코드에 영향을 받지 않는다.
- 단점
- 클래스 개수가 많아진다.
다만 각 기능마다 동일한 로직을 구현할 경우 여러 클래스에 코드가 중복될 수도 있는데, 이는 별도 클래스에 로직을 구현하여 코드가 중복되는 것을 방지할 수 있다.
6.3.2 응용 서비스의 인터페이스와 클래스
응용 서비스를 구현할 때 인터페이스를 만들고 이를 상속한 클래스를 만드는 것이 좋을지에 대한 고민이 있을 수 있다. 인터페이스가 필요한 몇 가지 상황이 있는데 그중 하나는 구현 클래스가 여러 개인 경우이다. 하지만 이런 경우는 흔하지 않다. 또 Mockito와 같은 테스트 도구는 응용 서비스에 대한 인터페이스 없이 표현 영역을 테스트할 수 있도록 해주기 때문에 응용 서비스에 대한 인터페이스 필요성을 약화시킨다.
6.3.3 메서드 파라미터와 값 리턴
응용 서비스가 제공하는 메서드는 도메인을 이용해서 사용자가 요구한 기능을 실행하는 데 필요한 값을 파라미터로 전달받아야 한다. 만약 응용 서비스에 데이터로 전달할 요청 파라미터가 두 갸 이상 존재하는 경우 데이터 전달을 위한 별도 클래스를 사용하는 것이 편리하다.(=DTO 클래스)
업로드 예정