«1. Обзор

До сих пор в нашем облачном приложении мы использовали шаблон шлюза для поддержки двух основных функций.

Во-первых, мы изолировали наших клиентов от каждой службы, устранив необходимость в поддержке разных источников. Затем мы реализовали поиск экземпляров сервисов с помощью Eureka.

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

Чтобы узнать, как использовать клиент Feign, ознакомьтесь с этой статьей.

Spring Cloud теперь также предоставляет проект Spring Cloud Gateway, который реализует этот шаблон.

2. Настройка

Давайте откроем pom.xml нашего сервера шлюза и добавим зависимость для Feign:

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

Для справки: мы можем найти последние версии на Maven Central (весна- облако-стартер-притворяется).

Теперь, когда у нас есть поддержка для создания клиента Feign, давайте включим его в GatewayApplication.java:

@EnableFeignClients
public class GatewayApplication { ... }

Теперь давайте настроим клиентов Feign для службы книги и оценки.

3. Имитация клиентов

3.1. Book Client

Давайте создадим новый интерфейс с именем BooksClient.java:

@FeignClient("book-service")
public interface BooksClient {
 
    @RequestMapping(value = "/books/{bookId}", method = RequestMethod.GET)
    Book getBookById(@PathVariable("bookId") Long bookId);
}

С помощью этого интерфейса мы указываем Spring создать клиент Feign, который будет обращаться к «/books/{bookId}». конечная точка. При вызове метод getBookById выполняет HTTP-вызов конечной точке и использует параметр bookId.

Чтобы все заработало, нам нужно добавить DTO Book.java:

@JsonIgnoreProperties(ignoreUnknown = true)
public class Book {
 
    private Long id;
    private String author;
    private String title;
    private List<Rating> ratings;
    
    // getters and setters
}

Давайте перейдем к RatingsClient.

3.2. Ratings Client

Давайте создадим интерфейс с именем RatingsClient:

@FeignClient("rating-service")
public interface RatingsClient {
 
    @RequestMapping(value = "/ratings", method = RequestMethod.GET)
    List<Rating> getRatingsByBookId(
      @RequestParam("bookId") Long bookId, 
      @RequestHeader("Cookie") String session);
    
}

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

Однако эта конечная точка защищена. Чтобы правильно получить доступ к этой конечной точке, нам нужно передать сеанс пользователя в запрос.

Мы делаем это с помощью аннотации @RequestHeader. Это даст указание Feign записать значение этой переменной в заголовок запроса. В нашем случае мы пишем в заголовок Cookie, потому что Spring Session будет искать нашу сессию в cookie.

В нашем случае мы пишем в заголовок Cookie, потому что Spring Session будет искать нашу сессию в cookie.

Наконец, давайте добавим Rating.java DTO:

@JsonIgnoreProperties(ignoreUnknown = true)
public class Rating {
    private Long id;
    private Long bookId;
    private int stars;
}

Теперь оба клиента готовы. Давайте использовать их!

4. Комбинированный запрос

Одним из распространенных вариантов использования шаблона шлюза является наличие конечных точек, которые инкапсулируют часто вызываемые службы. Это может повысить производительность за счет уменьшения количества клиентских запросов.

Для этого давайте создадим контроллер и назовем его CombinedController.java:

@RestController
@RequestMapping("/combined")
public class CombinedController { ... }

Затем давайте подключим наши только что созданные фиктивные клиенты:

private BooksClient booksClient;
private RatingsClient ratingsClient;

@Autowired
public CombinedController(
  BooksClient booksClient, 
  RatingsClient ratingsClient) {
 
    this.booksClient = booksClient;
    this.ratingsClient = ratingsClient;
}

И, наконец, давайте создадим запрос GET, который объединяет эти две конечные точки и возвращает одну книгу с загруженными рейтингами:

@GetMapping
public Book getCombinedResponse(
  @RequestParam Long bookId,
  @CookieValue("SESSION") String session) {
 
    Book book = booksClient.getBookById(bookId);
    List<Rating> ratings = ratingsClient.getRatingsByBookId(bookId, "SESSION="+session);
    book.setRatings(ratings);
    return book;
}

Обратите внимание, что мы устанавливаем значение сеанса с помощью аннотации @CookieValue, которая извлекает его из запроса.

Вот оно! У нас есть комбинированная конечная точка в нашем шлюзе, которая уменьшает количество сетевых вызовов между клиентом и системой!

5. Тестирование

Давайте удостоверимся, что наша новая конечная точка работает.

Перейдите к LiveTest.java и давайте добавим тест для нашей объединенной конечной точки:

@Test
public void accessCombinedEndpoint() {
    Response response = RestAssured.given()
      .auth()
      .form("user", "password", formConfig)
      .get(ROOT_URI + "/combined?bookId=1");
 
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());
    assertNotNull(response.getBody());
 
    Book result = response.as(Book.class);
 
    assertEquals(new Long(1), result.getId());
    assertNotNull(result.getRatings());
    assertTrue(result.getRatings().size() > 0);
}

Запустите Redis, а затем запустите каждую службу в нашем приложении: config, discovery, zipkin, gateway, book и рейтинг услуга.

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

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

Мы рассмотрели, как интегрировать Feign в наш шлюз для создания специализированной конечной точки. Мы можем использовать эту информацию для создания любого API, который нам нужно поддерживать. Самое главное, мы видим, что мы не попали в ловушку универсального API, который предоставляет доступ только к отдельным ресурсам.

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

«Как всегда, фрагменты кода можно найти на GitHub.