The dependencies of some of the beans in the application context form a cycle:
commentController defined in file [/Users/choihyuk/spring/sparta-schedule-plus/build/classes/java/main/org/example/spartascheduleplus/controller/comment/CommentController.class]
┌─────┐
| commentService defined in file [/Users/choihyuk/spring/sparta-schedule-plus/build/classes/java/main/org/example/spartascheduleplus/service/comment/CommentService.class]
↑ ↓
| scheduleService defined in file [/Users/choihyuk/spring/sparta-schedule-plus/build/classes/java/main/org/example/spartascheduleplus/service/schedule/ScheduleService.class]
└─────┘
위와 같은 에러를 보신 적이 있으신가요..?
ShceduleService → ← CommentService
이런 느낌이랄까??
구현하다 보면 Service Layer 간의 의존성이 너무 강해질 때가 있다.
가장 간단한 방법은 Service에서 각 Repository를 필드에 주입하여 사용하면 되지만...
그 코드가 길어진다면..? 어떡하죠??
두가지 방법을 통해 순환참조 오류를 해결해보자.
1. 파사드 패턴
2. @OneToMany
첫번째 방법은 파사드 패턴으로 순환참조 오류를 해결하는 것이다.
파사드(Facade) 패턴은 복잡하게 얽혀있는 서브로직들을 하나로 묶고,
하나의 클래스로 재구축을하여 사용자가 접근하기 편하게 만드는 디자인 패턴이다.
예를들어 우리가 main()
을 실행할 때, 우리는 내부로직에 대해서 자세히는 모르지만,
해당 메서드를 통해 프로그램이 실행되는 것을 알 수 있다.
내 프로젝트로 예를 들자면,
1. 댓글을 수정할 때, 일정정보도 조회해야 하고 댓글엔티티 뿐만아니라 일정엔티티에도 업데이트를 해야한다.
2. 일정정보를 조회할 때, 일정에 달린 댓글목록을 조회해서 반환해줘야 한다.
위의 상황에서 서로의 Service Layer
를 참조하게되었고, 순환참조 오류가 발생하였다.
나의 경우, 파사드패턴이 추구하는 하위 계층에 복잡도에 비하면 약한 수준이다..ㅎㅎ
Controller와 Service 사이에 Facade 패턴을 적용하여
순환참조 오류를 해결해보자.
Facade Layer
는 여러 Service를 주입받고,
인증/인가 부분에 대한 예외처리만을 담당한다.
Service Layer
는 오로지 Repository와의 상호작용을 통해
DB에서 데이터를 가져오고, 데이터에 대한 예외처리만을 담당한다.
// ✅ 컨트롤러 레이어
@GetMapping
public ResponseEntity<SuccessResponseDto<PagedScheduleResponseDto>> findAllSchedules(...) {
// ...
// ✅ 스케줄 파사드를 호출
scheduleFacade.findAll(pageable);
}
// ✅ 파사드 레이어
public PagedScheduleResponseDto findAll(Pageable pageable) {
// ... ✅ 인증/인가 예외처리
// ✅ 스케줄 레이어 호출
Page<Schedule> pagedSchedule = scheduleService.findAllSchedules(pageable);
List<ScheduleResponseDto> schedules = pagedSchedule
.map(schedule -> {
// ✅ 댓글 레이어 호출
PagedCommentResponseDto comments = commentService.findAllComments(schedule, pageable);
return new ScheduleResponseDto(schedule, comments);
}).getContent();
// ...
}
// ✅ 서비스 레이어 (스케줄)
public Page<Schedule> findAllSchedules(Pageable pageable) {
// ✅ 데이터 예외 처리
return scheduleRepository.findAll(pageable);
}
// ✅ 서비스 레이어 (댓글)
public PagedCommentResponseDto findAllComments(Schedule schedule, Pageable pageable) {
// ... ✅ 데이터 예외 처리
return new PagedCommentResponseDto(
new PageInfo(pagedComment),
scheduleInfoDto,
comments
);
}
코드가 길어서 부분생략했다.
이처럼 단방향 설정과 Facade 클래스를 중간에 추가해서, 순환참조 오류를 해결할 수 있었다.
튜터님께서 내가 분리한 방법은 파사드 패턴이 추구하는 기능별로 분리한 것보다,
비즈니스 로직을 분리한거라고 말씀주셨다.
파사드 패턴은 더욱더 복잡하며, 기능을 하나로 묶을 때 쓰는 디자인 패턴이므로,
나의 경우에는 Controller → Business → Service → Repository
로
비즈니스 계층을 하나 추가하는 방향을 추천해주셨다.
파사드 패턴은 추후 크고 복잡한 프로젝트에는 사용될만한 디자인 패턴이면서,
가장 단순한 디자인 패턴이라고 한다.
나의 경우에는 @OneToMany
어노테이션을 통해 쉽게 해결할 수 있는 정도의
프로젝트 규모이기에 해당 방법을 추천해주셨다.
다음번엔 순환참조 오류를 @OneToMany
어노테이션으로 해결해보는 방법을
작성해보려고한다!
스프링 파사드 패턴
얄코 파사드패턴
스프링 웹계층
파사드패턴으로 서비스 의존성 문제 해결
가볍게 알아보는 디자인 패턴 - 파사드 패턴
야생의 순환참조 오류
내배캠 튜터님들