Я подозревал, что мы используем публичное API для одного из наших учебных материалов, который содержит нежелательный материал. После подтверждения этого, я немедленно заявил о том, что бы мы выбрали другой источник. Желая избежать такого в будущем, я решил, что лучшим решением будет создание нашего собственного RESTful сервиса цитат. Также я решил использовать самый лучший инструмент для этого, Spring стек, и был готов начать миграцию на следующий день.
Выбор инструментов
Чтобы избавиться от лишнего, я написал список того, какие по моему мнению будут инструменты правильным выбором для создания RESTful web сервиса.
- Spring Data JPA — быстрая предзагрузка, выборка и обновление контента
- Spring MVC — надежная поддержка REST, начиная с Spring 3
- Spring Boot — создание всех необходимых компонентов практически без усилий
- start.spring.io — сборка проекта простым выбором необходимых компонентов
Я быстро ушел от решения добавления, удаления, управления или просмотра данных через web-страницу. Вместо этого, я обратил внимание на подаче фиксированного количества данных с точно такой же структурой, которая необходима учебному материалу.
Выбор контента
Оригинальный контент для учебного материала был из серии шуток «Chunk Norris». Я люблю хорошо посмеяться. Но когда я пересмотрел публичное API, я увидел, что некоторые из шуток были немного прогорклыми. После небольшого обсуждения с коллегами, пришла идея приводить цитаты из истории. Я взял эту идею и изменил её немного. Для моей предстоящей книги, Learning Spring Boot, я собрал цитаты от различных разработчиков Spring Boot. Поэтому я решил использовать её в качестве источника.
Время для кода!
Чтобы избавиться от лишнего, я посетил start.spring.io. Это Spring Boot приложение позволяет уточнить детали вашего нового проекта, выбрать версию Java и компоненты запуска Spring Boot при необходимости. Я использовал свой список(см. выше) и создал новый gradle-проект.
Определение домена
Первое, что я сделал после распаковки и импорта в IDE, скопировал объекты домена как показано в учебном материале. Таким образом, я могу убедиться, что данные, отправленные моим REST сервисом были корректны. Поскольку POJO-классы в моем приложении идентично, я не буду их приводить здесь.
Затем я создал Spring Data репозиторий.
public interface QuoteRepository extends CrudRepository {}
Это пустой интерфейс, определяющий обработчики Quote
объектов с внутренним первичным ключем типа Long
. Расширяясь от Spring Data Commons CrudRepository
, он наследует методы для операций с БД.
Следующий шаг? Инициализируем некоторые данные. Я создал DatabaseLoader
:
@Service
public class DatabaseLoader {
private final QuoteRepository repository;
@Autowired
public DatabaseLoader(QuoteRepository repository) {
this.repository = repository;
}
@PostConstruct
void init() {
repository.save(new Quote("Working with Spring Boot is like pair-programming with the Spring developers."));
// more quotes...
}
}
- Помеченный как
@Service
, он автоматически будет подхвачен@ComponentScan
при запуске приложения - Используется конструктор с автоподключением копии
QuoteRepository
для большей доступности @PostConstruct
говорит Spring MVC запускать метод загрузки данных после того, как все бины будут созданы- В итоге, метод
init()
используется Spring Data JPA для создания целой кучи цитат
Т.к. я выбрал Н2 в качестве моей БД(com.h2database:h2) в build.gradle
, здесь нет всего того, что требуется для её установки(спасибо Spring Boot).
Создание контроллера
После того, как я создал этот слой БД, я захотел создать API. C Spring MVC это было не трудно.
@RestController
public class QuoteController {
private final QuoteRepository repository;
private final static Quote NONE = new Quote("None");
private final static Random RANDOMIZER = new Random();
@Autowired
public QuoteController(QuoteRepository repository) {
this.repository = repository;
}
@RequestMapping(value = "/api", method = RequestMethod.GET)
public List getAll() {
return StreamSupport.stream(repository.findAll().spliterator(), false)
.map(q -> new QuoteResource(q, "success"))
.collect(Collectors.toList());
}
@RequestMapping(value = "/api/{id}", method = RequestMethod.GET)
public QuoteResource getOne(@PathVariable Long id) {
if (repository.exists(id)) {
return new QuoteResource(repository.findOne(id), "success");
} else {
return new QuoteResource(NONE, "Quote " + id + " does not exist");
}
}
@RequestMapping(value = "/api/random", method = RequestMethod.GET)
public QuoteResource getRandomOne() {
return getOne(nextLong(1, repository.count() + 1));
}
private long nextLong(long lowerRange, long upperRange) {
return (long)(RANDOMIZER.nextDouble() * (upperRange - lowerRange)) + lowerRange;
}
}
Давайте разберем его:
- Весь класс помечен как
@RestController
. Это означает, что все адреса возвращают объекты не для просмотра - У меня есть несколько статических объектов, в частности
NONE
цитата и Java 8Random
для произвольного выбора цитат - Используется конструктор с автоподключением
QuoteRepository
/api
— выборка всех цитат
/api/{id}
— выбока цитаты по id
/api/random
— выборка произвольной цитаты
Для выборки ВСЕХ цитат я использовал Java 8 stream, чтобы обернуть метод findAll()
, чтобы в свою очередь обернуть каждое значение в QuoteResource
. Результат будет получен типа List
.
Для выборки одной цитаты, проверяется на существование по переданному id. Если не существует, то возвращается NONE
. Иначе — обернутая цитата.
И наконец, для выборки произвольной цитаты я использовал метод утилитарного класса Random
из Java 8 nextLong()
для выбора Long
в диапазоне между lowerRange
и upperRange
включительно.
QuoteResource
? Quote
— центральный объект домена, возвращаемый QuoteRepository
. В соответствии с предыдущим публичным API, я обернул каждый экземпляр в QuoteResource
, который включает в себя код состояния.Тестирование результатов
В данном случае, по умолчанию Application
класс был создан в start.spring.io и готов к запуску.
$ curl localhost:8080/api/random { type: "success", value: { id: 1, quote: "Working with Spring Boot is like pair-programming with the Spring developers." } }
Та дам!
В завершение, я собрал JAR-файл и отправил в Pivotal Web Services. Вы можете сами увидеть результат на сайте http://gturnquist-quoters.cfapps.io/api/random.
Осталось сказать, что я изменил для учебного материала ТОЛЬКО ОДНУ СТРОЧКУ КОДА.
Чтобы посмотреть весь код, посетите github.com/gregturn/quoters.
Нерешенные вопросы
- Этот RESTful сервис соответствует Level 2 — HTTP Verbs Richardson Maturity Model. В то время как лучше поддерживать Level 3 — Hypermedia. С Spring HATEOAS это легче, чем добавление каждой hypermedia ссылки. Оставайтесь с нами
- Здесь нет привычной web-страницы. Это было бы замечательно, это не требуется
- Контент фиксирован и определяется внутри приложения. Для создания динамического контента, нам необходимо открыть двери для POST и PUT. Это предполагает желание введения обеспечения безопасности должным образом
Это некоторые нерешенные вопросы, которые не входят в бюджет по времени и не требуются для решения исходной проблемы. Но они являются хорошими упражнениями, которые вы можете изучить! Вы можете клонировать проект в GitHub и стрельнуть самим себе!