spring open feign, error handling 하기

2022-10-27
  • feign
  • spring
  • spring cloud open feign 을 도입하긴 했는데, error handling 을 하다보니 고민이되어 글로 정리 한다.

    요구사항

    • connection error 이건 400 이상의 http status 이건, 제대로된 응답을 받지 못할 경우 동일한 응답을 받고싶다. (defined error dto)

    결론

    decoder 만 써보기도하고, fallback 만 써보기도하고, feign client method 의 return type 을 dto, feign.Response 모두 써보기도 했다.

    error decoder 와 fallback factory, 그리고 @ExceptionHandler 모두를 써야 원하는 결과를 얻을 수 있었다.

    보통 블로그의 예제들을 보면, custom error decoder 를 구현하고 decoded exception 을 직접 ExceptionHandler 에서 처리하는 예제가 많이 보인다.

    하지만 circuit breaker 를 사용하게되면, decoded exception 은 fallback 에 의해 무시되게 되는데, 이럴땐 fallback 을 직접 구현하지말고 fallback factory 를 통해 cause 를 넘겨주도록 구한하여, fallback 에서 cause 에 따라 적절한 exception 을 던져야한다.

    그리고 ExceptionHandler 에서 IllegalStateException 로 잡게되면, IllegalStateException.cause.targetException 에 들어있는 decoded exception 을 사용할 수 있게된다. 그리고 ExceptionHandler 에서 decoded exception 로 잡아서 처리하면된다. (위의 부분은 circuit breaker 버전이 낮은 경우 그렇다. 아마 3.1 이상이면 되는듯. FeignCircuitBreakerInvocationHandler.invoke 함수의 fallback exception 처리 확인 참고. unwrapAndRethrow(exception) 을 사용하면 높은 버전임)

    추가로 actuator 에서 circuit breaker 의 상태를 보기위한 설정

    management:
      health:
        circuitbreakers:
          enabled: true # 필수, /health 에서 circuitbreakers 항목이 보이게된다.
      endpoint:
        health:
          show-details: always # 필수, 안하면 /health 에서 server 의 status 만 보이게되고 나머지 상세정보(circuitbreaker 포함)가 아무것도 안보이게된다.
      endpoints:
        web:
          exposure:
            include: info, caches, configprop, mappings, env, refresh, health, prometheus, circuitbreakers # 선택, /actuator/circuitbreakers 활성화
      server:
        port: ${actuator.port:8081}
      metrics:
        tags:
          application: ${spring.application.name}
    
    resilience4j:
      circuitbreaker:
        configs:
          default:
            register-health-indicator: true # 필수, /health 에서 circuitbreakers 상태를 제공한다. 없으면 unkonwn 으로 표시됨
    

    추가로 알게된 사실들

    • 아래 설정은 circuit breaker 사용하기 위한 필수 설정임. 이거 그냥 circuit breaker 관련 bean 이 있으면 될 줄 알았는데, 아니었음..
      feign:
      circuitbreaker:
        enabled: true
      
    • org.springframework.cloud:spring-cloud-starter-openfeign 3.1.0 버전과 3.1.3 버전(아마도 이후?) 은 fallback 에서 나온 exception 을 handling 하는 방식이 다르기 때문에, 3.1.3 버전을 사용해야 @ExceptionHandler 를 사용할때 깔끔하게 사용할 수 있다.
    • 3.1.0 ``` java // FeignCircuitBreakerInvocationHandler 93 line Function<Throwable, Object> fallbackFunction = throwable -> { Object fallback = this.nullableFallbackFactory.create(throwable); try { return this.fallbackMethodMap.get(method).invoke(fallback, args); } catch (Exception e) { throw new IllegalStateException(e); // <– 이부분 } }; return circuitBreaker.run(supplier, fallbackFunction);
    * 3.1.3
    ``` java
    // FeignCircuitBreakerInvocationHandler 95 line
    			Function<Throwable, Object> fallbackFunction = throwable -> {
    				Object fallback = this.nullableFallbackFactory.create(throwable);
    				try {
    					return this.fallbackMethodMap.get(method).invoke(fallback, args);
    				}
    				catch (Exception exception) {
    					unwrapAndRethrow(exception); // <-- 이부분
    				}
    				return null;
    			};
    
    • feign 은 classpath 에 bulkhead(io.github.resilience4j:resilience4j-feign 에 속해 있음) 가 있는 경우, bulkhead 패턴을 사용하는데.. 이부분은 좀 더 학습이 필요하다.

    참고