1. 개요
서비스 중에 일부분에서 외부 API에 요청을 날리고, boolean 타입의 응답을 받는 로직이 포함되어 있었다. 만약 true이면 특별 유저로 간주하고 프론트에서 다른 화면을 보여주는 구성이었다. 간헐적으로 404 에러가 내려오는 케이스가 있었는데, 이 경우엔 fallback 메서드를 통해 false로 처리해두었다.
트래픽이 몰리자 클라이언트에서 전달받은 유저ID에 대해 유저 데이터가 없는 케이스가 늘어났고, 서킷 브레이커가 열리게 되었다. 때문에 기본 응답으로 false가 내려가면서 특별 유저라 하더라도 일반 유저가 보는 화면을 보게 되었다.
hystrix는 resilience4j와 다르게 특정 예외를 골라서 무시할 수 있는 옵션이 없어서 상당히 애를 먹었다. resilience4j는 다음과 같은 옵션으로 간단하게 특정 예외를 무시할 수 있다.
recordExceptionsAsSuccess:
- org.springframework.web.client.HttpClientErrorException
물론 @HystrixCommand를 쓰는 방법이 있긴 한데, feign client에서 붙여쓰는 방법엔 적절하지 않다. @HystrixCommand를 통해 핸들링하기 전에 feign client 내부적으로 예외를 던져버린다.
@HystrixCommand(ignoreExceptions = { BaseException.class, MissingServletRequestParameterException.class, TypeMismatchException.class })
2. 해결
몇 시간의 삽질 후 찾아낸 방법은 HystrixBadRequestException을 이용하는 것이었다. HystrixBadRequestException은 hystrix 내에서 fallback이나 서킷 브레이커를 발동시키지 않는 특별한 예외다.
ErrorDecoder를 작성해서 404 케이스에 대해서는 HystrixBadRequestException이 떨어지도록 한다. 이때 client에 꼭 Configuration을 적용해주자.
// MemberClientConfig
@Bean
fun errorDecoder() =
object : ErrorDecoder {
private val errorDecoder: ErrorDecoder = ErrorDecoder.Default()
override fun decode(methodKey: String?, response: Response): Exception {
if (HttpStatus.resolve(response.status()) == HttpStatus.NOT_FOUND) {
HystrixBadRequestException("Member API 404 Error")
}
return errorDecoder.decode(methodKey, response)
}
}
@ReactiveFeignClient(
...
configuration = [MemberClientConfig::class]
)
interface MemberClient {
...
}
마지막으로 서비스에서 HystrixBadRequestException을 캐치하면 된다. 여기선 간단하게 작성했지만, 실제로는 HystrixBadRequestException을 그대로 쓰기 보다는 상속해서 별도의 커스텀 예외를 정의해주는 것이 좋다.
suspend fun isSpecialMember(memberId: Long): Boolean {
return try {
memberClient.isSpecialMember(custNo).awaitFirstOrNull()
} catch (e: HystrixBadRequestException) {
// fallback 대신 수행할 로직 정의
false
}
}
3. 참고
'Spring > Spring' 카테고리의 다른 글
[Spring] 스프링의 테스트 컨텍스트 캐싱(Spring TestContext Caching)에 대해 알아보자 (0) | 2023.07.17 |
---|---|
[Spring] @ContextConfiguration이란? (0) | 2023.07.16 |
[Spring] Reactive Feign에서 커스텀 Serializer/Deserializer 사용하기 (0) | 2023.03.21 |
[Spring] @Bean 애너테이션의 name 속성과 value 속성 (0) | 2023.03.07 |
[Spring] 빈 중복 시 해결방법(@Autowired, @Qualifier, @Primary) (0) | 2022.12.19 |