Вступ до весняних HATEOAS

REST Top

Я щойно оголосив про новий курс Learn Spring , орієнтований на основи Spring 5 та Spring Boot 2:

>> ПЕРЕВІРИТИ КУРС

1. Огляд

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

2. Весна-НЕЙТИНИ

Проект 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 

Як завжди, ми можемо шукати останні версії початкових HATEOAS, spring-hateoas та spring-plugin-core залежностей в Maven Central.

Далі ми маємо ресурс Клієнта без підтримки 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); } } 

Нарешті, представлення ресурсів Клієнта :

{ "customerId": "10A", "customerName": "Jane", "customerCompany": "ABC Company" } 

4. Додавання підтримки HATEOAS

У проекті Spring HATEOAS нам не потрібно ні шукати контекст сервлету, ні об'єднувати змінну шляху до базового URI.

Натомість Spring HATEOAS пропонує три абстракції для створення URI - ReprezentationModel, Link та WebMvcLinkBuilder . Ми можемо використовувати їх для створення метаданих та прив’язки їх до подання ресурсу.

4.1. Додавання підтримки Hypermedia до ресурсу

Проект надає базовий клас, який називається ReprepressionModel, який слід успадковувати при створенні представлення ресурсу:

public class Customer extends RepresentationModel { private String customerId; private String customerName; private String companyName; // standard getters and setters } 

Клієнт ресурс простягається від RepresentationModel класу успадковувати Add () метод . Отже, коли ми створимо посилання, ми можемо легко встановити це значення для подання ресурсу, не додаючи до нього жодного нового поля.

4.2. Створення посилань

Spring HATEOAS надає об'єкт Link для зберігання метаданих (розташування або URI ресурсу).

Спочатку ми створимо просте посилання вручну:

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

Об'єкт Link відповідає синтаксису посилання Atom і складається з rel, який ідентифікує відношення до ресурсу та атрибуту href, який є фактичним самим посиланням.

Ось як виглядає ресурс Клієнта тепер, коли він містить нове посилання:

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

URI, пов'язаний з відповіддю, кваліфікується як самостійне посилання. Семантика самовідношення зрозуміла - це просто канонічне розташування ресурсу, до якого можна отримати доступ.

4.3. Створення кращих посилань

Ще одна дуже важлива абстракція , пропонована в бібліотеці WebMvcLinkBuilder - що спрощує побудову URIs , уникаючи жорстко закодовані посилання.

Наступний фрагмент показує побудову самозв’язку клієнта за допомогою класу WebMvcLinkBuilder :

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

Давай подивимось:

  • linkTo () метод перевіряє клас контролера і отримує її кореневе відображення
  • косою риси () метод додає CustomerId значення в якості змінної шляху ланки
  • нарешті, withSelfMethod () кваліфікує відношення як самозв’язок

5. Відносини

У попередньому розділі ми продемонстрували відношення, яке посилається на себе. Однак більш складні системи можуть включати й інші відносини.

Наприклад, клієнт може мати стосунки із замовленнями. Давайте також змоделюємо клас Order як ресурс:

public class Order extends RepresentationModel { private String orderId; private double price; private int quantity; // standard getters and setters } 

На даний момент ми можемо розширити CustomerController методом, який повертає всі замовлення конкретного клієнта:

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

Our method returns a CollectionModel object to comply with the HAL return type, as well as a “_self” link for each of the orders and the full list.

An important thing to notice here is that the hyperlink for the customer orders depends on the mapping of getOrdersForCustomer() method. We'll refer to these types of links as method links and show how the WebMvcLinkBuilder can assist in their creation.

6. Links to Controller Methods

The WebMvcLinkBuilder offers rich support for Spring MVC Controllers. The following example shows how to build HATEOAS hyperlinks based on the getOrdersForCustomer() method of the CustomerController class:

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

The methodOn() obtains the method mapping by making dummy invocation of the target method on the proxy controller and sets the customerId as the path variable of the URI.

7. Spring HATEOAS in Action

Let's put the self-link and method link creation all together in a getAllCustomers() method:

@GetMapping(produces = { "application/hal+json" }) public CollectionModel 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(); CollectionModel result = new CollectionModel(allCustomers, link); return result; }

Next, let's invoke the getAllCustomers() method:

curl //localhost:8080/spring-security-rest/api/customers 

And examine the result:

{ "_embedded": { "customerList": [{ "customerId": "10A", "customerName": "Jane", "companyName": "ABC Company", "_links": { "self": { "href": "//localhost:8080/spring-security-rest/api/customers/10A" }, "allOrders": { "href": "//localhost:8080/spring-security-rest/api/customers/10A/orders" } } },{ "customerId": "20B", "customerName": "Bob", "companyName": "XYZ Company", "_links": { "self": { "href": "//localhost:8080/spring-security-rest/api/customers/20B" }, "allOrders": { "href": "//localhost:8080/spring-security-rest/api/customers/20B/orders" } } },{ "customerId": "30C", "customerName": "Tim", "companyName": "CKV Company", "_links": { "self": { "href": "//localhost:8080/spring-security-rest/api/customers/30C" } } }] }, "_links": { "self": { "href": "//localhost:8080/spring-security-rest/api/customers" } } }

Within each resource representation, there is a self link and the allOrders link to extract all orders of a customer. If a customer doesn't have orders, then the link for orders won't appear.

Цей приклад демонструє, як Spring HATEOAS сприяє виявленню API у веб-службі відпочинку. Якщо посилання існує, клієнт може перейти за ним і отримати всі замовлення для клієнта:

curl //localhost:8080/spring-security-rest/api/customers/10A/orders 
{ "_embedded": { "orderList": [{ "orderId": "001A", "price": 150, "quantity": 25, "_links": { "self": { "href": "//localhost:8080/spring-security-rest/api/customers/10A/001A" } } },{ "orderId": "002A", "price": 250, "quantity": 15, "_links": { "self": { "href": "//localhost:8080/spring-security-rest/api/customers/10A/002A" } } }] }, "_links": { "self": { "href": "//localhost:8080/spring-security-rest/api/customers/10A/orders" } } }

8. Висновок

У цьому підручнику ми обговорили, як створити веб-службу Spring REST на основі гіпермедіа за допомогою проекту Spring HATEOAS .

У цьому прикладі ми бачимо, що клієнт може мати єдину точку входу до програми, і подальші дії можуть бути здійснені на основі метаданих у поданні відповіді.

Це дозволяє серверу змінювати схему URI, не порушуючи клієнта. Крім того, програма може рекламувати нові можливості, розміщуючи нові посилання або URI у поданні.

Нарешті, повну реалізацію цієї статті можна знайти в проекті GitHub.

Відпочинок знизу

Я щойно оголосив про новий курс Learn Spring , орієнтований на основи Spring 5 та Spring Boot 2:

>> ПЕРЕВІРИТИ КУРС