1. Обзор
До сих пор в нашем облачном приложении мы использовали шаблон шлюза для поддержки двух основных функций.
Во-первых, мы изолировали наших клиентов от каждой службы, устранив необходимость в поддержке разных источников. Затем мы реализовали поиск экземпляров сервисов с помощью Eureka.
В этой статье мы рассмотрим, как использовать шаблон Gateway для получения данных из нескольких служб с помощью одного запроса . Для этого мы собираемся внедрить 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
( spring-cloud-starter-feign ).
Теперь, когда у нас есть поддержка создания клиента Feign, давайте включим его в GatewayApplication.java
:
@EnableFeignClients
public class GatewayApplication { ... }
Теперь давайте настроим клиентов Feign для сервисов книги и рейтинга.
3. Притворяйтесь клиентами
3.1. Забронировать клиент
Давайте создадим новый интерфейс с именем 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
.
Чтобы это работало, нам нужно добавить Book.java
DTO:
@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. Клиент рейтингов
Давайте создадим интерфейс с именем 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
, представленный здесь метод выполнит вызов службы оценки и вернет список оценок для книги.
Однако эта конечная точка защищена. Чтобы правильно получить доступ к этой конечной точке, нам нужно передать сеанс пользователя в запрос.
Мы делаем это с помощью аннотации @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.