6.3.1 Настройка путей ресурсов и имен отношений
На самом деле Spring Data REST предоставляет вам endpoint для работы с тако. Но насколько бы умным ни был Spring Data REST, он показывает несколько странным, в том как он предоставляет конечную точку tacos.
При создании endpoint для репозиториев Spring Data, Spring Data REST пытается мультиплицировать связанный класс сущностей. Для объекта Ingredient endpoint является /ingredients. Для сущностей Order и User это /orders и /users. Пока все логично.
Но иногда, например, с “taco”,, оно путается в слове, и множественная версия не совсем верна. Как выяснилось, Spring Data REST назвала“taco” как “tacoes”, поэтому, чтобы сделать запрос на тако, вы должны учесть это и запросить /api/tacoes:
% curl localhost:8080/api/tacoes
{
"_embedded" : {
"tacoes" : [ {
"name" : "Carnivore",
"createdAt" : "2018-02-11T17:01:32.999+0000",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/tacoes/2"
},
"taco" : {
"href" : "http://localhost:8080/api/tacoes/2"
},
"ingredients" : {
"href" : "http://localhost:8080/api/tacoes/2/ingredients"
}
}
}]
},
"page" : {
"size" : 20,
"totalElements" : 3,
"totalPages" : 1,
"number" : 0
}
}
Вы можете быть удивлены, откуда я знал, что “taco” будет истолковано как “tacoes”. Оказывается, Spring Data REST также предоставляет домашний ресурс, содержащий ссылки для всех открытых endpoint. Просто сделайте запрос GET к базовому пути API:
$ curl localhost:8080/api
{
"_links" : {
"orders" : {
"href" : "http://localhost:8080/api/orders"
},
"ingredients" : {
"href" : "http://localhost:8080/api/ingredients"
},
"tacoes" : {
"href" : "http://localhost:8080/api/tacoes{?page,size,sort}",
"templated" : true
},
"users" : {
"href" : "http://localhost:8080/api/users"
},
"profile" : {
"href" : "http://localhost:8080/api/profile"
}
}
}
Как вы можете видеть, здесь есть ссылки для всех ваших сущностей. Все выглядит хорошо, за исключением ссылки tacos, где и имя отношения, и URL имеют нечетное множественное число “taco”.
Хорошей новостью является то, что вам не нужно мириться с этой маленькой причудой Spring Data REST. Добавив простую аннотацию к классу Taco, вы можете настроить как имя отношения, так и этот путь:
@Data
@Entity
@RestResource(rel="tacos", path="tacos")
public class Taco {
...
}
Аннотация @RestResource позволяет вам присвоить сущности любое имя и путь отношения. В этом случае вы устанавливаете их обоих на “tacos”. Теперь, когда вы запрашиваете домашний ресурс, вы видите ссылку tacos с правильным множественным числом:
"tacos" : {
"href" : "http://localhost:8080/api/tacos{?page,size,sort}",
"templated" : true
},
Это также сортирует путь для endpoint, чтобы вы могли создавать запросы к /api/tacos для работы с ресурсами taco.
Говоря о сортировке, давайте посмотрим, как можно отсортировать результаты с Spring Data REST endpoint.
6.3.2 Paging и sorting
Возможно, вы заметили, что все ссылки на домашнем ресурсе предлагают дополнительные параметры page, size, и sort. По умолчанию запросы возвращающие коллекцию, например /api/tacos, возвращают первую страницу с количеством объектов на ней числом до 20-ти. Но вы можете настроить размер страницы и отображаемую страницу, указав параметры page и size в своем запросе.
Например, чтобы запросить первую страницу тако, где размер страницы равен 5, вы можете выполнить следующий запрос GET (используя curl):
$ curl "localhost:8080/api/tacos?size=5"
Предположим, что общее количество тако более пяти, вы можете запросить вторую страницу тако, добавив параметр page:
$ curl "localhost:8080/api/tacos?size=5&page=1"
Обратите внимание, что параметр страницы начинается с нуля, что означает, что запрос страницы 1 на самом деле запрашивает вторую страницу. (Вы также заметите, что многие запросы командной строки смещаются над амперсандом в запросе, поэтому я привел весь URL в предыдущей команде curl.)
Вы можете использовать манипуляции со строками, чтобы добавить эти параметры в URL, но HATEOAS приходит на помощь, предлагая ссылки на первую, последнюю, следующую и предыдущие страницы в ответе:
"_links" : {
"first" : {
"href" : "http://localhost:8080/api/tacos?page=0&size=5"
},
"self" : {
"href" : "http://localhost:8080/api/tacos"
},
"next" : {
"href" : "http://localhost:8080/api/tacos?page=1&size=5"
},
"last" : {
"href" : "http://localhost:8080/api/tacos?page=2&size=5"
},
"profile" : {
"href" : "http://localhost:8080/api/profile/tacos"
},
"recents" : {
"href" : "http://localhost:8080/api/tacos/recent"
}
}
С этими ссылками клиент API не должен отслеживать, на какой странице он находится, и объединять параметры с URL. Вместо этого он должен просто знать, что нужно искать одну из этих навигационных ссылок на странице по ее имени и следовать за ней.
Параметр sort позволяет сортировать полученный список по любому свойству объекта. Например, вам нужен способ получить 12 последних созданных тако. Вы можете сделать это, указав следующее сочетание параметров страницы и сортировки:
$ curl "localhost:8080/api/tacos?sort=createdAt,desc&page=0&size=12"
Здесь параметр sort указывает, что должна произвестись сортировка по свойству createdDate в порядке убывания (чтобы самые новые тако были первыми). Параметры page и size указывают, что вы должны увидеть первую страницу с 12 тако.
Это именно то, что нужно UI, чтобы показать самые последние созданные тако. Результат примерно такой же, как /design/recent endpoint, которую вы определили в DesignTacoController ранее в этой главе.
Однако есть небольшая проблема. Код пользовательского интерфейса должен быть жестко запрограммирован, чтобы запросить список тако с этими параметрами. Конечно, это будет работать. Но вы добавляете некоторую хрупкость клиенту, делая его слишком осведомленным о том, как создать запрос API. Было бы здорово, если бы клиент мог найти URL-адрес из списка ссылок. И было бы еще круче, если бы URL был более лаконичным, как /design/recent endpoint, которую вы использовали ранее.
6.3.3 Добавление пользовательских endpoint
Spring Data REST отлично подходит для создания endpoint для выполнения операций CRUD с репозиториями Spring Data. Но иногда вам нужно отойти от CRUD API по умолчанию и создать endpoint, которая доходит до сути проблемы.
Ничто не мешает вам реализовать любой endpoint, который вы хотите, в аннотированном компоненте @RestController, чтобы дополнить то, что Spring Data REST генерирует автоматически. Фактически, вы можете воскресить DesignTacoController, описанный ранее в этой главе, и он все равно будет работать вместе с конечными точками, предоставленными Spring Data REST.
Но когда вы пишете свои собственные API контроллеров, их endpoint кажутся несколько оторванными от endpoint Spring Data REST по нескольким причинам:
Ваши собственные endpoint контроллера не отображаются в соответствии с базовым путем Spring Data REST. Вы можете принудительно назначить префиксу их сопоставлений любой базовый путь, который вы хотите, включая базовый путь Spring Data REST, но если базовый путь должен был измениться, вам необходимо отредактировать сопоставления контроллера, чтобы они соответствовали.
Любые endpoint, которые вы определяете в своих собственных контроллерах, не будут автоматически включены в качестве гиперссылок в ресурсы, возвращаемые endpoint Spring Data REST. Это означает, что клиенты не смогут обнаружить ваши пользовательские endpoint с именем отношения.
Давайте сначала обратимся к проблеме базового пути. Spring Data REST включает в себя @RepositoryRestController, новую аннотацию для аннотирования классов контроллеров, чьи отображения должны принимать базовый путь, который совпадает с тем, который настроен для endpoint Spring Data REST. Проще говоря, все сопоставления в контроллере, помеченном @RepositoryRestController, будут иметь свой путь с префиксом со значением свойства spring.data.rest.base-path (которое вы настроили как / api).
Вместо того, чтобы воскрешать DesignTacoController, в котором было несколько методов-обработчиков, которые вам не нужны, вы создадите новый контроллер, который содержит только метод recentTacos(). RecentTacosController в следующем листинге помечен аннотацией @RepositoryRestController для принятия базового пути Spring Data REST для его сопоставлений запросов (для request mapping).
Листинг 6.7. Применение базового пути Spring Data REST к контроллеру
package tacos.web.api;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
import java.util.List;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import tacos.Taco;
import tacos.data.TacoRepository;
@RepositoryRestController
public class RecentTacosController {