고민한 상황
Controller > Service > Repository 구조의 스프링 프로젝트에서 Service가 다른 Service에 의존할지, 다른 Repository에 의존할지 고민이 되는 순간이 생겼다. 코드로 보자면 아래와 같은 상황이었다.
@Service
public class ReservationService {
private final ReservationRepository reservationRepository;
private final ReservationTimeRepository reservationTimeRepository;
public ReservationResponse saveReservation(ReservationRequest request) {
ReservationTime time = findReservationTimeById(request.timeId());
Reservation reservation = request.toReservation(time);
Reservation savedReservation = reservationRepository.save(reservation);
return ReservationResponse.from(savedReservation);
}
private ReservationTime findReservationTimeById(Long id) {
return reservationTimeRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 시간입니다."));
}
}
public class Reservation {
private Long id;
private String name;
private LocalDate date;
private ReservationTime time;
}
Java
복사
Reservation이 ReservationTime과 연관관계를 맺고 있어 Reservation을 저장하려면 ReservationTime에 대한 조회 쿼리를 함께 날려야 했다. 이때 ReservationService 에서 ReservationTimeRepository의 findById() 함수를 이용할지, ReservationTimeService의 findById() 함수를 이용할지 고민이 되었다. 둘중 뭘 사용해도 결과는 쿼리 실행 결과는 똑같은 상황이었다. 고려할 점은 객체 간 의존성이었다.
Service에서 다른 Service 의존
ReservationService가 ReservationTimeDao를 아는 건 Controller -> Serivce -> Dao 순에서 하위 계층을 참조하고 있기에 순환참조의 위험이 없다. 그러나 중복 코드가 생길 수 있다. 지금 상황에선 ReservationTimeService 에 아래의 함수가 존재했다면, 두 Service 객체 사이에 중복 코드가 존재한다.
@Service
public class ReservationTimeService {
private final ReservationTimeRepository reservationTimeRepository;
private ReservationTime findReservationTimeById(Long id) {
return reservationTimeRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 시간입니다."));
}
}
Java
복사
Service에서 다른 Repository 의존
반면, ReservationService가 ReservationTimeService를 아는 건 순환참조의 위험이 존재한다. 그러나 순환참조가 생기지 않도록 조심한다면 너무 경계할 필요는 없다. 오히려 중복 코드를 제거한단 관점에서 효율적일 수 있다. 현재 상황에선 아래와 같이 고쳐볼 수 있다.
@Service
public class ReservationService {
private final ReservationRepository reservationRepository;
private final ReservationTimeService reservationTimeService;
public ReservationResponse saveReservation(ReservationRequest request) {
ReservationTime time = reservationTimeService.findById(request.timeId());
Reservation reservation = request.toReservation(time);
Reservation savedReservation = reservationRepository.save(reservation);
return ReservationResponse.from(savedReservation);
}
}
Java
복사
선택의 근거
둘 중 뭐가 옳다 하는 정답은 없고, 상황마다 팀마다 다르게 선택할 거 같다. 만일 ResevationService가 사용해야 하는 ReservationTimeService의 메서드가 단순히 dao 접근만 담당하는 게 아니라 그 외의 복잡한 비즈니스 로직을 실행한다면 ReservationTimeService를 사용해 중복 코드를 줄일 거 같다. 현재 상황에선 단순히 dao 접근만 담당하는 전자에 가깝기에 ReservationService가 ReservationTimeService가 아닌 ReservationTimeRepository를 사용하는 방식을 택했다.