응용 서비스의 역할
- 응용 서비스의 주요 역할은 도메인 객체를 사용해서 사용자의 요청을 처리하는 것이므로 표현(사용자) 영역 입장에서 보았을 때 응용 서비스는 도메인 영역과 표현 영역을 연결해 주는 창구 역할을 한다
- 응용 서비스는 주로 도메인 객체 간의 흐름을 제어하기 때문에 아래와 같이 단순한 형태를 갖는다
public Result dbSomeFunc(SomeReq req) {
// 리포지터리에서 애그리거트 조회
SomeAgg agg = someAggRepository.findById(reg.getId());
// 애그리거트의 도메인 기능 수행
add.doFunc(reg.getValue());
// 사용자가 봐야하는 결과 리턴
return createSuccessResult(agg);
}
- 응용 서비스가 복잡하다면 응용 서비스에서 도메인 로직의 일부를 구현하고 있을 가능성이 높다
- 응용 서비스가 도메인 로직을 일부 구현하면 로직 분산 및 도메인 규칙이 분산되는 것에 영향을 줄 수 있다
- 코드의 응집성이 떨어진다 → 도메인 로직을 파악하기 위해 여러 영역을 분석해야 한다는 것을 의미한다
- 여러 응용 서비스에서 동일한 도메인 로직을 구현할 가능성이 높아진다 → 도메인 로직으로 구현했다면 응용 서비스는 그냥 사용하면 된다
- 소프트웨어가 가져야할 주요 장점은 소프트하다는 것 변경에 유연하게 대처할 수 있어야 한다는 것 이다
- 응용 서비스는 트랜잭션 처리도 담당한다
- 그 외에도 접근 제어와 이벤트 처리 등이 존재한다
응용 서비스의 크기
- 회원과 관련된 응용 서비스는 두 가지 방법 중 한가지 방법으로 구현할 수 있다
- 한 응용 서비스 클래스에 회원 도메인의 모든 기능 구현
- 구분되는 기능별로 응용 서비스 클래스를 따로 구현
- 한 응용 서비스에서 모든 기능 구현의 특징은
- 동일 로직에 대한 코드 중복을 제거할 수 있다
- 그러나 서비스 클래스의 크기가 커진다 → 크기가 커지면 연관성이 적은 코드가 한 클래스에 함께 위치할 가능성이 높아지게 되며 결과적으로 관련 없는 코드가 뒤섞여 코드를 이해하는데 방해가 될 수 있다
- 더불어 한 곳에 모이기 시작하면 엄연히 분리하는 것이 좋은 상황임에도 습관적으로 기존에 존재하는 클래스에 억지로 끼워 넣게 된다
- 구분되는 기능별로 구현하는 것의 특징은
- 한 개 내지는 2~3개 의 기능을 구현하는 방식을 권장한다 (기능별로 생각해서)
- 이로인해 일정 수준의 일관된 유지보수성을 높이는데 도움이 된다
- 필요한 의존 객체만 포함하므로 다른 기능을 구현한 코드에 영향을 받지 않는다
- 코드 중복이 생길 경우 별도 클래스에 로직을 구현해서 코드가 중복되는 것을 방지할 수 있다 (
MemberServiceHelper, ...)
응용서비스와 인터페이스
- 응용 서비스를 구현하면서 논쟁이 될 만한 것이 인터페이스가 필요한 것 인가 이다
- 인터페이스가 필요한 몇가지 상황이 있는데 그중 하나는 구현 클래스가 여러개인 경우이다
- 응용 서비스는 여러개의 구현체를 두는 상황이 드물다
- 그러나 테스트를 위한 가짜 객체를 사용하기 위해서 필요할 순 있다 → 이 또한 외부 라이브러리(
Mockito)를 사용하면 해결할 수 있다
표현 영역에 의존하지 않기
- 응용 서비스의 파라미터 타입을 결정할 때 주의할 점은 표현 영역과 관련된 타입을 사용하면 안된다는 점이다
- 예를 들어 표현 영역에 해당하는
HttpServletRequests, HttpSession 등
- 이럴 경우 응용 서비스 단독으로 테스트하기가 어려워진다
- 게다가 표현 영역이 변경되면 응용 서비스 또한 변경되어야 한다
- 이 보다 더 심각한 것은 응용 서비스가 표현 영역의 역할까지 대신하는 상황이 발생할 수 있다는 것 이다
HttpSession 을 통해 쿠키 정보를 조회 및 처리하는 작업이 있을 경우 응용 서비스에서 처리할 수 있는 방법이 존재하므로 응용 서비스에서 처리하는 경우 → 이것은 결과적으로 코드 유지보수 비용을 증가시키는 원인이 된다
- 더불어 응용 서비스에서 예외가 발생할 경우 이에 따른 표현 영역에서 에러 코드에 알맞는 처리를 해야 한다
public String changePassword() {
try {
service.changePassword();
} catch (BadPasswordException | NoMemberException ex) {
...
}
}
값 검증
- 표현 영역과 응용 서비스 두 곳에서 모두 수행할 수 있다
- 일반적으로는 값에 대한 검증은 응용 서비스에서 처리한다
- 그러나 표현 영역은 잘못된 값이 존재하면 이를 사용자에게 알려주고 값을 다시 입력받아야 한다
- 표현 영역에서 필수 값과 형식을 검사하면 실질적으로 응용 서비스는 ID 중복 여부와 같은 논리적 오류만 검사하면 된다
- 즉 표현 영역과 응용 서비스가 값 검사를 나눠서 수행하는 것 이다
- 표현 영역 : 필수 값, 값의 형식, 범위 등을 검증
- 응용 서비스 : 데이터의 존재 유무와 같은 논리적 오류를 검증