Создание REST-клиента с помощью Spring Cloud OpenFeign и Netflix Ribbon



Книга Создание REST-клиента с помощью Spring Cloud OpenFeign и Netflix Ribbon

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


Среди растущего числа распределенных облачных сервисов, где сервера постоянно появляются и исчезают, конечные точки сервисов оказываются динамичны и их сложно знать наперед. По этой причине, прежде чем отправлять запрос сервису, REST-клиенту необходимо интегрироваться в реестр сервисов и находить там его конечную точку.


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


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


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


В текущей статье вы узнаете:


  • Как с помощью Spring Cloud OpenFeign построить декларативный легко читаемый REST-клиент для вызова сервисов по HTTP.
  • Как настроить Ribbon и конечные точки для балансировки нагрузки. 
  • Как активировать Eureka-клиент в вашем сервисе Spring REST для интеграции с Eureka Server.

Создание Maven-проекта 


Для генерации Maven-проекта со Spring Boot 2.x можно использовать Spring Initializr. Добавьте в этот проект зависимости Spring Web, OpenFeign и Ribbon.


Spring Initializr  —  добавление зависимостей

Если вы начинаете Maven-проект с нуля, то импортируйте Spring Cloud Dependencies POM, чтобы он наследовал все версии артефактов семейства 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.


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


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


@SpringBootApplication
@EnableFeignClients
public class Application {

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

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


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


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


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

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


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


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


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

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


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


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


Далее добавьте в интерфейсе клиента Postman Echo клиентский метод getEcho, который принимает параметры запроса String foo и String bar, возвращая объект EchoGetResponse. Прикрепите к этому GET-запросу path/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 будет десериализован в нашем методе GET-запроса Postman Echo как EchoGetResponse.


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’


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


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

Выполните автоматическое внедрение (autowire) созданного Spring bean-компонента PostmanEchoClient в класс модульного теста. Bean-компонент должен использовать настроенный список серверов Ribbon для балансировки нагрузки на клиентской стороне. 


@Autowired private PostmanEchoClient client;

Создайте метод getEcho с аннотацией @Test, использующий внедренный bean-компонент 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. Добавьте в интерфейс PostmanEchoClient клиентский метод postEcho, принимающий параметры запроса String foo и String bar. У него должно быть тело запроса объекта EchoPostRequest, а возвращаться им должен объект EchoPostResponse.


Добавьте к этому POST-запросу path/post при помощи аннотации @PostMapping. При вызове этот клиентский метод будет вызывать конечную точку POST-запроса Postman Echo.


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

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


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

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


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;
}
}
}

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


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


Снова включаем метод теста postEcho в ранее добавленный класс PostmanEchoClientTests для только что добавленного клиентского метода postEcho. Как и ранее используйте для вызова конечной точки POST-запроса Postman Echo автоматическое внедрение PostmanEchoClient.


@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


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


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

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


@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 Server. Если это имя не указать, ваш сервис будет отображаться на Eureka Server как неизвестный.


spring:
application:
name: feignclient

eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka

Проделав эти несколько шагов, вы интегрировали сервис Spring REST в Eureka Server.


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


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

Создание Eureka Server


Создайте с помощью Spring Initializr другой проект  —  на этот раз для Eureka Server, добавив в него зависимость Eureka Server.


Spring Initialzr  —  добавление зависимостей

Если вы начинаете новый Maven-проект, импортируйте Spring Cloud Dependencies POM и добавьте зависимость 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. Для активации реализации Eureka Server также добавьте в него аннотацию @EnableEurekaServer.


@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 Server.


Postman  —  запрос на Eureka Server

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


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

Запросите содержимое для регистрации Postman Echo на Eureka Server так:


{
"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.


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


Postman — ответ от Eureka Server

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


Eureka Server  —  зарегистрированные инстансы

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


Заключение


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


Благодарю за чтение и надеюсь, что статья оказалась для вас полезна.


1153   0  

Comments

    Ничего не найдено.