Новости

01.07.2022

Книга «Spring Boot по-быстрому»

Spring Boot, который скачивают более 75 миллионов раз в месяц, — наиболее широко используемый фреймворк Java. Его удобство и возможности совершили революцию в разработке приложений, от монолитных до микросервисов. Тем не менее простота Spring Boot может привести в замешательство. Что именно разработчику нужно изучить, чтобы сразу же выдавать результат? Это практическое руководство научит вас писать успешные приложения для критически важных задач. Марк Хеклер из VMware, компании, создавшей Spring, проведет вас по всей архитектуре Spring Boot, охватив такие вопросы, как отладка, тестирование и развертывание. Если вы хотите быстро и эффективно разрабатывать нативные облачные приложения Java или Kotlin на базе Spring Boot с помощью реактивного программирования, создания API и доступа к разнообразным базам данных — эта книга для вас.

Создаем первый Spring Boot REST API


В этой главе я расскажу и покажу, как разработать простейшее работающее приложение с помощью Spring Boot. Поскольку большинство приложений предполагают доступ пользователей к серверным облачным ресурсам, обычно через пользовательский интерфейс клиентской части, программный интерфейс приложений (Application Programming Interface, API) — прекрасная отправная точка как для изучения, так и для применения на практике. Что ж, приступим.

«Как» и «почему» API


Эпоха монолитных приложений, отвечающих сразу за все, прошла.

Это не значит, что их больше нет или что их не будут создавать в дальнейшем. В силу различных обстоятельств монолитное приложение, предоставляющее «в одном флаконе» множество возможностей, все еще может пригодиться, особенно в следующих случаях:
предметная область, а значит и ее границы, практически неизвестна;

  • предоставляемые функциональные возможности тесно сцеплены, и скорость взаимодействий между модулями важнее гибкости;
  • требования по масштабированию всех функциональных возможностей известны и согласованы между собой;
  • функциональность стабильна, изменения происходят медленно или носят ограниченный характер, либо наблюдается и то и другое.


На все остальные случаи жизни существуют микросервисы.

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

В любой распределенной системе — и не сомневайтесь, включающая микросервисы система относится как раз к этой категории — ключевым фактором является обмен информацией. Нет сервиса, который был бы как остров1. И хотя существуют многочисленные механизмы соединения приложений/микросервисов, чаще всего мы начинаем с эмуляции того, что пронизывает всю нашу повседневную жизнь, — интернета.

Интернет предназначен для обмена информацией. На самом деле создатели его предшественника, Сети Агентства перспективных исследовательских проектов (Advanced Research Projects Agency Network, ARPANET), предусмотрели необходимость поддержания обмена информацией между системами даже в случае серьезных сбоев. Логично предположить, что подход на основе HTTP, сходный с тем, что мы используем ежедневно, тоже позволит создавать, извлекать, обновлять и удалять различные ресурсы по сети.

И хотя я очень люблю историю, но не стану углубляться в особенности развития REST API, скажу только, что Рой Филдинг (Roy Fielding) изложил их принципы в своей диссертации 2000 года, основанной на объектной модели HTTP 1994 года.

Что такое REST и почему это важно


Как уже упоминалось, API — это спецификация/интерфейс, написанный нами, разработчиками, чтобы наш код мог использовать другой код: библиотеки, прочие приложения или сервисы. Но что означает аббревиатура REST в REST API?

REST представляет собой акроним фразы «передача состояния представления» (representational state transfer) — довольно загадочного способа заявить, что наше приложение взаимодействует с другим. Приложение A не ожидает от приложения Б сохранения своего состояния — текущего и накопленной информации о процессах — между вызовами связи. Приложение A передает представление нужной части своего состояния с каждым запросом к приложению Б. Сразу видно, почему это повышает живучесть и отказоустойчивость: состояние взаимодействий приложения Б с приложением A сохраняется в случае аварийного сбоя и перезапуска первого — приложение A может повторно выполнить запрос и продолжить с того же места, где произошел сбой.

Ресурсы, которые придерживаются этого общего принципа, часто называют приложениями/сервисами без сохранения состояния (stateless), поскольку каждый сервис поддерживает собственное состояние даже в ходе последовательности взаимодействий и не ждет от других сервисов/приложений, что они сделают это вместо него.

 

API в стиле HTTP-глаголов


А как же REST API, иногда называемый реализующим REST (RESTful API)?
Существует несколько стандартных HTTP-команд, описанных в рабочих предложениях (request for comments, RFC) IETF (Internet Engineering Task Force, инженерный совет Интернета). Небольшое их количество постоянно применяется для создания API, периодически используются еще несколько. REST API обычно создаются на основе следующих HTTP-команд:

  • POST;
  • GET;
  • PUT;
  • PATCH;
  • DELETE.


Эти команды соответствуют типовым операциям над ресурсами: созданию (POST), чтению (GET), обновлению (PUT и PATCH) и удалению (DELETE).

Следует признать, что я несколько размыл границы команд, соотнеся PUT с обновлением ресурса, и чуть меньше — тем, что соотнес POST с созданием ресурса. Читатели, потерпите немного, я проясню все, когда мы будем обсуждать реализации.


Иногда применяются также следующие две команды:

  • OPTIONS;
  • HEAD.


Их можно использовать для извлечения параметров обмена информацией для пар «запрос — ответ» (OPTIONS) и заголовка ответа без его тела (HEAD).

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

Возвращаемся к Initializr


Начнем, как обычно, со Spring Initializr, как показано на рис. 3.1. Я изменил значения полей Group и Artifact в соответствии со своими предпочтениями (можете спокойно использовать обозначения, которые нравятся вам), указал Java 11 в разделе Options (необязательно, подойдет любая из перечисленных версий) и выбрал только зависимость Spring Web. Как указано в отображаемом описании, эта зависимость предоставляет несколько функциональных возможностей, в том числе возможность «создания веб-приложений, включая реализующие REST, с помощью Spring MVC» (курсив мой). Именно это нам и нужно.

image
Сгенерировав проект в Initializr и сохранив полученный файл .zip на локальной машине, извлекаем из архива файлы проекта — обычно это можно сделать, дважды щелкнув кнопкой мыши на скачанном файле sbur-rest-demo.zip в Проводнике или с помощью команды unzip из окна командной оболочки/терминала. После этого открываем проект в своем любимом IDE или текстовом редакторе, в результате чего видим что-то вроде изображенного на рис. 3.2.

image
Создание простого класса предметной области
Для работы с ресурсами необходимо написать соответствующий им код. Начнем с создания очень простого класса предметной области, соответствующего нужному нам ресурсу.
Я страстный любитель кофе, как знают мои друзья, в число которых теперь входите и вы. Поэтому в качестве предметной области для этого примера я возьму кофе и создам класс, представляющий его конкретный вид.

Начнем с создания класса Coffee. Без него в этом примере не обойтись, ведь нам нужен какой-либо ресурс для демонстрации работы с ресурсами через REST API. Но простота или сложность предметной области для этого примера несущественны, так что можно взять попроще, чтобы сосредоточиться на конечной цели — итоговом REST API.

Как показано на рис. 3.3, класс Coffee имеет две переменные-члена:

  • поле id, однозначно идентифицирующее конкретный вид кофе;
  • поле name с названием кофе.


image

Я объявил поле id как final, чтобы ему можно было присвоить значение только один раз и нельзя было модифицировать в дальнейшем, поэтому значение ему необходимо присваивать при создании экземпляра класса Coffee и метода-модификатора у него нет.

Я создал два конструктора: один принимает оба параметра, другой предоставляет уникальный идентификатор, если он не указан при создании экземпляра Coffee.

Далее я создал методы доступа (accessor) и изменения (mutator) — они же метод-сеттер и метод-геттер, если вы предпочитаете такую терминологию, — для поля name, которое не объявлено final, то есть является изменяемым. Это спорное архитектурное решение, но для этого примера оно хорошо подходит.

С предметной областью разобрались. Пришло время для REST.

GET


Вероятно, чаще всего используемая из наиболее широко используемых HTTP-команд — GET. Так что приступим!

Коротко об аннотации @RestController


Если не залезать слишком глубоко в кроличью нору, то можно сказать, что фреймворк Spring MVC («модель — представление — контроллер») был создан для разделения ответственности относительно данных, их доставки и визуализации в предположении, что представления будут визуализироваться в виде веб-страницы, отображаемой сервером. Для связи всего этого служит аннотация Controller.

Controller — стереотип/псевдоним для аннотации Component, означающий, что при запуске приложения из этого класса образуется компонент Spring, создаваемый и управляемый контейнером инверсии управления (inversion of control, IoC) в приложении. Классы, снабженные аннотацией Controller, включают объект Model для передачи слою представления данных, соответствующих модели, и отображения (совместно с ViewResolver) приложением конкретного представления с помощью специальной технологии.

Можно также указать классу, снабженному аннотацией Controller, возвращать ответ в формате нотации объектов JavaScript (JSON) или другом ориентированном на данные формате, например XML, посредством добавления к классу или методу аннотации @ResponseBody (по умолчанию JSON). В результате в возвращаемое методом значение входит все тело ответа на веб-запрос вместо лишь части Model.

Удобная аннотация @RestController сочетает в себе аннотации Controller и @ResponseBody, упрощая тем самым код и более прозрачно выражая намерения разработчика. Снабдив класс аннотацией @RestController, можно приступать к созданию REST API.

Попробуем GET


REST API работают с объектами — либо по отдельности, либо в виде группы связанных объектов. Возвращаясь к нашему сценарию с кофе: может понадобиться извлечь конкретный вид кофе, все виды кофе или все сильно обжаренные, все относящиеся к определенному диапазону идентификаторов либо со словом «Колумбийский» в названии, например. Чтобы учесть варианты извлечения одного или нескольких экземпляров класса, рекомендуется создавать в коде несколько методов.

Начнем с создания списка объектов класса Coffee для метода, возвращающего несколько объектов Coffee, как показано в следующем простейшем описании класса. Я определил переменную для хранения этой группы видов кофе в виде List объектов Coffee. В качестве высокоуровневого интерфейса для своего типа переменной-члена выбрал List, но на самом деле присваиваю для использования внутри класса RestApiDemoController пустой ArrayList:

@RestController
class RestApiDemoController {
    private List<Coffee> coffees = new ArrayList<>();
}

 

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


Никогда не помешает иметь немного данных для извлечения, чтобы убедиться, что все работает должным образом. В следующем коде я написал конструктор класса RestApiDemoController, добавив код для заполнения списка видов кофе при создании объекта:

@RestController
class RestApiDemoController {
   private List<Coffee> coffees = new ArrayList<>();
   public RestApiDemoController() {
      coffees.addAll(List.of(
         new Coffee("Café Cereza"),
         new Coffee("Café Ganador"),
         new Coffee("Café Lareño"),
         new Coffee("Café Três Pontas")
      ));
  }
}


Как показано в следующем коде, я создал в классе RestApiDemoController метод, возвращающий итерируемую группу видов кофе, представленную переменной экземпляра coffees. Я решил воспользоваться Iterable, поскольку желаемую функциональность для этого API легко обеспечит любой итерируемый тип.

Используем @RequestMapping для получения (GET) списка видов кофе:

@RestController
class RestApiDemoController {
   private List<Coffee> coffees = new ArrayList<>();
   public RestApiDemoController() {
      coffees.addAll(List.of(
         new Coffee("Café Cereza"),
         new Coffee("Café Ganador"),
         new Coffee("Café Lareño"),
         new Coffee("Café Três Pontas")
     ));
   }
   @RequestMapping(value = "/coffees", method = RequestMethod.GET)
   Iterable<Coffee> getCoffees() {
      return coffees;
   }
}


К аннотации @RequestMapping я добавил спецификацию пути /coffees и тип метода RequestMethod.GET, указывающий, что метод будет отвечать на запросы по пути /coffees, причем возможны только запросы типа HTTP GET. Этот метод отвечает за извлечение данных, но не за обновления какого-либо вида. Spring Boot с помощью включенных в Spring Web зависимостей Jackson автоматически выполняет маршалинг и демаршалинг объектов в JSON и другие форматы.

К аннотации @RequestMapping я добавил спецификацию пути /coffees и тип метода RequestMethod.GET, указывающий, что метод будет отвечать на запросы по пути /coffees, причем возможны только запросы типа HTTP GET. Этот метод отвечает за извлечение данных, но не за обновления какого-либо вида. Spring Boot с помощью включенных в Spring Web зависимостей Jackson автоматически выполняет маршалинг и демаршалинг объектов в JSON и другие форматы.

Можно еще больше упростить этот код с помощью другой удобной аннотации — @GetMapping, допускающей лишь запросы типа GET, что позволяет указать только путь (даже без path =, поскольку никакого разрешения конфликтов параметров не требуется), сокращая тем самым стереотипный код. Следующий код ясно демонстрирует преимущества перехода на эту аннотацию в смысле удобочитаемости:

@GetMapping("/coffees")
Iterable<Coffee> getCoffees() {
   return coffees;
}

Комментарии: 0

Пока нет комментариев


Оставить комментарий






CAPTCHAОбновить изображение

Наберите текст, изображённый на картинке

Все поля обязательны к заполнению.

Перед публикацией комментарии проходят модерацию.