Введение в Spring HATEOAS

Вступление к весне HATEOAS

1. обзор

В этой статье описывается процесс создания веб-службы REST на основе гипермедиа с использованием проекта Spring HATEOAS.

2. Весна-HATEOAS

Проект Spring HATEOAS - это библиотека API-интерфейсов, которую мы можем использовать для простого создания REST-представлений, соответствующих принципу HATEOAS (гипертекст как движок состояния приложения).

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

В этой статье мы собираемся создать пример с использованием Spring HATEOAS с целью разделения клиента и сервера и теоретического разрешения API изменять свою схему URI без нарушения работы клиентов.

3. подготовка

Во-первых, давайте добавим зависимость Spring HATEOAS:


    org.springframework.boot
    spring-boot-starter-hateoas
    2.1.4.RELEASE

Если мы не используем Spring Boot, мы можем добавить в наш проект следующие библиотеки:


    org.springframework.hateoas
    spring-hateoas
    0.25.1.RELEASE


    org.springframework.plugin
    spring-plugin-core
    1.2.0.RELEASE

Как всегда, мы можем искать последние версии зависимостейstarter 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 offers three abstractions for creating the URI – ResourceSupport, Link, and ControllerLinkBuilder. Мы можем использовать их для создания метаданных и привязки их к представлению ресурса.

4.1. Добавление поддержки гипермедиа к ресурсу

Проект предоставляет базовый классResourceSupport для наследования при создании представления ресурса:

public class Customer extends ResourceSupport {
    private String customerId;
    private String customerName;
    private String companyName;

    // standard getters and setters
}

The Customer resource extends from the ResourceSupport class to inherit the add() method. Поэтому, как только мы создадим ссылку, мы можем легко установить это значение для представления ресурса, не добавляя в него никаких новых полей.

Spring HATEOAS предоставляет объектLink для хранения метаданных (расположение или URI ресурса).

Сначала создадим простую ссылку вручную:

Link link = new Link("http://localhost:8080/spring-security-rest/api/customers/10A");

ОбъектLink следует синтаксису ссылкиhttps://en.wikipedia.org/wiki/Atom (стандартный) [Atom] _ и ​​состоит изrel, который определяет отношение к ресурсу, и атрибутаhref, который является фактической ссылкой. сам.

Вот как выглядит ресурсCustomer теперь, когда он содержит новую ссылку:

{
    "customerId": "10A",
    "customerName": "Jane",
    "customerCompany": "ABC Company",
    "_links":{
        "self":{
            "href":"http://localhost:8080/spring-security-rest/api/customers/10A"
         }
    }
}

URI, связанный с ответом, квалифицируется как ссылкаself. Семантика отношенияself ясна - это просто каноническое местоположение, из которого можно получить доступ к Ресурсу.

Другая очень важная абстракция, предлагаемая библиотекой, -the ControllerLinkBuilder – which simplifies building URIs, позволяющая избегать жестко заданных ссылок.

В следующем фрагменте показано построение само-ссылки клиента с использованием классаControllerLinkBuilder:

linkTo(CustomerController.class).slash(customer.getCustomerId()).withSelfRel();

Давайте посмотрим:

  • методlinkTo() проверяет класс контроллера и получает его корневое отображение

  • методslash() добавляет значениеcustomerId в качестве переменной пути ссылки

  • наконец,withSelfMethod() квалифицирует отношение как самосвязь

5. связи

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

Например,customer может иметь отношение к заказам. Давайте также смоделируем классOrder как ресурс:

public class Order extends ResourceSupport {
    private String orderId;
    private double price;
    private int quantity;

    // standard getters and setters
}

На этом этапе мы можем расширитьCustomerController с помощью метода, который возвращает все заказы конкретного клиента:

@GetMapping(value = "/{customerId}/orders", produces = { "application/hal+json" })
public Resources getOrdersForCustomer(@PathVariable final String customerId) {
    List 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();
    Resources result = new Resources(orders, link);
    return result;
}

Наш метод возвращает объектResources в соответствии с типом возврата HAL, а также ссылку «_self” для каждого из заказов и полного списка.

Здесь важно отметить, что гиперссылка для заказов клиентов зависит от сопоставления методаgetOrdersForCustomer(). Мы назовем эти типы ссылок ссылками на методы и покажем, какControllerLinkBuilder могут помочь в их создании.

ControllerLinkBuilder предлагает богатую поддержку контроллеров Spring MVC. В следующем примере показано, как создавать гиперссылки HATEOAS на основе методаgetOrdersForCustomer() классаCustomerController:

Link ordersLink = linkTo(methodOn(CustomerController.class)
  .getOrdersForCustomer(customerId)).withRel("allOrders");

The methodOn() obtains the method mapping by making dummy invocation of the target method на прокси-контроллере и устанавливаетcustomerId в качестве переменной пути в URI.

7. Spring HATEOAS в действии

Давайте объединим само-ссылку и создание ссылки на метод в методgetAllCustomers():

@GetMapping(produces = { "application/hal+json" })
public Resources getAllCustomers() {
    List 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();
    Resources result = new Resources(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 в остальном веб-сервисе. If the link exists, the client can follow it and get all orders for a customer:с

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. Заключение

В этом руководстве мы обсудили, какbuild a hypermedia-driven Spring REST web service using the Spring HATEOAS project.

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

Это позволяет серверу изменять свою схему URI, не прерывая работу клиента. Кроме того, приложение может рекламировать новые возможности, помещая новые ссылки или URI в представление.

Наконец, полную реализацию этой статьи можно найти вthe GitHub project.