«1. Обзор

Zuul — это пограничный сервис (или шлюз API) от Netflix, который обеспечивает динамическую маршрутизацию, мониторинг, отказоустойчивость и безопасность.

В этом руководстве мы рассмотрим, как настроить маршруты Zuul с запасными вариантами.

2. Первоначальная настройка

Для начала мы настроим два приложения Spring Boot. В первом приложении мы создадим простую службу REST. Принимая во внимание, что во втором приложении мы будем использовать прокси-сервер Zuul для создания маршрута для службы REST первого приложения.

2.1. Простой REST-сервис

Предположим, что нашему приложению необходимо отобразить пользователю сегодняшнюю информацию о погоде. Итак, мы создадим приложение службы погоды на основе Spring Boot, используя стартер spring-boot-starter-web:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Теперь мы создадим контроллер для нашей службы погоды:

@RestController
@RequestMapping("/weather")
public class WeatherController {

    @GetMapping("/today")
    public String getMessage() {
        return "It's a bright sunny day today!";
    }

}

Теперь давайте запустим службу погоды и проверим API службы погоды:

$ curl -s localhost:8080/weather/today
It's a bright sunny day today!

2.2. Приложение шлюза API

Давайте теперь создадим наше второе приложение Spring Boot, шлюз API. В этом приложении мы создадим маршрут Zuul для нашей службы погоды.

И поскольку и наша служба погоды, и Zuul по умолчанию хотят использовать 8080, мы настроим его для работы на другом порту, 7070.

Итак, давайте сначала добавим spring-cloud-starter-netflix- zuul в pom.xml:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

Затем мы добавим аннотацию @EnableZuulProxy в наш класс приложения шлюза API:

@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }

}

Наконец, мы настроим маршрут Zuul, используя ленту, для нашего API службы погоды в application.yml:

spring:
   application:
      name: api-gateway
server:
   port: 7070
  
zuul:
   igoredServices: '*'
   routes:
      weather-service:
         path: /weather/**
         serviceId: weather-service
         strip-prefix: false

ribbon:
   eureka:
      enabled: false

weather-service:
   ribbon:
      listOfServers: localhost:8080

2.3. Тестирование маршрута Zuul

На данный момент оба приложения Spring Boot настроены на предоставление API службы погоды с использованием прокси-сервера Zuul.

Итак, запустим оба приложения и проверим API службы погоды через Zuul:

$ curl -s localhost:7070/weather/today
It's a bright sunny day today!

2.4. Тестирование сбоя маршрута Zuul без отката

Теперь давайте остановим приложение службы погоды и снова проверим службу погоды через Zuul. В результате мы увидим в ответе сообщение об ошибке:

$ curl -s localhost:7070/weather/today
{"timestamp":"2019-10-08T12:42:09.479+0000","status":500,
"error":"Internal Server Error","message":"GENERAL"}

Очевидно, это не тот ответ, который хотел бы видеть пользователь. Итак, один из способов позаботиться об этом — создать запасной вариант для маршрута метеослужбы Zuul.

3. Zuul Fallback для маршрута

Прокси-сервер Zuul использует ленту для балансировки нагрузки, а запросы выполняются в команде Hystrix. В результате сбои на маршруте Зуул появляются в матрице Hystrix.

Поэтому, чтобы создать собственный запасной вариант для маршрута Zuul, мы создадим bean-компонент типа FallbackProvider.

3.1. Класс WeatherServiceFallback

В этом примере мы хотим вернуть сообщение из резервного ответа вместо сообщения об ошибке по умолчанию, которое мы видели ранее. Итак, давайте создадим простую реализацию FallbackProvider для маршрута службы погоды:

@Component
class WeatherServiceFallback implements FallbackProvider {

    private static final String DEFAULT_MESSAGE = "Weather information is not available.";

    @Override
    public String getRoute() {
        return "weather-service";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        if (cause instanceof HystrixTimeoutException) {
            return new GatewayClientResponse(HttpStatus.GATEWAY_TIMEOUT, DEFAULT_MESSAGE);
        } else {
            return new GatewayClientResponse(HttpStatus.INTERNAL_SERVER_ERROR, DEFAULT_MESSAGE);
        }
    }

}

Как видим, мы переопределили методы getRoute и fallbackResponse. Метод getRoute возвращает идентификатор маршрута, для которого мы должны создать запасной вариант. Принимая во внимание, что метод fallbackResponse возвращает настраиваемый резервный ответ, объект типа GatewayClientResponse в нашем случае. GatewayClientResponse — это простая реализация ClientHttpResponse.

3.2. Тестирование запасного варианта Zuul

Теперь давайте проверим резервный вариант, который мы создали для службы погоды. Поэтому мы запустим приложение API Gateway и убедимся, что приложение службы погоды остановлено.

Теперь давайте обратимся к API службы погоды через маршрут Zuul и посмотрим на резервный ответ в действии:

$ curl -s localhost:7070/weather/today
Weather information is not available.

4. Резервный вариант для всех маршрутов

До сих пор мы видели, как создать резервный вариант. для маршрута Zuul, используя его идентификатор маршрута. Однако предположим, что мы также хотим создать общий запасной вариант для всех других маршрутов в нашем приложении. Мы можем сделать это, создав еще одну реализацию FallbackProvider и вернув * или null из метода getRoute вместо идентификатора маршрута:

@Override
public String getRoute() {
    return "*"; // or return null;
}

5. Заключение

В этом руководстве мы видели пример создание запасного варианта для маршрута Zuul. Мы также увидели, как можно создать общий запасной вариант для всех маршрутов Zuul.

«Как обычно, реализацию всех этих примеров и фрагментов кода можно найти на GitHub.