풋살장 예약 API 내 구장 예약에 관해 로직을 작성하는 도중 순환 참조 에러가 발생했다...
The dependencies of some of the beans in the application context form a cycle:
reserveController defined in file [reserve-field/build/classes/java/main/com/example/reservefield/controller/ReserveController.class]
┌─────┐
| reserveService defined in file [reserve-field/build/classes/java/main/com/example/reservefield/domain/reserve/ReserveService.class]
↑ ↓
| stadiumService defined in file [reserve-field/build/classes/java/main/com/example/reservefield/domain/stadium/StadiumService.class]
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
내가 작성한 프로젝트는 도메인 별 관련 DB는 해당 서비스안에서 해결한 후에 불러오는 방법을 사용했다.
(reserveRepository 에 해당하는 Query 는 reserveService 에서 메서드로 만든 후, StadiumService 등 다른 Service 에서 사용했다.)
그 과정에서 StadiumService 에는 ReserveService 가 선언되고, ReserveService 에는 StadiumService 가 선언되어 각각의 파일에서 불러오고 불러오다보니 순환 에러가 발생한 것이다.
현재 문제는 Service -> Service 의 같은 단계에서 순환되어 발생되는 문제이다.
(StadiumService)
@Transactional
public Stadium getStadiumById(Long id) {
return stadiumRepository.findById(id)
.orElseThrow(() -> new CustomException(HttpStatus.NOT_FOUND, "구장 정보가 존재하지 않습니다."));
}
(ReserveService)
...
Stadium stadium = stadiumService.getStadiumById(stadiumId);
...
(ReserveService)
...
Stadium stadium = stadiumRepository.findById(id)
.orElseThrow(() -> new CustomException(HttpStatus.NOT_FOUND, "구장 정보가 존재하지 않습니다."));
...
위처럼 처리하면 각각의 하위 단계인 Repository 파일에서 다른 Repository 를 참조하여 사용하지 않기 때문에 원활한 처리가 가능하다.
위 사진처럼 Component Service 파일을 하나두고 그 안에 각각의 Service 를 @Lazy 로 불러오는 방법을 사용하면 각각의 Service 파일에서 불러올 수 있다.
@Getter
@Component
public class CircularService {
private final StadiumService stadiumService;
private final ReserveService reserveService;
public CircularService(
@Lazy StadiumService stadiumService,
@Lazy ReserveService reserveService
) {
this.stadiumService = stadiumService;
this.reserveService = reserveService;
}
}
(StadiumService)
@Transactional
public Stadium getStadiumById(Long id) {
return stadiumRepository.findById(id)
.orElseThrow(() -> new CustomException(HttpStatus.NOT_FOUND, "구장 정보가 존재하지 않습니다."));
}
(ReserveService)
...
Stadium stadium = stadiumService.getStadiumById(stadiumId);
...
(StadiumService)
@Transactional
public Stadium getStadiumById(Long id) {
return stadiumRepository.findById(id)
.orElseThrow(() -> new CustomException(HttpStatus.NOT_FOUND, "구장 정보가 존재하지 않습니다."));
}
(ReserveService)
...
Stadium stadium = Stadium stadium = circularService.getStadiumService().getStadiumById(reserve.getStadium().getId());
...
Repository 를 이용하여 처리하는게 수월해보이지만,프로젝트 내 상황을 보았을 때, Image 를 처리하는 과정에서 ImageService 내 사진을 저장하는 메서드를 꼭 불러와 사용하여야 되기 때문에 CircularService 파일을 만들어 @Lazy 로 처리하는 방식을 사용했다.
사용하려는 Service 를 매번 추가해주는 것을 감안하면 조금 복잡하지만, 프로젝트 내 상황을 고려하면 이게 맞다고 생각했다.
프로젝트에 맞는 방식을 고려하여 채택하는 것도 중요하니 각자의 최선을 방식을 고려해보고 선택했으면 좋겠다.