«1. Обзор

В этом руководстве мы познакомимся с Feign — декларативным HTTP-клиентом, разработанным Netflix.

Feign стремится упростить клиенты HTTP API. Проще говоря, разработчику нужно только объявить и аннотировать интерфейс, в то время как фактическая реализация предоставляется во время выполнения.

2. Пример

В этом руководстве мы будем использовать пример приложения книжного магазина, которое предоставляет конечную точку REST API.

Мы можем легко клонировать проект и запускать его локально:

mvn install spring-boot:run

3. Настройка

Во-первых, добавим необходимые зависимости:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
    <version>10.11</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-gson</artifactId>
    <version>10.11</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-slf4j</artifactId>
    <version>10.11</version>
</dependency>

Помимо зависимости feign-core (которая также вытащил), мы будем использовать несколько плагинов, в частности: feign-okhttp для внутреннего использования клиента Square OkHttp для выполнения запросов, feign-gson для использования Google GSON в качестве процессора JSON и feign-slf4j для использования фасада Simple Logging для регистрации запросов. .

Чтобы на самом деле получить некоторый вывод журнала, нам понадобится наша любимая реализация регистратора с поддержкой SLF4J в пути к классам.

Прежде чем мы приступим к созданию нашего клиентского интерфейса, сначала мы настроим модель Book для хранения данных:

public class Book {
    private String isbn;
    private String author;
    private String title;
    private String synopsis;
    private String language;

    // standard constructor, getters and setters
}

ПРИМЕЧАНИЕ. Обработчику JSON необходим по крайней мере «конструктор без аргументов».

Фактически, наш поставщик REST представляет собой API, управляемый гипермедиа, поэтому нам дополнительно понадобится простой класс-оболочка:

public class BookResource {
    private Book book;

    // standard constructor, getters and setters
}

Примечание. Мы сохраним BookResource простым, поскольку наш пример клиента Feign не t извлечь выгоду из функций гипермедиа!

4. Сторона сервера

Чтобы понять, как определить клиент Feign, мы сначала рассмотрим некоторые методы и ответы, поддерживаемые нашим поставщиком REST.

Давайте попробуем вывести список всех книг с помощью простой команды curl. Нам нужно помнить, что перед всеми вызовами стоит префикс /api, который является контекстом сервлета приложения:

curl http://localhost:8081/api/books

В результате мы получим полный репозиторий книг, представленный в виде JSON:

[
  {
    "book": {
      "isbn": "1447264533",
      "author": "Margaret Mitchell",
      "title": "Gone with the Wind",
      "synopsis": null,
      "language": null
    },
    "links": [
      {
        "rel": "self",
        "href": "http://localhost:8081/api/books/1447264533"
      }
    ]
  },

  ...

  {
    "book": {
      "isbn": "0451524934",
      "author": "George Orwell",
      "title": "1984",
      "synopsis": null,
      "language": null
    },
    "links": [
      {
        "rel": "self",
        "href": "http://localhost:8081/api/books/0451524934"
      }
    ]
  }
]

Мы также может запросить отдельный ресурс Book, добавив ISBN к запросу на получение:

curl http://localhost:8081/api/books/1447264533

5. Клиент Feign

Наконец, давайте определим наш клиент Feign.

Мы будем использовать аннотацию @RequestLine, чтобы указать глагол HTTP и часть пути в качестве аргумента. Параметры будут смоделированы с использованием аннотации @Param:

public interface BookClient {
    @RequestLine("GET /{isbn}")
    BookResource findByIsbn(@Param("isbn") String isbn);

    @RequestLine("GET")
    List<BookResource> findAll();

    @RequestLine("POST")
    @Headers("Content-Type: application/json")
    void create(Book book);
}

ПРИМЕЧАНИЕ. Клиенты Feign могут использоваться только для использования текстовых API-интерфейсов HTTP, что означает, что они не могут обрабатывать двоичные данные, например. загрузки или скачивания файлов.

Вот и все! Теперь мы будем использовать Feign.builder() для настройки нашего клиента на основе интерфейса. Фактическая реализация будет предоставлена ​​во время выполнения:

BookClient bookClient = Feign.builder()
  .client(new OkHttpClient())
  .encoder(new GsonEncoder())
  .decoder(new GsonDecoder())
  .logger(new Slf4jLogger(BookClient.class))
  .logLevel(Logger.Level.FULL)
  .target(BookClient.class, "http://localhost:8081/api/books");

Feign поддерживает различные подключаемые модули, такие как кодировщики и декодеры JSON/XML или базовый HTTP-клиент для выполнения запросов.

6. Модульный тест

Давайте создадим три тестовых примера для тестирования нашего клиента. Обратите внимание, мы используем статический импорт для org.hamcrest.CoreMatchers.* и org.junit.Assert.*:

@Test
public void givenBookClient_shouldRunSuccessfully() throws Exception {
   List<Book> books = bookClient.findAll().stream()
     .map(BookResource::getBook)
     .collect(Collectors.toList());

   assertTrue(books.size() > 2);
}

@Test
public void givenBookClient_shouldFindOneBook() throws Exception {
    Book book = bookClient.findByIsbn("0151072558").getBook();
    assertThat(book.getAuthor(), containsString("Orwell"));
}

@Test
public void givenBookClient_shouldPostBook() throws Exception {
    String isbn = UUID.randomUUID().toString();
    Book book = new Book(isbn, "Me", "It's me!", null, null);
    bookClient.create(book);
    book = bookClient.findByIsbn(isbn).getBook();

    assertThat(book.getAuthor(), is("Me"));
}

7. Дальнейшее чтение

Если нам нужен какой-то запасной вариант на случай недоступности сервиса , мы могли бы добавить HystrixFeign в путь к классам и создать наш клиент с помощью HystrixFeign.builder().

Ознакомьтесь с этой специальной серией руководств, чтобы узнать больше о Hystrix.

Кроме того, если мы хотим интегрировать Spring Cloud Netflix Hystrix с Feign, здесь есть специальная статья.

Более того, в наш клиент также можно добавить балансировку нагрузки и/или обнаружение сервисов на стороне клиента.

Мы могли бы добиться этого, добавив ленту в наш путь к классам и используя конструктор следующим образом:

BookClient bookClient = Feign.builder()
  .client(RibbonClient.create())
  .target(BookClient.class, "http://localhost:8081/api/books");

Для обнаружения службы мы должны создать нашу службу с включенным Spring Cloud Netflix Eureka. Затем просто интегрируйте Spring Cloud с Netflix Feign. В результате мы получаем балансировку нагрузки Ribbon бесплатно. Подробнее об этом можно узнать здесь.

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

В этой статье мы объяснили, как создать декларативный HTTP-клиент с помощью Feign для использования текстовых API.

Как обычно, все примеры кода, показанные в этом руководстве, доступны на GitHub.