Узнайте, как создать декларативный и легко читаемый клиент REST без написания шаблонного кода для вызова служб через HTTP.

Выполнение HTTP-запросов между службами в Java довольно просто. При наличии ряда хорошо известных HTTP-клиентов с открытым исходным кодом, таких как OkHttp и RestTemplate в Spring, выбрать подходящего кандидата не составляет труда. Настоящая проблема в том, что нас ждет впереди.

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

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

Именно здесь на помощь приходит Spring Cloud OpenFeign. Spring Cloud OpenFeign - это не просто HTTP-клиент, но и решение для решения проблем, связанных с современными REST-клиентами. Spring Cloud OpenFeign обеспечивает интеграцию OpenFeign для Spring Boot посредством автоконфигурации и привязки к среде Spring.

OpenFeign, первоначально известный как Feign и спонсируемый Netflix, разработан, чтобы позволить разработчикам использовать декларативный способ создания HTTP-клиентов посредством создания аннотированных интерфейсов без написания какого-либо шаблонного кода. Spring Cloud OpenFeign использует Ribbon для обеспечения балансировки нагрузки на стороне клиента, а также для хорошей интеграции с другими облачными сервисами, такими как Eureka для обнаружения сервисов и Hystrix для отказоустойчивости. Все они предоставляются «из коробки» без написания кода.

В этой статье вы узнаете, как использовать Spring Cloud OpenFeign для создания декларативного и удобочитаемого клиента REST для вызова служб через HTTP. В более поздней части вы также научитесь настраивать ленту с конечными точками для балансировки нагрузки в OpenFeign. И, наконец, мы включим клиент Eureka в вашем сервисе Spring REST для интеграции с Eureka Server.

Создать проект Maven

Вы можете использовать веб-сайт Spring Initializr для создания проекта Maven с помощью Spring Boot 2.x. Добавьте в свой проект зависимости Spring Web, OpenFeign и Ribbon.

Если вы начинаете с пустого проекта Maven, импортируйте POM Spring Cloud Dependencies, чтобы ваш проект унаследовал все версии артефактов в семействе Spring Cloud.

<!-- spring-cloud -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-dependencies</artifactId>
  <version>Hoxton.SR6</version>
  <scope>import</scope>
  <type>pom</type>
</dependency>

Затем добавьте модули Spring Boot Starter Web, Spring Cloud Starter OpenFeign и Spring Cloud Starter Netflix Ribbon в зависимость вашего проекта.

<!-- spring -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency><dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

Как вариант, вы можете скачать весь проект с GitHub.

Создание класса запуска приложения

Как и в случае с любым другим приложением Spring Boot, нам нужен основной класс для запуска Spring ApplicationContext. Создайте класс приложения с аннотацией @SpringBootApplication, при этом основной метод входа вызывает SpringApplication.run() Spring Boot для запуска приложения.

@SpringBootApplication
@EnableFeignClients
public class Application {

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

@SpringBootApplication включает сканирование компонентов и автоконфигурацию в нашем приложении. Мы добавляем аннотацию @EnableFeignClients к нашему классу приложения, чтобы включить сканирование компонентов для интерфейсов, помеченных @FeignClient.

Создайте клиентский интерфейс REST

Создайте интерфейс PostmanEchoClient, аннотируйте его аннотацией @FeignClient и назовите его postman-echo. Spring автоматически просканирует наш интерфейс и создаст реализацию для нашего клиента REST во время выполнения.

@FeignClient(name = "postman-echo")
public interface PostmanEchoClient {
}

Spring использует имя postman-echo, поставляемое с аннотацией @FeignClient, в качестве идентификатора для создания RibbonClient для балансировщика нагрузки на стороне клиента.

Есть несколько способов снабдить созданный RibbonClient конечной точкой сервера для целей балансировки нагрузки, например, с помощью конфигурации Java, свойств приложения Spring или интеграции с Eureka Server для поиска конечной точки сервера.

Чтобы перейти от жестко заданного URL-адреса конечной точки сервера к решению с балансировкой нагрузки, давайте настроим Ribbon со статическим listOfServers для Bow. В файле application.yaml в разделе src/main/resources/ добавьте следующие свойства:

postman+echo:
  ribbon:
    listOfServers: https://postman-echo.com/, https://postman-echo.com/

Postman Echo - это сервис, который мы можем использовать для тестирования наших клиентов REST и выполнения примеров вызовов API. Он предоставляет конечные точки для GET, POST и PUT с различными механизмами аутентификации.

Документацию по конечным точкам, а также примеры ответов можно найти на официальном сайте Postman Echo.

Добавить клиентский метод GET-запроса

Затем добавьте getEcho клиентский метод в клиентский интерфейс Postman Echo, который принимает параметры запроса String foo и String bar и возвращает объект EchoGetResponse. Добавьте _24 _ / _ 25_ к этому GET-запросу, используя аннотацию @GetMapping для этого метода. Этот клиентский метод будет вызывать конечную точку GET-запроса Postman Echo при вызове.

@GetMapping(
    path = "/get", 
    consumes = "application/json")
EchoGetResponse getEcho(
    @RequestParam("foo") String foo, 
    @RequestParam("bar") String bar
    );

Конечная точка GET-запроса Postman Echo будет отображать все предоставленные параметры запроса в виде тела ответа в элементе args соответственно. Документацию по GET-запросу Postman Echo можно найти здесь.

{
  "args": {
    "foo": "abc",
    "bar": "123"
  }
}

Создайте новый класс EchoGetResponse для представления ответа JSON. EchoGetResponse - это простой старый класс Java-объекта (POJO). Ответ JSON будет десериализован как EchoGetResponse в нашем клиентском методе запроса Postman Echo GET.

public class EchoGetResponse {

  private Args args;

  public Args getArgs() {
    return args;
  }

  public void setArgs(Args args) {
    this.args = args;
  }

  public static class Args {

    private String foo;

    private String bar;

    public String getFoo() {
      return foo;
    }

    public void setFoo(String foo) {
      this.foo = foo;
    }

    public String getBar() {
      return bar;
    }

    public void setBar(String bar) {
      this.bar = bar;
    }
  }
}

Тестирование клиентского метода GET-запроса с помощью SpringBootTest

Мы используем SpringBootTest для модульного тестирования вызова нашего клиентского метода запроса GET к удаленной конечной точке GET-запроса Postman Echo. Для этого создайте PostmanEchoClientTests класс. Установите контекст теста для использования случайного порта с аннотацией @SpringBootTest .

@SpringBootTest(
    webEnvironment = WebEnvironment.RANDOM_PORT)
class PostmanEchoClientTests {
}

Автоматически подключите созданный Spring bean-объект PostmanEchoClient к нашему классу модульного тестирования. Ожидается, что созданный компонент будет использовать настроенный список серверов ленты для балансировки нагрузки на стороне клиента.

@Autowired private PostmanEchoClient client;

Создайте метод getEcho @Test, который использует автоматически подключенный компонент PostmanEchoClient для вызова конечной точки GET-запроса Postman Echo.

@Test
void getEcho() throws Exception {
  
  final EchoGetResponse response = 
      client.getEcho("abc", "123");
  
  assertThat(
      response.getArgs().getFoo()
      ).isEqualTo("abc");
  assertThat(
      response.getArgs().getBar()
      ).isEqualTo("123");
  
}

После вызова удаленной конечной точки убедитесь, что возвращенный ответ EchoGetResponse соответствует предоставленным аргументам запроса, отметив, правильно ли он десериализован из ожидаемого содержимого JSON, как задумано.

Добавить клиентский метод POST-запроса

Теперь, когда мы протестировали простой запрос GET, давайте попробуем запрос POST. Добавьте postEcho клиентский метод в PostmanEchoClient интерфейс, который принимает параметры запроса String foo и String bar. Он должен иметь EchoPostRequest тело запроса объекта и возвращать EchoPostResponse объект.

Добавьте _45 _ / _ 46_ к этому запросу POST, используя аннотацию @PostMapping для этого метода. Этот клиентский метод будет вызывать конечную точку POST-запроса Postman Echo при вызове.

@PostMapping(
    path = "/post", 
    consumes = "application/json")
EchoPostResponse postEcho(
    @RequestParam("foo") String foo,
    @RequestParam("bar") String bar,
    @RequestBody EchoPostRequest body);

Конечная точка POST-запроса Postman Echo будет отображать предоставленные параметры запроса и запрашивать body в качестве ответа в элементах args и data соответственно.

{
  "args": {
    "foo": "abc",
    "bar": "123"
  },
  "data": {
    "message": "hello"
  }
}

Создайте новый класс EchoPostRequest для представления тела JSON-запроса. EchoPostRequest - это простой старый класс Java-объекта (POJO), который содержит единственное свойство String сообщения. Объект EchoPostRequest будет сериализован в тело JSON-запроса при вызове конечной точки POST-запроса Postman Echo.

public class EchoPostRequest {

  private String message;

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }
}

Создайте еще один новый класс EchoPostResponse для представления ответа JSON. Класс EchoPostResponse содержит args со свойствами foo и bar и data со свойством message. Ответ JSON будет десериализован как EchoPostResponse в нашем клиентском методе POST-запроса Postman Echo.

public class EchoPostResponse {

  private Args args;

  private Data data;

  public Args getArgs() {
    return args;
  }

  public void setArgs(Args args) {
    this.args = args;
  }

  public Data getData() {
    return data;
  }

  public void setData(Data data) {
    this.data = data;
  }

  public static class Args {

    private String foo;

    private String bar;

    public String getFoo() {
      return foo;
    }

    public void setFoo(String foo) {
      this.foo = foo;
    }

    public String getBar() {
      return bar;
    }

    public void setBar(String bar) {
      this.bar = bar;
    }
  }

  public static class Data {

    private String message;

    public String getMessage() {
      return message;
    }

    public void setMessage(String message) {
      this.message = message;
    }
  }
}

Тестирование клиентского метода POST-запроса с помощью SpringBootTest

Опять же, мы добавим postEcho тестовый метод в ранее добавленный класс PostmanEchoClientTests для недавно добавленного postEcho клиентского метода. Точно так же используйте autowired bean PostmanEchoClient для вызова конечной точки POST-запроса Postman Echo.

@Test
void postEcho() {
  
  final EchoPostRequest request = 
      new EchoPostRequest();
  request.setMessage("xyz");

  final EchoPostResponse response = 
      client.postEcho("abc", "123", request);

  assertThat(
      response.getArgs().getFoo()
      ).isEqualTo("abc");
  assertThat(
      response.getArgs().getBar()
      ).isEqualTo("123");
  assertThat(
      response.getData().getMessage()
      ).isEqualTo("xyz");
  
}

После вызова удаленной конечной точки убедитесь, что возвращенный ответ EchoPostResponse соответствует предоставленным аргументам запроса и телу запроса, и убедитесь, что он правильно десериализован из ожидаемого содержимого JSON.

Интеграция с Eureka Server

С помощью простой аннотации и настройки вы можете быстро включить клиент обнаружения Netflix Eureka в своей службе Spring REST.

Перед этим добавьте в свой проект модуль зависимости spring-cloud-starter-netfliix-eureka-client.

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

Затем добавьте аннотацию @EnableDiscoveryClient к своему классу приложения, чтобы активировать реализацию клиента обнаружения Netflix Eureka. Затем он зарегистрируется в реестре служб Netflix Eureka Server и будет использовать абстракцию Spring Cloud DiscoveryClient для опроса метаданных службы, содержащих конечные точки служб, которые клиент ленты будет использовать для балансировки нагрузки.

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class Application {

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

Наконец, настройте имя приложения и URL-адрес конечной точки для вашего Eureka Server в application.yaml.

Клиент Eureka использует имя вашего приложения для регистрации на сервере Eureka. Без настройки имени приложения ваша служба будет отображаться на сервере Eureka как неизвестная.

spring:
  application:
    name: feignclient
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka

Выполнив эти несколько шагов, вы интегрировали службу Spring REST в Eureka Server.

Теперь, когда ваша служба REST готова к подключению к серверу Eureka, вам нужно удалить или прокомментировать свойство listOfServers для ленты из application.yaml.

# postmanEcho:
#   ribbon:
#     listOfServers: https://postman-echo.com/

Создайте сервер Eureka

Используйте веб-сайт Spring Initializr для создания другого проекта для Eureka Server с зависимостью Eureka Server.

Если вы начинаете с пустого проекта Maven, импортируйте POM Spring Cloud Dependencies и добавьте в свой проект зависимость Spring Cloud Starter Netflix Eureka Server.

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

Или загрузите проект Eureka Server с GitHub.

Вам необходимо создать стандартный основной класс записи с аннотацией @SpringBootApplication. Добавьте аннотацию @EnableEurekaServer в свой класс, чтобы активировать реализацию Eureka Server.

@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {

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

Затем измените порт вашего сервера на 8761.

server:
  port: 8761

Наконец, отключите функции саморегистрации и получения реестра в application.yaml.

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka
    registerWithEureka: false
    fetchRegistry: false

На этом ваш Eureka Server готов. Начни это.

Поскольку Postman Echo является внешней службой, вам необходимо вручную зарегистрировать ее на сервере Eureka Server. Вы можете получить список операций REST, поддерживаемых Eureka, здесь. Вы будете использовать API только для регистрации нового приложения.

Используйте Postman, чтобы отправить запрос на ваш сервер Eureka.

URL-адрес для регистрации Postman Echo на сервере Eureka следующий:

http://localhost:8761/eureka/apps/POSTMAN-ECHO

Запросите контент для регистрации Postman Echo на сервере Eureka следующим образом:

{
  "instance": {
    "app": "POSTMAN-ECHO",
    "hostName": "postman-echo.com",
    "vipAddress": "postman-echo",
    "secureVipAddress": "postman-echo",
    "ipAddr": "54.90.58.153",
    "status": "UP",
    "port": {"$": "80", "@enabled": "true"},
    "securePort": {"$": "443", "@enabled": "true"},
    "healthCheckUrl": "http://postman-echo.com/get",
    "statusPageUrl": "http://postman-echo.com/get",
    "homePageUrl": "http://postman-echo.com",
    "dataCenterInfo": {
      "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", 
      "name": "MyOwn"
    }
  }
}

Вам необходимо зарегистрировать приложение как POSTMAN-ECHO, потому что вы ранее назвали свой @FeignClient postman-echo.

Вы должны получить успешный ответ без содержания.

Откройте панель управления Eureka, и вы должны увидеть, что POSTMAN-ECHO зарегистрирован.

Снова запустите свой PostmanRestClientTests, чтобы убедиться, что все работает без сбоев.

Резюме

Мы узнали, как создать декларативный клиент REST с помощью Spring Cloud OpenFeign, используя Spring Cloud Netflix Ribbon для обеспечения балансировки нагрузки на стороне клиента и обеспечения отказоустойчивости. Мы также узнали, как настроить конечную точку статического сервера для ленты и как интегрироваться с Eureka Server для получения списка конечных точек с зарегистрированным сервером. Весь исходный код доступен на GitHub.

Спасибо за чтение, и я надеюсь, что эта статья оказалась для вас полезной.