Chapter6 응용 서비스 표현 영역
6.1 표현 영역과 응용 영역
- 도메인이 제 기능을 하려면 사용자와 도메인을 연결하는 매개체가 필요하다.
- 표현 영역은 사용자 요청을 해석한다.
- HTTP 요청을 해석하고 응용서비스를 실행한다.
- 응용 서비스가 요구하는 형식으로 사용자 요청을 변환한다.
- 응용 영역은 사용자가 원하는 기능을 제공한다.
6.2 응용 서비스의 역할
- 응용 서비스는 주로 도메인 객체 간 흐름을 제어하기에 단순한 형태를 갖는다.
public Result doSomeFunc(SomeReq req) {
// 1. 리포지터리에서 애그리거트를 조회
SomeAgg agg = someAggRepository.findById(req.getId());
// 2. 애그리거트의 도메인 기능을 실행
agg.doFunc(req.getValue());
// 3. 결과를 리턴
return createSuccessResult(agg);
}
- 응용 서비스가 복잡하다면 응용 서비스에서 도메인 로직 일부를 구현하고 있을 가능성이 높다.
- 응용 서비스는 트랜잭션 처리 외에 접근 제어와 이벤트 처리도 담당한다.
6.2.1 도메인 로직 넣지 않기
- 도메인 로직을 도메인 영역과 응용 서비스에 분산해서 구현하면 코드 품질에 문제가 발생한다.
- 코드의 응집성이 떨어져 도메인 로직을 파악하기 위해 여러 영역을 분석해야 한다.
- 도메인 로직의 중복 가능성이 높아진다.
- 코드 품질에 문제가 발생하면 결과적으로 변경을 어렵게 만들고 소프트웨어 가치가 떨어진다.
6.3 응용 서비스의 구현
- 응용 서비스 자체는 복잡한 로직을 수행하지 않는다.
- 디자인 패턴에서 파사드(facade)와 같은 역할을 한다.
6.3.1 응용 서비스의 크기
응용 서비스의 크기를 고려해 두 가지 방법 중 하나로 구현할 수 있다.
- 한 응용 서비스 클래스에 도메인의 모든 기능 구현하기
- 도메인의 모든 기능이 한 클래스에 위치하므로 동일 로직에 대한 코드 중복을 제거할 수 있다.
- 한 서비스 클래스의 크기가 커진다는 것은 단점이다.
- 코드 크기가 커지면 관련 없는 코드가 뒤섞여 코드 이해에 방해가 된다.
- 엄연히 분리하는 것이 좋은 상황임에도 습관적으로 기존 서비스 클래스에 억지로 끼워 넣게 된다.
- 구분되는 기능별로 응용 서비스 클래스를 따로 구현하기
- 클래스 개수가 많아지게 된다.
- 한 클래스에 모두 구현하는 것과 비교해 코드 품질을 일정 수준으로 유지하는 데 도움이 된다.
- 각 서비스마다 중복 코드를 구현할 가능성이 있다.
- 공통 로직을 제공하는 별도 클래스를 구현하면 해결할 수 있다.
import static com.myshop.member.application.MemberServiceHelper.*;
public class ChangePasswordService {
private final MemberRepository memberRepository;
public void changePassword(String memberId, String curPw, String newPw) {
// MemberServiceHelper의 static 메서드
Member member = findExistingMember(memberRepository, memberId);
member.changePassword(curPw, newPw);
}
}
6.3.2 응용 서비스의 인터페이스와 클래스
응용 서비스에 인터페이스는 크게 필요하지 않다.
- 인터페이스가 필요한 경우는 구현 클래스가 여러 개인 경우다.
- 응용 서비스의 구현 클래스가 두 개인 경우는 드물다.
- 인터페이스는 런타임에 구현 객체를 교체해야 할 때 유용하다.
- 응용 서비스는 런타임에 교체하는 경우가 거의 없다.
- 테스트를 위한 가짜 객체가 필요할 때 인터페이스를 추가할 수도 있다.
- Mockito와 같은 테스트 도구 덕에 인터페이스 없이도 테스트가 가능하다.
6.3.3 메서드 파라미터와 값 리턴
- 응용 서비스가 기능을 실행하기 위해 필요한 값을 파라미터로 전달 받아야 한다.
- 각 값을 개별적으로 전달받을 수도 있지만 여러개가 있다면 데이터 전달을 위한 별도 클래스를 사용하는 것이 편리하다. (DTO)
- 응용 서비스가 표현 영역에 결과 값을 리턴하는데 대표적인 예가 식별자다.
- 애그리거트 자체를 리턴할 수 있지만 추천하지 않는다.
- 도메인 로직 실행은 응용 영역과 표현 영역 두 곳에서 할 수 있게 된다.
- 이는 도메인 로직을 분산시켜 코드 응집도를 낮추게 된다.
6.3.4 표현 영역에 의존하지 않기
- 응용 서비스의 파라미터 타입을 정할 때 표현 영역과 관련한 타입을 사용하면 안 된다.
- ex)
HttpServletRequest
, HttpSession
등
- 응용 서비스가 표현 영역을 의존하면 응용 서비스 단독으로 테스트하기 어려워진다.
- 표현 영역이 변경되었을 때 응용 서비스도 함께 변경해야 하는 문제가 발생한다.
- 응용 서비스가 표현 영역의 역할까지 대신하는 상황이 벌어질 수도 있다.
6.4 표현 영역
표현 영역의 책임은 크게 다음과 같다.
- 사용자가 시스템을 사용할 수 있는 흐름(화면)을 제공하고 제어한다.
- 사용자의 요청을 알맞은 응용 서비스에 전달하고 결과를 사용자에게 제공한다.
- 사용자의 세션을 관리한다.
6.5 값 검증
- 값 검증은 표현, 응용 영역 두 곳에서 모두 수행할 수 있다.
- 표현 영역에서 사용자 입력을 검사하게 되면 표현 영역 코드가 다소 번잡해진다.
- 응용 서비스에서 입력 값을 검사하게 되면 좋지 않은 사용자 경험을 제공한다.
- 특정 입력이 잘못됐다는 것만 알려주고 모든 항목에 대해 잘못된 값이 있는지는 알려주지 않기 때문
- 이는 사용자가 같은 폼에 값을 여러 번 입력하게 만든다.
- 응용 서비스에서 사용자 불편을 해결하는 방법으로 에러 코드를 모아 하나의 익셉션으로 발생시키는 방법도 있다.
- 예외가 발견될 때마다 컬렉션에 저장된 뒤 컬렉션이 비어 있지 않다면 예외를 발생시키면 된다.
- 표현 영역에서는 필수 값, 값 형식, 범위 등을 검증하고 응용 서비스에선 논리적 오류를 검증하게할 수도 있다.
- 필자는 응용 서비스에서 검증 관련 로직을 모두 처리하고 있다.
- 응용 서비스의 코드가 늘어나기는 하지만 응용 서비스의 완성도가 높아지는 이점이 있다.
6.6 권한 검사
- 특정 시스템은 사용자 권한 검사가 복잡해 실행 가능한 기능이 역할마다 달라지는 경우도 있다.
- 스프링 시큐리티 같은 프레임워크는 유연하고 확장 가능한 구조를 가지고 있다.
- 유연한 만큼 복잡하기에 이해가 부족하면 직접 권한 기능을 구현하는 것이 유지 보수에 유리할 수 있다.
- 보안 프레임워크 복잡도를 떠나 표현, 응용, 도메인에서 권한 검사를 수행할 수 있다.