В 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.
Если вы начинаете 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.
Если вы начинаете новый 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.
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
.
В результате вы должны получить успешный ответ без содержимого.
Откройте панель Eureka, где должна отразиться регистрация POSTMAN-ECHO
.
Еще раз запустите PostmanRestClientTests
, чтобы убедиться, что все работает как надо.
Заключение
Мы научились создавать декларативного REST-клиента при помощи Spring Cloud OpenFeign, задействовав Spring Cloud Netflix Ribbon для обеспечения на клиентской стороне балансировки нагрузки и отказоустойчивости. Плюсом к этому мы узнали, как настраивать конечную точку статического сервера для Ribbon и как выполнять интеграцию с Eureka Server для получения списка конечных точек зарегистрированных серверов. Весь исходный код доступен на GitHub.
Благодарю за чтение и надеюсь, что статья оказалась для вас полезна.
Читайте также:
- Как создать криптовалютный дашборд с помощью Plotly и API Binance
- 7 бесплатных API для уникальных приложений
- Генерируем образы Docker с помощью Spring Boot
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Andy Lian: Building a REST Client with Spring Cloud OpenFeign and Netflix Ribbon