REST API с использованием Spring

Spring Framework WebMVC позволяет разрабатывать не только классические веб-приложения, но и реализовывать REST API. В этой статье я опишу процесс разработки REST API простого проекта на Java с использованием Spring Boot и Spring Framework.

О REST и его поддержке в Spring

REST — это архитектурный стиль взаимодействия приложений в сети. Но в отличии от SOAP, у REST отсутствует какой-либо стандарт, а данные между клиентом и сервером могут предаваться в любом виде, будь то JSON, XML, YAML и т.д.

Spring Framework предоставляет богатый набор инструментов, упрощающий разработку REST API: инструменты для маршрутизации запросов, классы-кодеки для преобразования JSON/XML в объекты требуемых типов и т.д.

HTTP-методы

В отличии от классических веб-приложений, в которых используются только методы GET и POST, в REST API используются практически все HTTP-методы, например:

  • GET — для получения объекта или списка объектов
  • HEAD — проверка существования объекта
  • OPTIONS — проверка доступных методов для указанного пути
  • POST — для создания нового объекта
  • PUT — для полного изменения объекта или помещения объекта в список
  • PATCH — для частичного изменения объекта
  • DELETE — для удаления объекта

Стоит помнить, что хорошей практикой является использование GET только для запросов, которые не изменяют состояние объекта. Если запрос должен изменить состояние объекта, то должен использоваться соответствующий HTTP-метод, хотя бы POST.

Для маршрутизации запросов в Spring Framework используется аннотация @RequestMapping с указанием HTTP-метода при помощи свойства method или более простые аннотации вроде
@GetMapping, @PostMapping, @DeleteMapping и т.д.

HTTP-заголовки

Очень важными при разработке REST API являются и HTTP-заголовки. Например, клиент может, указывать MIME-тип содержимого отправляемого запроса при помощи заголовка Content-type или ожидаемый MIME-тип ответа или даже его версию при помощи заголовка Accept.

Возможности Spring Framework WebMVC позволяют маршрутизировать запросы в зависимости не только от их пути или методов, но и в зависимости от заголовков:

Базовый проект

Проект будет реализовывать REST API списка задач. В проекте мы будем использовать Spring Boot, из которого нам понадобятся стартеры:

  • Web — для поддержки веб
  • JDBC — для работы с базой данных
  • H2 — в качестве тестовой базы данных

Код pom.xml приведён ниже:

Поскольку в проекте будет использоваться реляционная база данных, нам потребуется описать SQL-схему таблицы, в которой будут храниться задачи:

Класс, описывающий эту структуру:

REST контроллер

Теперь, когда у нас есть вся необходимая структура, мы можем приступить непосредственно к разработке REST API. Для этого создадим заготовку класса TodoRestController:

  • Контекст приложения создаст и зарегистрирует экземпляр класса, поскольку указана аннотация @RestController. Кроме этого аннотация подсказывает, что возвращаемые методами значения являются телом ответов на запросы. Эта аннотация заменяет собой пару @Controller + @ResponseBody.
  • Аннотация @RequestMapping указывает на маршрутизацию запросов: все запросы, начинающиеся с api/todo будут обрабатываться этим контроллером.
  • Объект типа JdbcOperations будет внедрён через конструктор.

Получение списка задач

Метод получения списка задач должен быть вызван при GET-запросе по пути /api/todo, он должен обратиться к базе данных и вернуть полученный список с HTTP-кодом 200 OK.

  • @GetMapping указывает, что метод getTodoList должен обрабатывать GET-запросы с путём /api/todo. Альтернативно может быть использована аннотация @RequestMapping(method = RequestMethod.GET).
  • При помощи объекта типа ResponseEntity, возвращаемого методом, можно тонко настроить ответ, включая коди заголовки ответа. Фактически можно просто вернуть список задач, не оборачивая его в ResponseEntity.

Получение одной задачи

Метод получения одной задачи по её идентификатору должен быть вызван при GET-запросе по пути /api/todo/{todoId}; он должен обратиться к базе данных и вернуть задачу с HTTP-кодом 200 OK или 404 Not Found, если задачи нет в БД.

  • @GetMapping(«{todoId}») указывает, что метод getTodo должен обрабатывать GET-запросы с путём /api/todo/{todoId}. Фигурные скобки в данном случае указывают, что todoId — переменная пути (Path variable) и может быть использована в аргуметах метода. Можно задать более строгие правила валидации пути, добавив после todoId через двоеточие регулярное выражение, которое будет использоваться для валидации пути.
  • @PathVariable указывает, что значение аргумента todoId должно браться из пути запроса. По умолчанию все значения являются строковыми, но при помощи классов-конвертеров строка преобразуется в объект класса java.util.UUID. Обратите внимание, что имя аргумента соответствует плейсхолдеру, указанному в @GetMapping, в противном случае нам пришлось бы указать плейсхолдер в свойстве name аннотации @PathVariable.
  • Исключение IncorrectResultSizeDataAccessException выбрасывается в случае, если база данных вернула в ответ на запрос количество строк неравное 1. Если актуальное количество строк рано 0, то соответствующая запись в БД отсутствует, и мы можем вернуть ответ с HTTP-кодом 404 Not Found; если строк больше 1, то мы имеем дело с какой-то ошибкой и можем вернуть ответ с HTTP-кодом 500 Internal Server Error.

Создание задачи

Метод создания задачи должен быть вызван при POST-запросе по адресу /api/todo; он должен сохранить в БД полученные данные и вернуть сохранённую задачу в ответ.

  • Аннотация @PostMapping указывает, что данный метод обрабатывает все POST-запросы с путём /api/todo. Свойство consumes задаёт список MIME-типов, которые может обрабатывать данный метод.
  • Аннотация @RequestBody указывает, что отмеченный аргумент является телом запроса. Тело запроса преобразуется в объект нужного класса при помощи конвертеров.
  • Тело запроса автоматически преобразуется в объект типа TodoPayload при помощи класса-кодека.

Изменение задачи

Метод изменения задачи должен быть вызван при PUT-запросе по адресу /api/todo/{todoId}; он должен сохранить изменения в БД и вернуть ответ со статусом 204 No Content, либо вернуть ответ со статусом 404 Not Found, если задача не найдена.

Этот метод может возвращать ответ, содержащий какую-то информацию, например, изменённую задачу, но клиент и так знает о внесённых изменениях.

Удаление задачи

Метод удаления задачи должен быть вызван при DELETE-запросе по адресу /api/todo/{todoId}; он должен удалить задачу из БД и вернуть ответ со статусом 204 No Content, либо вернуть ответ со статусом 404 Not Found, если задача не найдена.

Я рассмотрел в этой статье только базовые моменты разработки REST API при помощи Spring Framework WebMVC. В следующих постах я постараюсь описать тестирование, управление доступом, применение гипермедиа и реактивных API, поскольку все эти темы достаточно объёмны и требуют отдельных статей

Полезные ссылки