1. Обзор
В этой статье объясняется процесс создания веб-службы REST на основе гипермедиа с использованием проекта Spring HATEOAS.
2. Весна-ХАТЕОАС
Проект Spring HATEOAS — это библиотека API-интерфейсов, которую мы можем использовать для простого создания представлений REST, соответствующих принципу HATEOAS (гипертекст как механизм состояния приложения).
Вообще говоря, принцип подразумевает, что API должен направлять клиента через приложение, возвращая соответствующую информацию о следующих возможных шагах вместе с каждым ответом.
В этой статье мы собираемся создать пример с использованием Spring HATEOAS с целью разъединения клиента и сервера и теоретически позволить API изменять свою схему URI без нарушения работы клиентов.
3. Подготовка
Во-первых, давайте добавим зависимость Spring HATEOAS:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
<version>2.6.4</version>
</dependency>
Если мы не используем Spring Boot, мы можем добавить в наш проект следующие библиотеки:
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>
Как всегда, мы можем искать последние версии стартового HATEOAS , зависимостей spring-hateoas и spring-plugin-core в Maven Central.
Далее у нас есть ресурс Customer
без поддержки Spring HATEOAS:
public class Customer {
private String customerId;
private String customerName;
private String companyName;
// standard getters and setters
}
И у нас есть класс контроллера без поддержки Spring HATEOAS:
@RestController
@RequestMapping(value = "/customers")
public class CustomerController {
@Autowired
private CustomerService customerService;
@GetMapping("/{customerId}")
public Customer getCustomerById(@PathVariable String customerId) {
return customerService.getCustomerDetail(customerId);
}
}
Наконец, представление ресурса Customer :
{
"customerId": "10A",
"customerName": "Jane",
"customerCompany": "ABC Company"
}
4. Добавление поддержки HATEOAS
В проекте Spring HATEOAS нам не нужно ни искать контекст сервлета, ни объединять переменную пути с базовым URI.
Вместо этого Spring HATEOAS предлагает три абстракции для создания URI — RepresentationModel, Link и WebMvcLinkBuilder
. Мы можем использовать их для создания метаданных и связывания их с представлением ресурса.
4.1. Добавление поддержки Hypermedia к ресурсу
Проект предоставляет базовый класс RepresentationModel
для наследования при создании представления ресурсов:
public class Customer extends RepresentationModel<Customer> {
private String customerId;
private String customerName;
private String companyName;
// standard getters and setters
}
Ресурс Customer
расширяется от класса RepresentationModel
, чтобы наследовать метод add ()
. Поэтому, как только мы создадим ссылку, мы можем легко установить это значение для представления ресурса, не добавляя к нему никаких новых полей.
4.2. Создание ссылок
Spring HATEOAS предоставляет объект Link
для хранения метаданных (местоположение или URI ресурса).
Сначала мы создадим простую ссылку вручную:
Link link = new Link("http://localhost:8080/spring-security-rest/api/customers/10A");
Объект Link
следует синтаксису ссылки Atom
и состоит из rel
, который идентифицирует отношение к ресурсу, и атрибута href
, который является фактической самой ссылкой.
Вот как ресурс Customer
выглядит теперь, когда он содержит новую ссылку:
{
"customerId": "10A",
"customerName": "Jane",
"customerCompany": "ABC Company",
"_links":{
"self":{
"href":"http://localhost:8080/spring-security-rest/api/customers/10A"
}
}
}
URI, связанный с ответом, квалифицируется как собственная
ссылка. Семантика самоотношения
ясна — это просто каноническое местоположение, в котором можно получить доступ к ресурсу.
4.3. Создание лучших ссылок
Другой очень важной абстракцией, предлагаемой библиотекой, является WebMvcLinkBuilder ,
который упрощает создание URI , избегая жестко закодированных ссылок.
В следующем фрагменте показано создание самостоятельной ссылки клиента с помощью класса WebMvcLinkBuilder
:
linkTo(CustomerController.class).slash(customer.getCustomerId()).withSelfRel();
Давайте посмотрим:
- метод
linkTo()
проверяет класс контроллера и получает его корневое сопоставление - метод
slash()
добавляет значениеcustomerId
в качестве переменной пути ссылки - наконец,
withSelfMethod()
квалифицирует отношение как самосвязь.
5. Отношения
В предыдущем разделе мы показали самоссылающееся отношение. Однако более сложные системы могут включать и другие отношения.
Например, клиент
может иметь отношения с заказами. Давайте также смоделируем класс Order
как ресурс:
public class Order extends RepresentationModel<Order> {
private String orderId;
private double price;
private int quantity;
// standard getters and setters
}
На этом этапе мы можем расширить CustomerController
методом, который возвращает все заказы конкретного клиента:
@GetMapping(value = "/{customerId}/orders", produces = { "application/hal+json" })
public CollectionModel<Order> getOrdersForCustomer(@PathVariable final String customerId) {
List<Order> orders = orderService.getAllOrdersForCustomer(customerId);
for (final Order order : orders) {
Link selfLink = linkTo(methodOn(CustomerController.class)
.getOrderById(customerId, order.getOrderId())).withSelfRel();
order.add(selfLink);
}
Link link = linkTo(methodOn(CustomerController.class)
.getOrdersForCustomer(customerId)).withSelfRel();
CollectionModel<Order> result = CollectionModel.of(orders, link);
return result;
}
Наш метод возвращает объект CollectionModel
для соответствия типу возвращаемого значения HAL, а также ссылку « _self»
для каждого из заказов и полного списка.
Здесь важно отметить, что гиперссылка для заказов клиентов зависит от отображения метода getOrdersForCustomer()
. Мы будем называть эти типы ссылок ссылками на методы и покажем, как WebMvcLinkBuilder
может помочь в их создании.
6. Ссылки на методы контроллера
WebMvcLinkBuilder предлагает расширенную
поддержку контроллеров Spring MVC. В следующем примере показано, как создавать гиперссылки HATEOAS на основе метода getOrdersForCustomer() класса
CustomerController
:
Link ordersLink = linkTo(methodOn(CustomerController.class)
.getOrdersForCustomer(customerId)).withRel("allOrders");
Метод methodOn()
получает сопоставление метода путем фиктивного вызова целевого метода на прокси-контроллере и устанавливает идентификатор клиента
в качестве переменной пути URI.
7. Spring HATEOAS в действии
Давайте объединим самоссылку и создание ссылки на метод в методе getAllCustomers()
:
@GetMapping(produces = { "application/hal+json" })
public CollectionModel<Customer> getAllCustomers() {
List<Customer> allCustomers = customerService.allCustomers();
for (Customer customer : allCustomers) {
String customerId = customer.getCustomerId();
Link selfLink = linkTo(CustomerController.class).slash(customerId).withSelfRel();
customer.add(selfLink);
if (orderService.getAllOrdersForCustomer(customerId).size() > 0) {
Link ordersLink = linkTo(methodOn(CustomerController.class)
.getOrdersForCustomer(customerId)).withRel("allOrders");
customer.add(ordersLink);
}
}
Link link = linkTo(CustomerController.class).withSelfRel();
CollectionModel<Customer> result = CollectionModel.of(allCustomers, link);
return result;
}
Далее вызовем метод getAllCustomers()
:
curl http://localhost:8080/spring-security-rest/api/customers
И изучите результат:
{
"_embedded": {
"customerList": [{
"customerId": "10A",
"customerName": "Jane",
"companyName": "ABC Company",
"_links": {
"self": {
"href": "http://localhost:8080/spring-security-rest/api/customers/10A"
},
"allOrders": {
"href": "http://localhost:8080/spring-security-rest/api/customers/10A/orders"
}
}
},{
"customerId": "20B",
"customerName": "Bob",
"companyName": "XYZ Company",
"_links": {
"self": {
"href": "http://localhost:8080/spring-security-rest/api/customers/20B"
},
"allOrders": {
"href": "http://localhost:8080/spring-security-rest/api/customers/20B/orders"
}
}
},{
"customerId": "30C",
"customerName": "Tim",
"companyName": "CKV Company",
"_links": {
"self": {
"href": "http://localhost:8080/spring-security-rest/api/customers/30C"
}
}
}]
},
"_links": {
"self": {
"href": "http://localhost:8080/spring-security-rest/api/customers"
}
}
}
В каждом представлении ресурса есть ссылка self и ссылка
allOrders
для извлечения всех заказов клиента. Если у клиента нет заказов, ссылка на заказы не появится.
В этом примере показано, как Spring HATEOAS способствует обнаружению API в веб-службе отдыха. Если ссылка существует, клиент может перейти по ней и получить все заказы для клиента:
curl http://localhost:8080/spring-security-rest/api/customers/10A/orders
{
"_embedded": {
"orderList": [{
"orderId": "001A",
"price": 150,
"quantity": 25,
"_links": {
"self": {
"href": "http://localhost:8080/spring-security-rest/api/customers/10A/001A"
}
}
},{
"orderId": "002A",
"price": 250,
"quantity": 15,
"_links": {
"self": {
"href": "http://localhost:8080/spring-security-rest/api/customers/10A/002A"
}
}
}]
},
"_links": {
"self": {
"href": "http://localhost:8080/spring-security-rest/api/customers/10A/orders"
}
}
}
8. Заключение
В этом руководстве мы обсудили, как создать управляемый гипермедиа веб-сервис Spring REST с использованием проекта Spring HATEOAS .
В примере мы видим, что клиент может иметь единую точку входа в приложение, и дальнейшие действия могут быть предприняты на основе метаданных в представлении ответа.
Это позволяет серверу изменять свою схему URI без нарушения работы клиента. Кроме того, приложение может рекламировать новые возможности, помещая в представление новые ссылки или URI.
Наконец, полную реализацию этой статьи можно найти в проекте GitHub .