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

 }

 @Override

 protected IngredientResource instantiateResource(

       Ingredient ingredient) {

   return new IngredientResource(ingredient);

 }

}

Как видите, IngredientResourceAssembler очень похож на TacoResourceAssembler, но работает с объектами Ingredient и IngredientResource вместо объектов Taco и TacoResource.

Говоря о IngredientResource,  он выглядит так:

package tacos.web.api;

import org.springframework.hateoas.ResourceSupport;

import lombok.Getter;

import tacos.Ingredient;

import tacos.Ingredient.Type;

public class IngredientResource extends ResourceSupport {

 @Getter

 private String name;

 @Getter

 private Type type;

 public IngredientResource(Ingredient ingredient) {

   this.name = ingredient.getName();

   this.type = ingredient.getType();

 }

}

Как и в случае с TacoResource, IngredientResource расширяет ResourceSupport и копирует соответствующие свойства из типа домена в свой собственный набор свойств (исключая свойство id).

Осталось лишь немного изменить TacoResource, чтобы он содержал объекты IngredientResource вместо объектов Ingredient:

package tacos.web.api;

import java.util.Date;

import java.util.List;

import org.springframework.hateoas.ResourceSupport;

import lombok.Getter;

import tacos.Taco;

public class TacoResource extends ResourceSupport {

 private static final IngredientResourceAssembler ingredientAssembler = new IngredientResourceAssembler();

 @Getter

 private final String name;

 @Getter

 private final Date createdAt;

 @Getter

 private final List ingredients;

 public TacoResource(Taco taco) {

   this.name = taco.getName();

   this.createdAt = taco.getCreatedAt();

   this.ingredients =ingredientAssembler.toResources(taco.getIngredients());

 }

}

Эта новая версия TacoResource создает final статический экземпляр IngredientResourceAssembler и использует его метод toResource() для преобразования списка Ingredient для данного объекта Taco в список IngredientResource.

Ваш недавний список тако теперь полностью снабжен гиперссылками, причем не только на себя (ссылка на recents), но также для всех его записей тако и ингредиентов этих тако. Ответ должен быть очень похож на JSON в листинге 6.3.

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

6.2.3 Именование встроенных отношений

Если вы внимательнее посмотрите на листинг 6.3, вы заметите, что элементы верхнего уровня выглядят так:

{

 "_embedded": {

   "tacoResourceList": [

     ...

   ]

 }

}

Прежде всего, позвольте мне обратить ваше внимание на имя tacoResourceList под embedded. Это имя было получено из того факта, что объект Resources был создан из List. Не то чтобы это было невероятно, но если бы вы реорганизовали имя класса TacoResource в другое, имя поля в результирующем JSON изменится, чтобы соответствовать ему. Это, вероятно, сломало бы любых клиентов, закодированных, чтобы рассчитывать на это имя.

Аннотация @Relation может помочь разорвать связь между именем поля JSON и именами классов типов ресурсов, как это определено в Java. Аннотируя TacoResource с помощью @Relation, вы можете указать, как Spring HATEOAS должен называть поле в результирующем JSON:

@Relation(value="taco", collectionRelation="tacos")

public class TacoResource extends ResourceSupport {

   ...

}

Здесь вы указали, что когда список объектов TacoResource используется в объекте Resources, он должен называться tacos. И хотя вы не используете это в нашем API, один объект TacoResource должен называться в JSON как taco.

В результате JSON, возвращенный из /design/recent, теперь будет выглядеть следующим образом (независимо от того, какой рефакторинг вы выполните в TacoResource):

{

 "_embedded": {

   "tacos": [

     ...

   ]

 }

}

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

Но если вы настаиваете на лени, то, возможно, для вас есть беспроигрышный сценарий, если вы используете Spring Data для своих репозиториев. Давайте посмотрим, как Spring Data REST может помочь вам автоматически создавать API на основе репозиториев, созданных с помощью Spring Data в главе 3.

6.3 Включение служб с поддержкой данных

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

Spring Data REST - это еще один член семейства Spring Data, который автоматически создает API REST для репозиториев, созданных на основе Spring Data. Делая чуть больше, чем просто добавление Spring Data REST в вашу сборку, вы получаете API с операциями для каждого интерфейса репозитория, который вы определили.

Чтобы начать использовать Spring Data REST, добавьте в свою сборку следующую зависимость:

org.springframework.boot

spring-boot-starter-data-rest

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

REST endpoint, которые создает Spring Data REST, по крайней мере так же хороши (и, возможно, даже лучше) тех, которые вы создали сами. Поэтому на данном этапе не стесняйтесь выполнить небольшую работу по удалению всех классов аннотированных @RestController, которые вы создали до этого момента, прежде чем двигаться дальше.

Чтобы опробовать endpoint, предоставляемые Spring Data REST, вы можете запустить приложение и начать тыкать в некоторые URL-адреса. Исходя из набора репозиториев, который вы уже определили для Taco Cloud, вы сможете выполнять запросы GET для тако, ингредиентов, заказов и пользователей.

Например, вы можете получить список всех ингредиентов, сделав запрос GET для /ingredients. Используя curl, вы можете получить что-то похожее на это (сокращенно, чтобы показать только первый ингредиент):

$ curl localhost:8080/ingredients

{

   "_embedded" : {

       "ingredients" : [ {

           "name" : "Flour Tortilla",

           "type" : "WRAP",

           "_links" : {

               "self" : {

                   "href" : "http://localhost:8080/ingredients/FLTO"

               },

               "ingredient" : {

                   "href" : "http://localhost:8080/ingredients/FLTO"

               }

           }

       },

       ...

       ]

   },

   "_links" : {

       "self" : {

           "href" : "http://localhost:8080/ingredients"

       },

       "profile" : {

           "href" : "http://localhost:8080/profile/ingredients"

       }

   }

}

Вот Это Да! Не делая ничего, кроме добавления зависимости к вашей сборке, вы получаете не endpoint для ингредиентов, но и возвращающиеся ресурсы также содержат гиперссылки! Притворяясь клиентом этого API, вы также можете использовать curl, чтобы перейти по ссылке self для записи мучной лепешки (FLTO):

$ curl http://localhost:8080/ingredients/FLTO

{

 "name" : "Flour Tortilla",

 "type" : "WRAP",

 "_links" : {

   "self" : {

     "href" : "http://localhost:8080/ingredients/FLTO"

   },

   "ingredient" : {

     "href" : "http://localhost:8080/ingredients/FLTO"

   }

 }

}

Чтобы не отвлекаться, мы не будем тратить больше времени в этой книге, копаясь во всех endpoint и опциях, созданных Spring Data REST. Но вы должны знать, что он также поддерживает методы POST, PUT и DELETE для endpoint, которые он создал. Верно: вы можете вызвать POST для /ingredients, чтобы создать новый ингредиент, и DELETE для /ingredients/FLTO, чтобы удалить лепешки из меню.

Одна вещь, которую вы, возможно, захотите сделать, это установить базовый путь для API, чтобы его endpoints были различны и не конфликтовали ни с какими контроллерами, которые вы пишете. (Фактически, если вы не удалите созданный ранее IngredientsController, он будет мешать endpoints /ingredients, предоставляемой Spring Data REST.) Чтобы настроить базовый путь для API, установите свойство spring.data.rest.base-path:

spring:

 data:

   rest:

     base-path: /api

Это установит базовый путь для endpoint Spring Data REST в /api. Следовательно, конечной точкой ингредиентов теперь является /api/ingredients. Теперь проверим новый базовый путь, запросив список тако:

$ curl http://localhost:8080/api/tacos

{

"timestamp": "2018-02-11T16:22:12.381+0000",

"status": 404,

"error": "Not Found",

"message": "No message available",

"path": "/api/tacos"

}

О, Боже!  Это сработало не совсем так, как ожидалось. У вас есть сущность Ingredient и интерфейс IngredientRepository, которые Spring Data REST предоставляет с помощью endpoint /api/ingredients. Итак, если у вас есть сущность Taco и интерфейс TacoRepository, почему Spring Data REST не предоставляет endpoint /api/tacos?