Netflix Feign - распространение статуса и исключения через микросервисы
Я использую Netflix Feign для вызова одной операции Microservice A для другой операции Microservice B, которая проверяет код с помощью Spring Boot.
Операция Микросервис B выдает исключение в случае неудачной проверки. Затем я занялся микросервисами и вернул 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"
}
Но мне нужно вернуть то же, что и в микросервисной операции Б.
Каковы лучшие способы или методы для распространения статуса и исключений через микросервисы с использованием Netflix Feign?
4 ответа
Вы могли бы использовать симуляцию 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);
}
}
Для весны, чтобы забрать ErrorDecoder, вы должны поместить его в ApplicationContext:
@Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}
Бесстыдный плагин для небольшой библиотеки, которую я сделал, которая использует отражение для динамического переброса проверенных исключений (и непроверенных, если они на интерфейсе Feign) на основе кода ошибки, возвращенного в теле ответа.
Более подробная информация в readme: https://github.com/coveo/feign-error-decoder
FeignException OpenFeign не привязывается к определенному HTTP-состоянию (то есть не использует Spring @ResponseStatus
аннотации), что делает Spring по умолчанию 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());
Напишите ваш собственный картограф исключений и зарегистрируйте его. Вы можете настроить ответы.
Полный пример здесь
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
@Override
public Response toResponse(Throwable ex) {
return Response.status(500).entity(YOUR_RETURN_OBJ_HERE).build();
}
}
С 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 и главный разработчик для этого декодера ошибок.
Что мы делаем следующим образом:
Совместно использовать общий jar-файл, содержащий исключения для обоих микросервисов.
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);
}