기록

[bug] SpringBoot/The dependencies of some of the beans in the application context form a cycle 본문

Web/Spring

[bug] SpringBoot/The dependencies of some of the beans in the application context form a cycle

youngyin 2023. 12. 13. 05:58

1. 문제 정의:

서비스 간 순환참조로 인해 스프링 부트 애플리케이션 실행 시 빈 초기화에서 순환 의존성 문제 발생.

The dependencies of some of the beans in the application context form a cycle
┌─────┐
|  challengeService defined in file 
↑     ↓
|  couponService defined in file
└─────┘

도메인 서비스 A와 B 간에 의존성이 존재하며, A는 B를 참조하고 B는 A를 참조하면서 순환참조 문제가 발생. 서비스 A는 ChallengeService를, 서비스 B는 CouponService를 참조하고 있음.

2. 고려사항

서비스 간 의존성 그래프를 분석하여 순환참조를 발생시키는 의존성 구조를 확인. 서비스 간의 의존성을 최소화하면서 문제를 해결할 방법을 고려.

// ChallengeService에서 CouponService를 직접 참조하고 있음
class ChallengeService {
    private val couponService: CouponService // 순환참조 문제 발생
    // ...
}

// CouponService에서 ChallengeService를 직접 참조하고 있음
class CouponService {
    private val challengeService: ChallengeService // 순환참조 문제 발생
    // ...
}

3. 고려한 해결방법

3.1. 서비스 분리:
CouponService와 ChallengeService를 더 작은 서비스로 분리하여 각각 독립적으로 동작하도록 설계. 이를 통해 순환참조를 방지하고 서비스 간의 명확한 경계를 설정.

3.2. 이벤트 기반 아키텍처 도입:
이벤트를 통해 서비스 간 통신하도록 아키텍처를 변경. ChallengeService에서 발생한 이벤트를 CouponService에서 수신하고 필요한 작업 수행.

// ChallengeService
class ChallengeService {
    private val eventBus: EventBus // 이벤트 버스를 통한 통신

    fun processChallengeCompletedEvent(challengeId: String) {
        // Challenge 완료 이벤트 발행
        eventBus.publish(Event(ChallengeCompletedEvent(challengeId)))
    }
}

// CouponService
class CouponService {
    fun onChallengeCompletedEvent(event: ChallengeCompletedEvent) {
        // Challenge 완료 이벤트 수신 및 처리 로직
        issueCoupon(event.challengeId)
    }
}

4. 해결방법

서비스 분리 방법 선택. ChallengeService와 CouponService를 별도의 마이크로서비스로 분리하여 순환참조 문제를 해결.

4.1 ChallengeReviewService 도입 후의 ChallengeService

@Service
class ChallengeService(
    private val converter : ChallengeConverter,
    private val repository: ChallengeRepository,
) : BaseService<ChallengeReq, ChallengeResp, Challenge, String> {

    fun findAll(searchDto : ChallengeSearchDto, pageable: Pageable): Page<ChallengeResp> {
        // 이전 구현 내용과 동일
    }

    override fun findById(id: String): ChallengeResp {
        // 이전 구현 내용과 동일
    }
}

4.2 ChallengeReviewService

@Service
class ChallengeReviewService(
    private val repository: ChallengeRepository,
    private val couponService: CouponService,
) {
    fun review(request: ChallengeReviewReq){
        // 도전 내역 리뷰 로직
        // ...
        // 쿠폰 서비스 호출
        couponService.save(CouponReq(/*...*/))
    }
}

5. 효과와 결과:

서비스 간의 명확한 분리로 순환참조 문제가 사라지고 애플리케이션 초기화 과정에서 발생한 에러가 해결.

6. 기타 고려 사항:

작은 규모의 프로젝트나 단순한 비즈니스 로직을 가진 프로젝트라서, 이벤트 기반 아키텍처를 도입할 필요성이 낮음.
작은 규모의 시스템에서는 단일 모놀리식 아키텍처나 간단한 마이크로서비스 아키텍처가 충분.

Comments