Netflix Feign - распространяет статус и исключение с помощью микросервисов

Я использую Netflix Feign для вызова одной операции Microservice A другой другой операции Microservice B, которая проверяет код, используя Spring Загрузка.

Работа Microservice B выдает исключение в случае, если проверка была плохой. Затем я обработал в Microservices и вернул HttpStatus.UNPROCESSABLE_ENTITY (422) следующим образом:

@ExceptionHandler({
       ValidateException.class
    })
    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
    @ResponseBody
    public Object validationException(final HttpServletRequest request, final validateException exception) {
        log.error(exception.getMessage(), exception);
        error.setErrorMessage(exception.getMessage());
        error.setErrorCode(exception.getCode().toString());
        return error;
    }

Итак, когда Microservice A вызывает B в интерфейсе следующим образом:

@Headers("Content-Type: " + MediaType.APPLICATION_JSON_UTF8_VALUE)
@RequestLine("GET /other")
void otherOperation(@Param("other")  String other );

@Headers("Content-Type: " + MediaType.APPLICATION_JSON_UTF8_VALUE)
@RequestLine("GET /code/validate")
Boolean validate(@Param("prefix") String prefix);

static PromotionClient connect() {

    return Feign.builder()
        .encoder(new GsonEncoder())
        .decoder(new GsonDecoder())
        .target(PromotionClient.class, Urls.SERVICE_URL.toString());
}

и сбой проверки. Он возвращает внутреннюю ошибку 500 со следующим сообщением:

{
  "timestamp": "2016-08-05T09:17:49.939+0000",
  "status": 500,
  "error": "Internal Server Error",
  "exception": "feign.FeignException",
  "message": "status 422 reading Client#validate(String); content:\n{\r\n  \"errorCode\" : \"VALIDATION_EXISTS\",\r\n  \"errorMessage\" : \"Code already exists.\"\r\n}",
  "path": "/code/validate"
}

Но мне нужно вернуть то же самое, что и операция Microservice B.

Какими будут лучшие способы или методы распространения статуса и исключений с помощью микросервисов с использованием Netflix Feign?

Ответ 1

Вы можете использовать feign ErrorDecoder

https://github.com/OpenFeign/feign/wiki/Custom-error-handling

Вот пример

public class MyErrorDecoder implements ErrorDecoder {

    private final ErrorDecoder defaultErrorDecoder = new Default();

    @Override
    public Exception decode(String methodKey, Response response) {
        if (response.status() >= 400 && response.status() <= 499) {
            return new MyBadRequestException();
        }
        return defaultErrorDecoder.decode(methodKey, response);
    }

}

Для spring, чтобы забрать ErrorDecoder, вы должны поместить его в ApplicationContext:

@Bean
public MyErrorDecoder myErrorDecoder() {
  return new MyErrorDecoder();
}

Ответ 2

Бесстыдный штекер для небольшой библиотеки, которую я использовал, использует отражение для динамически реконструированных проверенных исключений (и не отмечено, если они находятся в интерфейсе Feign) на основе кода ошибки, возвращаемого в тело ответа.

Дополнительная информация о файле readme: https://github.com/coveo/feign-error-decoder

Ответ 3

Запишите свой настраиваемый блок отображения и зарегистрируйте его. Вы можете настроить ответы.

Полный пример здесь

public class GenericExceptionMapper implements ExceptionMapper<Throwable> {

    @Override
    public Response toResponse(Throwable ex) {
        return Response.status(500).entity(YOUR_RETURN_OBJ_HERE).build();
    }

}

Ответ 4

Мы делаем следующее:

Поделиться общей банкой, которая содержит исключения с обоими микросервисами.

1.) В микросервисах. Исключение преобразования в класс DTO позволяет использовать ErrorInfo. Который будет содержать все атрибуты вашего настраиваемого исключения с String exceptionType, который будет содержать имя класса исключений.

2.) Когда он получен в микросервисе B, он будет обрабатываться ErrorDecoder в микросервисе B и попытается создать объект исключения из exceptionType, как показано ниже:

@Override
public Exception decode(String methodKey, Response response) {       

ErrorInfo errorInfo = objectMapper.readValue(details, ErrorInfo.class);
Class exceptionClass;

Exception decodedException;

try {

    exceptionClass = Class.forName(errorInfo.getExceptionType());  

    decodedException = (Exception) exceptionClass.newInstance();

    return decodedException;

 }

 catch (ClassNotFoundException e) {

    return new PlatformExecutionException(details, errorInfo);

 }
  return defaultErrorDecoder.decode(methodKey, response);
 }

Ответ 5

С 2017 года мы создали библиотеку, которая делает это на основе аннотаций (что делает довольно простым, как и для запросов и т.д., Кодирование этого с помощью аннотаций).

в основном это позволяет вам кодировать обработку ошибок следующим образом:

@ErrorHandling(codeSpecific =
    {
        @ErrorCodes( codes = {401}, generate = UnAuthorizedException.class),
        @ErrorCodes( codes = {403}, generate = ForbiddenException.class),
        @ErrorCodes( codes = {404}, generate = UnknownItemException.class),
    },
    defaultException = ClassLevelDefaultException.class
)
interface GitHub {

    @ErrorHandling(codeSpecific =
        {
            @ErrorCodes( codes = {404}, generate = NonExistentRepoException.class),
            @ErrorCodes( codes = {502, 503, 504}, generate = RetryAfterCertainTimeException.class),
        },
        defaultException = FailedToGetContributorsException.class
    )
    @RequestLine("GET /repos/{owner}/{repo}/contributors")
    List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}

Вы можете найти его в организации OpenFeign: https://github.com/OpenFeign/feign-annotation-error-decoder

Отказ от ответственности: я участник feign и главный разработчик для этого декодера ошибок.

Ответ 6

OpenFeign FeignException не привязывается к определенному HTTP-состоянию (то есть не использует аннотацию Spring @ResponseStatus), что делает Spring по умолчанию FeignException 500 когда сталкивается с FeignException. Это нормально, потому что FeignException может иметь множество причин, которые не могут быть связаны с определенным статусом HTTP.

Однако вы можете изменить способ, которым Spring обрабатывает FeignExceptions. Просто определите ExceptionHandler который обрабатывает FeignException так, как вам это нужно:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(FeignException.class)
    public String handleFeignStatusException(FeignException e, HttpServletResponse response) {
        response.setStatus(e.status());
        return "feignError";
    }

}

В этом примере Spring возвращает тот же статус HTTP, который вы получили от Microservice B. Вы можете пойти дальше, а также вернуть исходное тело ответа:

response.getOutputStream().write(e.content());