Spring in Action Covers Spring 5-1--11 — страница 31 из 63

Я решил отлавливать EmptyResultDataAccessException и ничего с ними не делать. Я думаю, что если вы попытаетесь удалить ресурс, который не существует, результат будет таким же, как если бы он существовал до удаления. То есть ресурс перестанет существовать. Существовал ли он раньше или нет не имеет значения. Кроме того, я мог бы написать deleteOrder(), чтобы вернуть ResponseEntity, установив тело в null и код состояния HTTP NOT FOUND.

Единственное, на что следует обратить внимание в методе deleteOrder(), это то, что он аннотирован @ResponseStatus, чтобы гарантировать, что HTTP-статус ответа равен 204 (NO CONTENT). Нет необходимости передавать какие-либо данные ресурса обратно клиенту для ресурса, который больше не существует, поэтому ответы на запросы DELETE обычно не имеют тела и, следовательно, должны сообщать код состояния HTTP, чтобы клиент знал, что не стоит ожидать никакого содержимого.

Ваш Taco Cloud API начинает обретать форму. Клиентский код теперь может легко использовать этот API для отображения ингредиентов, принятия заказов и отображения недавно созданных тако. Но есть кое-что, что вы можете сделать, что сделает ваш API еще проще для клиента. Давайте посмотрим, как вы можете добавить hypermedia в Taco Cloud API.

6.2 Добавление hypermedia

API, который вы создали до сих пор, довольно прост, но он работает, пока клиент, который его использует, знает схему URL API. Например, клиент может быть жестко закодирован, чтобы знать, что он может получить список недавно созданных тако, выполнив запрос GET для /design/recent. Аналогично, он должен понимать, что он может добавить идентификатор любого taco из полученного перечня в /design, чтобы получить URL для этого конкретного taco.

Использование жестко закодированных шаблонов URL и манипулирование строками распространено среди клиентского кода API. Но представьте на мгновение, что произойдет, если изменится схема URL API. Жестко закодированный клиентский код будет иметь устаревшее понимание API и, таким образом, будет не корректен. Жесткое кодирование URL-адресов API и использование строковых манипуляций с ними делает клиентский код хрупким.

Гипермедиа как движок состояния приложения, или HATEOAS, является средством создания API с функционалом самоописания, в которых ресурсы, возвращаемые из API, содержат ссылки на связанные ресурсы. Это позволяет клиентам перемещаться по API с минимальным пониманием URL-адресов API. Вместо этого он понимает взаимосвязи между ресурсами, обслуживаемыми API, и использует свое понимание этих взаимосвязей для обнаружения URL-адресов API по мере их прохождения.

Например, предположим, что клиент должен был запросить список недавно разработанных тако. В необработанном виде, без гиперссылок, список последних тако будет получен в клиенте с JSON, который выглядит следующим образом (для краткости все, кроме первого тако в списке, вырезаны):

[

 {

   "id": 4,

   "name": "Veg-Out",

   "createdAt": "2018-01-31T20:15:53.219+0000",

   "ingredients": [

       {"id": "FLTO", "name": "Flour Tortilla", "type": "WRAP"},

       {"id": "COTO", "name": "Corn Tortilla", "type": "WRAP"},

       {"id": "TMTO", "name": "Diced Tomatoes", "type": "VEGGIES"},

       {"id": "LETC", "name": "Lettuce", "type": "VEGGIES"},

       {"id": "SLSA", "name": "Salsa", "type": "SAUCE"}

     ]

   },

   ...

]

Если клиент желает получить или выполнить какую-либо другую HTTP-операцию над конкретным тако, ему необходимо знать (с помощью жесткого кодирования), что он может добавить значение свойства id к URL-адресу, путь которого - /design. Аналогично, если бы он хотел выполнить операцию HTTP над одним из ингредиентов, ему нужно было бы знать, что он может добавить значение свойства id ингредиента к URL-адресу, путь которого равен /ingredients. В любом случае ему также необходимо добавить префикс к этому пути с http: // или https: // и имя хоста API.

Напротив, если API включен с гипермедиа, API будет описывать свои собственные URL-адреса, освобождая клиента от необходимости жестко кодировать эти знания. Тот же список недавно созданных тако мог бы выглядеть как следующий список, если бы были вставлены гиперссылки.

Листинг 6.3. Список тако-ресурсов с гиперссылками

{

 "_embedded": {

   "tacoResourceList": [

     {

      "name": "Veg-Out",

       "createdAt": "2018-01-31T20:15:53.219+0000",

       "ingredients": [

       {

         "name": "Flour Tortilla", "type": "WRAP",

         "_links": {

         "self": { "href": "http://localhost:8080/ingredients/FLTO" }

       }

     },

     {

       "name": "Corn Tortilla", "type": "WRAP",

       "_links": {

         "self": { "href": "http://localhost:8080/ingredients/COTO" }

       }

     },

     {

       "name": "Diced Tomatoes", "type": "VEGGIES",

       "_links": {

         "self": { "href": "http://localhost:8080/ingredients/TMTO" }

       }

     },

     {

       "name": "Lettuce", "type": "VEGGIES",

       "_links": {

         "self": { "href": "http://localhost:8080/ingredients/LETC" }

       }

     },

     {

       "name": "Salsa", "type": "SAUCE",

       "_links": {

         "self": { "href": "http://localhost:8080/ingredients/SLSA" }

       }

     }

   ],

   "_links": {

     "self": { "href": "http://localhost:8080/design/4" }

   }

 },

 ...

 ]

 },

 "_links": {

   "recents": {

     "href": "http://localhost:8080/design/recent"

   }

 }

}

Этот конкретный вариант HATEOAS известен как HAL (Hypertext Application Language; http://stateless.co/hal_specification.html), простой и обычно используемый формат для встраивания гиперссылок в JSON ответы.

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

Если клиентскому приложению необходимо выполнить HTTP-запрос к тако в массиве, его не нужно формировать на основе знания, как будет выглядеть URL ресурса тако. Вместо известно, что нужно запросить собственную ссылку, которая отображается на http: //localhost:8080/design/4. Если клиент хочет иметь дело с конкретным ингредиентом, ему нужно только перейти по ссылке на себя для этого ингредиента.

Проект Spring HATEOAS обеспечивает поддержку гиперссылок в Spring. Он предлагает набор классов и ассемблеров ресурсов, которые можно использовать для добавления ссылок на ресурсы перед их возвратом из контроллера Spring MVC.

Чтобы включить гипермедиа в Taco Cloud API, вам нужно добавить starter Spring HATEOAS в зависимости:

org.springframework.boot

spring-boot-starter-hateoas

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

Вы начнете с добавления гипермедиа-ссылок в список последних тако, возвращаемых GET-запросом в /design/recent.

6.2.1 Добавление гиперлинков

Spring HATEOAS предоставляет два основных типа, которые представляют гиперссылочные ресурсы: Resource и Resources. Тип Resource представляет один ресурс, тогда как Resources представляет собой набор ресурсов. Оба типа способны содержать ссылки на другие ресурсы. При возврате из метода Spring MVC REST контроллера ссылок, они будут включены в JSON (или XML), полученный клиентом.

Чтобы добавить гиперссылки к списку недавно созданных тако, вам нужно будет вернуться к методу recentTacos(), показанному в листинге 6.2. Исходная реализация возвращает List, который был прекрасным решением несколько страниц назад, но вам понадобилось, чтобы возвращались  объекты ресурсов вместо этого. В следующем листинге показана новая реализация recentTacos(), которая включает в себя первые шаги по внедрению гиперссылок в списке недавно разработанных тако.

Листинг 6.4. Добавление гиперссылок на ресурсы

@GetMapping("/recent")

public Resources> recentTacos() {

 PageRequest page = PageRequest.of(

   0, 12, Sort.by("createdAt").descending());

 List tacos = tacoRepo.findAll(page).getContent();

 Resources> recentResources = Resources.wrap(tacos);

 recentResources.add(

   new Link("http://localhost:8080/design/recent", "recents"));

 return recentResources;

}

В этой новой версии recentTacos(), вы больше не возвращаете непосредственно список тако. Вместо этого вы используете Resources.wrap() для переноса списка тако в качестве экземпляра Resources>, который в конечном итоге возвращается из метода. Но перед возвратом объекта Resources вы добавляете ссылку, имя отношения которой - recents, а URL-адрес которой http://localhost:8080/design/recent. Как следствие, следующий фрагмент JSON включен в ресурс, возвращаемый из запроса API:

"_links": {

 "recents": {

   "href": "http://localhost:8080/design/recent"

 }

}

Это хорошее начало, но у вас еще есть работа. На данный момент единственная добавленная вами ссылка - на весь перечень; никакие ссылки не добавляются ни к самим ресурсам тако, ни к ингредиентам каждого тако. Вы добавите их в ближайшее время. Но сначала давайте обратимся к жестко закодированному URL, который вы задали для ссылки на recents.