Rest сервис java что это
REST API на Java без фреймворков
Перевод статьи подготовлен специально для студентов курса «Разработчик Java».
В экосистеме Java есть много фреймворков и библиотек. Хотя и не так много, как в JavaScript, но они и не устаревают так быстро. Тем не менее, это заставило меня задуматься о том, что мы уже забыли, как писать приложения без фреймворков.
Вы можете сказать, что Spring — это стандарт и зачем изобретать велосипед? А Spark — это хороший удобный REST-фреймворк. Или Light-rest-4jis. И я скажу, что вы, конечно, правы.
Но вместе с фреймворком, помимо готовой функциональности, вы получаете много магии, сложности с изучением, дополнительные функции, которые вы, скорее всего, не будете использовать, а также баги. И чем больше стороннего кода в вашем сервисе, тем больше вероятность того, что у вас будут ошибки.
Сообщество open source очень активное, и есть большая вероятность, что ошибки в фреймворке будут быстро исправлены. Но все же, я хотел бы призвать вас подумать, действительно ли вам нужен фреймворк. Если у вас небольшой сервис или консольное приложение, возможно, вы сможете обойтись без него.
Что вы можете получить (или потерять), используя чистый Java-код? Подумайте об этом:
Когда я начал писать этот код, то часто сталкивался с ситуациями, когда отсутствовал функционал, который есть в Spring из коробки. В эти моменты, вместо того, чтобы взять Spring, надо было переосмыслить и разработать все самостоятельно.
Я понял, что для решения реальных бизнес-задач я, все же, предпочел бы использовать Spring, а не изобретать велосипед. Тем не менее, я считаю, что это упражнение было довольно интересным опытом.
Начинаем
Я буду описывать каждый шаг, но не всегда буду приводить полный исходный код. Полный код вы можете посмотреть в отдельных ветках git-репозитория.
и Jackson для JSON-сериализации
Для упрощения POJO-классов будем использовать Lombok:
и vavr для средств функционального программирования
Исходный код в ветке step-1.
Первый эндпоинт
Веб-сервер запускается на порту 8000 и предоставляет эндпоинт, который просто возвращает Hello. Это можно проверить, например, используя curl:
Исходный код в ветке step-2.
Поддержка разных HTTP-методов
Наш первый эндпоинт работает отлично, но вы можете заметить, что независимо от того, какой HTTP-метод использовать, он всегда отвечает одинаково.
Первое, что нужно сделать, это добавить код для различения методов, например:
Попробуйте еще раз такой запрос:
ответ будет примерно таким:
Исходный код в ветке step-3.
Парсинг параметров запроса
Парсинг параметров запроса — это еще одна «функция», которую нам нужно реализовать самостоятельно.
Мы могли бы распарсить параметры следующим образом:
и использовать, как показано ниже:
Полный пример в ветке step-4.
Аналогично, если мы хотим использовать параметры в path. Например:
Чтобы получить элемент по нам нужно распарсить url самостоятельно. Это становится громоздким.
Безопасность
Часто нам нужно защитить доступ к некоторым эндпоинтам. Например, это можно сделать, используя базовую аутентификацию (basic authentication).
Для каждого HttpContext мы можем установить аутентификатор, как показано ниже:
Значение “myrealm” в конструкторе BasicAuthenticator — это имя realm. Realm — это виртуальное имя, которое может быть использовано для разделения областей аутентификации.
Подробнее об этом можно прочитать в RFC 1945.
Теперь вы можете вызвать этот защищенный эндпоинт, добавив заголовок Authorization :
Для аутентификации в реальном приложении вы, вероятно, получите учетные данные из заголовка и сравните их с именем пользователя и паролем, хранящимися в базе данных.
Если вы не укажете заголовок, то API ответит статусом
Полный пример в ветке step-5.
JSON, обработка исключений и прочее
Теперь пришло время для более сложного примера.
Из моего опыта в разработке программного обеспечения наиболее распространенным API, который я разрабатывал, был обмен JSON.
Мы собираемся разработать API для регистрации новых пользователей. Для их хранения будем использовать базу данных в памяти.
У нас будет простой доменный объект User :
Я использую Lombok, чтобы избавится от бойлерплейта (конструкторы, геттеры).
В REST API я хочу передать только логин и пароль, поэтому я создал отдельный объект:
Объекты User создаются в сервисе, который будем использовать в обработчике API. Сервисный метод просто сохраняет пользователя.
В реальном приложении можно сделать больше. Например, отправлять события после успешной регистрации пользователя.
Реализация репозитория выглядит следующим образом:
Наконец, склеим все вместе в handle() :
Здесь JSON-запрос преобразуется в объект RegistrationRequest :
Также мне нужно преобразовать объект RegistrationResponse обратно в JSON-строку. Для этого используем Jackson
( com.fasterxml.jackson.databind.ObjectMapper ).
Вот как я создаю новый обработчик ( handler ) в main() :
Рабочий пример можно найти в ветке step-6. Там я также добавил глобальный обработчик исключений для отправки стандартных JSON-сообщений об ошибках. Например, если HTTP-метод не поддерживается или запрос к API сформирован неправильно.
Вы можете запустить приложение и попробовать один из следующих запросов:
REST-сервис на Java — это просто
Многим программистам Java-технологии могут показаться монструозными и сложными для понимания. В этой небольшой статье я бы хотел показать, что при желании можно собрать приложение из довольно простых компонентов, не прибегая к мега-фреймворкам.
В качестве примера я выбрал простенький REST-сервис. Для описания ресурсов будет использоваться Jersey. Как бонус, будет показано использование Dependency Injection фреймворка Google Guice. Можно было бы и без него, но я не хочу, что бы пример показался слишком игрушечным и оторванным от жизни. Весь пример я постараюсь уложить в примерно 50 строк в одном файле и не будет использовано ни строчки XML.
1. Опишем простенький класс, который будет предоставлять доступ к некоторой информации. Пусть это будет счетчик вызовов:
@Singleton
public static class Counter <
private final AtomicInteger counter = new AtomicInteger(0);
public int getNext() <
return counter.incrementAndGet();
>
>
Аннотация Singleton нужна для указания джуйсу, что объект должен быть синглтоном 🙂
2. Опишем сервис, который будет возращать нам что-то, попутно дергая counter:
@Path( «/hello» )
public static class Resource <
@Inject Counter counter;
@GET
public String get () <
return «Hello, User number » + counter.getNext();
>
>
3. Теперь подружим Jersey и Guice. Я воспользовался готовой интеграцией, она называется jersey-guice. Интеграция осуществляется через сервлет/фильтр GuiceContainer, для использования которого нужно объявить ServletModule из расширения guice-servlet-module и указать, что нужные нам запросы будут обрабатываться GuiceContainer, что позволит объявлять Jersey ресурсы в контексте Guice.
public static class Config extends GuiceServletContextListener <
@Override
protected Injector getInjector() <
return Guice.createInjector( new ServletModule() <
@Override
protected void configureServlets() <
bind(Resource. class );
bind(Counter. class );
serve( «*» ).with(GuiceContainer. class );
>
>);
>
>
Там же мы забайндили Counter и Resource.
4. Осталось запустить все это добро, используя сервлет-контейнер. Для этого нам совершенно не обязательно собирать war-ку и деплоить в какой-нибудь Tomcat. Можно воспользоваться встраиваемым контейнером. На ум приходят Jetty и Grizzly. Я выбрал последний. Вот код, который запускает сервер:
Обратите внимание, что пришлось объявить пустой сервлет:
@SuppressWarnings( «serial» )
public static class DummySevlet extends HttpServlet
Он нужен, что бы Guice-фильтр сработал. Если не будет ни одного сервлета, Grizzly не будет передавать запрос никаким фильтрам.
Вот пожалуй и все. Далее приведу весь код:
@Path( «/hello» )
public static class Resource <
@Inject Counter counter;
@GET
public String get () <
return «Hello, User number » + counter.getNext();
>
>
@Singleton
public static class Counter <
private final AtomicInteger counter = new AtomicInteger(0);
public int getNext() <
return counter.incrementAndGet();
>
>
public static class Config extends GuiceServletContextListener <
@Override
protected Injector getInjector() <
return Guice.createInjector( new ServletModule() <
@Override
protected void configureServlets() <
bind(Resource. class );
bind(Counter. class );
serve( «*» ).with(GuiceContainer. class );
>
>);
>
>
@SuppressWarnings( «serial» )
public static class DummySevlet extends HttpServlet
REST API с использованием Spring Security и JWT
Рано или поздно каждый Java-разработчик столкнется с необходимостью реализовать защищенное REST API приложение. В этой статье хочу поделиться своей реализацией этой задачи.
1. Что такое REST?
REST (от англ. Representational State Transfer — «передача состояния представления») – это общие принципы организации взаимодействия приложения/сайта с сервером посредством протокола HTTP.
Диаграмма ниже показывает общую модель.
Всё взаимодействие с сервером сводится к 4 операциям (4 — это необходимый и достаточный минимум, в конкретной реализации типов операций может быть больше):
Получение данных с сервера (обычно в формате JSON, или XML);
Добавление новых данных на сервер;
Модификация существующих данных на сервере;
Удаление данных на сервере
Более подробно можно прочесть в остальных источниках, статей о REST много.
2. Задача
Необходимо подготовить защищенное REST приложение, доступ к которому может быть осуществлен только для авторизованного пользователя. Авторизация с передачей логина и пароля выполняется отдельным запросом, при успешной авторизации система должна сгенерировать и вернуть токен. Валидация остальных запросов должна быть осуществлена по токену.
Схема нашего приложения будет выглядеть следующим образом:
3. Технологии
Для решения используем фреймворк Spring Boot и Spring Web, для него требуется:
Авторизация и валидация будет выполнена силами Spring Security и JsonWebToken (JWT).
Для уменьшения кода использую Lombok.
4. Создание приложения
Переходим к практике. Создаем Spring Boot приложение и реализуем простое REST API для получения данных пользователя и списка пользователей.
4.1 Создание Web-проекта
Создаем Maven-проект SpringBootSecurityRest. При инициализации, если вы это делаете через Intellij IDEA, добавьте Spring Boot DevTools, Lombok и Spring Web, иначе добавьте зависимости отдельно в pom-файле.
4.2 Конфигурация pom-xml
После развертывания проекта pom-файл должен выглядеть следующим образом:
Должен быть указан parent-сегмент с подключенным spring-boot-starter-parent;
И установлены зависимости spring-boot-starter-web, spring-boot-devtools и Lombok.
4.3 Создание ресурса REST
Разделим все классы на слои, создадим в папке com.springbootsecurityrest четыре новые папки:
model – для хранения POJO-классов;
repository – в полноценных проектах используется для взаимодействия с БД, но т.к. у нас ее нет, то он будет содержать список пользователей;
service – слой сервиса, прослойка между контролером и слоем ресурсов, используется для получения данных из ресурса, их проверки и преобразования (если это необходимо);
rest – будет содержать в себе классы контроллеры.
В папке model создаем POJO класс User.
В папке repository создаём класс UserRepository c двумя методами:
getByLogin – который будет возвращать пользователя по логину;
getAll – который будет возвращать список всех доступных пользователей. Чтобы Spring создал бин на основании этого класса, устанавливаем ему аннотацию @Repository.
В папке service создаем класс UserService. Устанавливаем классу аннотацию @Service и добавляем инъекцию бина UserRepository. В класс добавляем метод getAll, который будет возвращать всех пользователей и getByLogin для получения одного пользователя по логину.
Создаем контроллер UserController в папке rest, добавляем ему инъекцию UserService и создаем один метод getAll. С помощью аннотации @GetMapping указываем адрес контроллера, по которому он будет доступен клиенту и тип возвращаемых данных.
Запускаем приложение и проверяем, что оно работает, для этого достаточно в браузере указать адрес http://localhost:8080/users, если вы все сделали верно, то увидите следующее:
5. Spring Security
Простенькое REST API написано и пока оно открыто для всех. Двигаемся дальше, теперь его необходимо защитить, а доступ открыть только авторизованным пользователям. Для этого воспользуемся Spring Security и JWT.
Spring Security это Java/JavaEE framework, предоставляющий механизмы построения систем аутентификации и авторизации, а также другие возможности обеспечения безопасности для корпоративных приложений, созданных с помощью Spring Framework.
JSON Web Token (JWT) — это открытый стандарт (RFC 7519) для создания токенов доступа, основанный на формате JSON. Как правило, используется для передачи данных для аутентификации в клиент-серверных приложениях. Токены создаются сервером, подписываются секретным ключом и передаются клиенту, который в дальнейшем использует данный токен для подтверждения своей личности.
5.1 Подключаем зависимости
Добавляем новые зависимости в pom-файл.
5.2 Генерация и хранения токена
Начнем с генерации и хранения токена, для этого создадим папку security и в ней создаем класс JwtTokenRepository с имплементацией интерфейса CsrfTokenRepository (из пакета org.springframework.security.web.csrf).
Интерфейс указывает на необходимость реализовать три метода:
Генерация токена в методе generateToken;
Сохранения токена – saveToken;
Получение токена – loadToken.
Генерируем токен силами Jwt, пример реализации метода.
Параметр secret является ключом, необходимым для расшифровки токена, оно может быть постоянным для всех токенов, но лучше сделать его уникальным только для пользователя, например для этого можно использовать ip-пользователя или его логин. Дата exp является датой окончания токена, рассчитывается как текущая дата плюс 30 минут. Такой параметр как продолжительность жизни токена рекомендую вынести в application.properties.
Токен будет генерироваться новый на каждом запросе с жизненным циклом в 30 минут. После каждого запроса на фронте необходимо перезаписывать токен и следующий запрос выполнять с новым. Он станет невалидным только в том случае, если между запросами пройдет более 30 минут.
Сохранение токена выполняем в response (ответ от сервера) в раздел headers и открываем параметр для чтения фронта указав имя параметра в Access-Control-Expose-Headers.
Добавляем к классу еще один метод по очистке токена из response, будем использовать его при ошибке авторизации.
5.3 Создание нового фильтра для SpringSecurity
Создаем новый класс JwtCsrfFilter, который является реализацией абстрактного класса OncePerRequestFilter (пакет org.springframework.web.filter). Класс будет выполнять валидацию токена и инициировать создание нового. Если обрабатываемый запрос относится к авторизации (путь /auth/login), то логика не выполняется и запрос отправляется далее для выполнения базовой авторизации.
5.4 Реализация сервиса поиска пользователя
Теперь необходимо подготовить сервис для поиска пользователя по логину, которого будем авторизовывать. Для этого нам необходимо добавить к сервису UserService интерфейс UserDetailsService из пакета org.springframework.security.core.userdetails. Интерфейс требует реализовать один метод, выносить его в отдельный класс нет необходимости.
Полученного пользователя необходимо преобразовать в класс с реализацией интерфейса UserDetails или воспользоваться уже готовой реализацией из пакета org.springframework.security.core.userdetails. Последним параметром конструктора необходимо добавить список элементов GrantedAuthority, это роли пользователя, у нас их нет, оставим его пустым. Если пользователя по логину не нашли, то бросаем исключение UsernameNotFoundException.
5.5 Обработка авторизации
По результату успешно выполненной авторизации возвращаю данные авторизованного пользователя. Для этого создадим еще один контроллер AuthController с методом getAuthUser. Контроллер будет обрабатывать запрос /auth/login, а именно обращаться к контексту Security для получения логина авторизованного пользователя, по нему получать данные пользователя из сервиса UserService и возвращать их на фронт.
5.6 Обработка ошибок
Что бы видеть ошибки авторизации или валидации токена, необходимо подготовить обработчик ошибок. Для этого создаем новый класс GlobalExceptionHandler в корне com.springbootsecurityrest, который является расширением класса ResponseEntityExceptionHandler с реализацией метода handleAuthenticationException.
Метод будет устанавливать статус ответа 401 (UNAUTHORIZED) и возвращать сообщение в формате ErrorInfo.
5.7 Настройка конфигурационного файла Spring Security.
Все данные подготовили и теперь необходимо настроить конфигурационный файл. В папке com.springbootsecurityrest создаем файл SpringSecurityConfig, который является реализацией абстрактного класса WebSecurityConfigurerAdapter пакета org.springframework.security.config.annotation.web.configuration. Помечаем класс двумя аннотациями: Configuration и EnableWebSecurity.
Реализуем метод configure(AuthenticationManagerBuilder auth), в класс AuthenticationManagerBuilder устанавливаем сервис UserService, для того что бы Spring Security при выполнении базовой авторизации мог получить из репозитория данные пользователя по логину.
Реализуем метод configure(HttpSecurity http):
Разберем метод детальнее:
.authorizeRequests().antMatchers(«/auth/login»).authenticated() для запроса /auth/login выполняем авторизацию силами security. Что бы не было двойной валидации (по токену и базовой), запрос был добавлен в исключение к классу JwtCsrfFilter;
6. Проверка функционала
Для проверки использую Postman. Запускаем бэкенд и выполняем запрос http://localhost:8080/users с типом GET.
Токена нет, валидация не пройдена, получаем сообщение с 401 статусом.
Пытаемся авторизоваться с неверными данными, выполняем запрос http://localhost:8080/auth/login с типом POST, валидация не выполнена, токен не получен, вернулась ошибка с 401 статусом.
Авторизуемся с корректными данными, авторизация выполнена, получен авторизованный пользователь и токен.
Повторяем запрос http://localhost:8080/users с типом GET, но с полученным токеном на предыдущем шаге. Получаем список пользователей и обновленный токен.
Заключение
В этой статье рассмотрели один из примеров реализации REST приложения с Spring Security и JWT. Надеюсь данный вариант реализации кому то окажется полезным.
Рекомендации по REST API — примеры проектирования веб-сервисов на Java и Spring
Это последняя статья из серии статей про REST API:
При разработке хорошего REST API важно иметь хорошие микросервисы.
Как вы разрабатываете свой REST API? Каковы лучшие практики?
Вы изучите:
Используйте Consumer First подход
Кто будет пользоваться вашим сервисом? Потребитель услуг.
Вы смотрите на сервис с точки зрения потребителя?
Всякий раз, когда у вас проходит обсуждение или ревью сервиса, ставьте требования потребителя на первое место.
Используйте Contract First подход
Что такое контракт?
Создатель веб-сервиса считается поставщиком сервиса. Приложение, которое использует веб-сервис, является потребителем сервиса. Контракт — это соглашение между поставщиком и потребителем об услуге.
Чтобы правильно использовать услугу, потребитель сервиса должен полностью понимать договор. Контракт включает в себя детали многих аспектов обслуживания, таких как:
При contract first подходе вы сначала определяете контракт на обслуживание, а затем только внедряете сервис.
Contract First с WSDL
Например, когда вы определяете веб-службы SOAP, вы используете WSDL для определения договора.
WSDL определяет, каковы конечные точки службы, операции, которые вы публикуете, и структуры запроса и ответа.
Contract First с Swagger / Open API
Когда вы используете RESTful веб-сервисы, Swagger — это популярный инструмент для документирования ваших веб-сервисов.
Swagger позволяет вам определить, какие ресурсы вы предоставляете в рамках своего API. Он включает в себя детали каждой операции, например, использует ли он XML, JSON или оба. Схема ответов также присутствует там.
Он также дает подробную информацию о кодах ответов, которые он поддерживает. Вы также можете увидеть, что этот конкретный ресурс, /jpa/users:
поддерживает операцию GET. Фактически также поддерживает операцию POST:
Схема ответа, если этот ресурс доступен, будет:
Определение объекта User присутствует в договоре Swagger как:
Он включает в себя атрибуты: birthDate, id, name и массив сообщений posts. Некоторые поля также включают в себя поле описания внутри них.
При contract first подходе перед внедрением сервиса вы вручную или с помощью приложения создаете такое определение, какое создал Swagger.
Преимущества подхода Contract First
Используя contract first подход, вы думаете о своих потребителях и о том, как они могут использовать эту услугу. Вы изначально не беспокоитесь о деталях реализации.
Если на раннем этапе вы придаете большое значение внедрению, технические подробности проникают в определение вашего сервиса.
Определите организационные стандарты для REST API
Важным ориентиром для ваших организационных стандартов является YARAS.
YARAS означает Yet Another RESTful API Standard (еще один стандарт RESTful API). YARAS предоставляет стандарты, руководства и соглашения, которые необходимо соблюдать при разработке RESTful веб-сервисов. Он дает рекомендации для следующих вещей:
Единый подход к разработке сервисов
Вам нужно решить множество сложных проблем, прежде чем приступить к проектированию RESTful веб-сервисов. Все перечисленное выше, необходимо выяснить.
Руководство вашей организация не захочет, чтобы команды, которые занимаются различными ресурсами, разные подходы к этим вещам.
Например, нежелательно, чтобы Команда-A организовывала версионность на основе параметров запроса, а Команда-B использовало версионность на основе URI.
Поэтому важно, чтобы у вас были четко определенные организационные стандарты для подхода к RESTful веб-сервисАМ.
Кастомизация YARAS ПОД организационные нужды
Хорошая вещь в YARAS — это то, что он может быть настроен для удовлетворения потребностей, специфичных для организации. Например, вы можете:
Выбор стандартного общеорганизационного REST API фреймвока
Типичными фреймвоками, которые используются для создания веб-сервисов RESTful в мире Java, являются Spring MVC, Spring REST и JAX-RS.
Если вы создадите специфичную для организации фреймвок/архетип/эталонное приложение, придерживающееся общих стандартов организации, поверх предпочтительной REST API платформы, это позволит командам легко придерживаться общих стандартов.
Типичные особенности включают в себя:
Децентрализованное управление REST API
Создайте группу экспертов из команд, создающих REST API, и сформируйте команду управления. Команда несет ответственность за
Широкое использование HTTP
Всякий раз, когда вы думаете о веб-сервисах RESTful, думайте о HTTP.
HTTP имеет все функции, которые помогут вам создавать отличные веб-сервисы.
Используйте правильные методы HTTP-запросов
Подумайте о методах HTTP-запросов, которые вам нужно использовать. Когда вы думаете о реализации какой-либо операции, определите ресурс, на котором она должна быть выполнена, а затем найдите соответствующий метод HTTP-запроса. Вы извлекаете детали, создаете что-то, обновляете что-то существующее или удаляете существующее?
Использование HTTP методов:
Используйте соответствующий статус ответа HTTP
При выполнении операции убедитесь, что вы вернули правильный статус ответа.
Например, когда конкретный ресурс не найден, не выбрасывайте исключение сервера. Вместо этого отправьте соответствующий код ответа в ответном сообщении, например 404.
Если на самом деле существует исключение сервера, отправьте обратно код 500.
В случае ошибки проверки отправьте код для неверного запроса.
Фокус на представление
Каждый ресурс может иметь несколько представлений — в формате XML или JSON. Потребитель услуг может выбрать представление по своему выбору.
Сервис возвращает 3 элемента users, когда мы отправляем запрос GET. В этом случае мы получаем JSON-представление ресурса /users.
Когда потребитель не указывает предпочтительное представление, мы используем JSON.
Потребитель может отправить заголовок Accept, чтобы указать представление.
Тело ответа теперь имеет содержимое XML:
Используйте множественное число
Всегда используйте множественное число, когда вы именуете ресурсы.
Давайте посмотрим на простой пример. Предположим, у нас есть сервис, который размещает ресурс пользователя. Ниже описано, как получить к ним доступ:
Создать хорошую документацию
Потребители должны понимать, как наилучшим образом использовать сервис, и лучший способ помочь им — создавать хорошую документацию.
Веб-сервисы SOAP могут использовать функциональность WSDL, в то время как RESTful веб-сервисы имеют опции Swagger (теперь стандарт документации Open API).
Выбор формата — это только одна часть создания хорошей документации. Что также важно, так это предоставление нужного количества информации, чтобы помочь потребителю.
Документация должна быть полной и включать следующие пункты:
Создать общий портал документации REST API
Полезно было бы иметь общий портал документации REST API для всей организации. Взгляните на один такой портал:
Такой портал объединяет все ресурсы, имеющиеся в организации. Наличие пользовательского интерфейса, такого как Swagger UI, будет иметь свои дополнительные преимущества. Используя Swagger UI, вы можете посмотреть документацию более подробно:
Это может быть использовано даже нетехническими пользователями. Информация, предоставляемая здесь, включает в себя:
Поддержка версий
Управление версиями влечет за собой большую сложность для веб-службы. Поддерживать несколько разных версий одного и того же веб-сервиса очень сложно. Старайтесь избегать этого, когда это возможно.
Однако в определенных сценариях управление версиями неизбежно.
Давайте начнем с рассмотрения примера службы. Предположим, что мы изначально определили службу, имеющую класс PersonV1, следующим образом:
Эта версия класса не учитывает, что у имени может быть много подразделов, таких как имя и фамилия. Посмотрите это:
Вторая версия исходного класса была обновлена, чтобы исправить эту аномалию:
Мы не можем сразу перенести весь сервис для использования PersonV2! Там могут быть другие потребители, которые ожидают ответов в формате PersonV1.
Вот где требуется управление версиями.
Давайте теперь посмотрим на варианты, которые мы имеем для управления версиями этих двух ресурсов.
Использование разных URI
У нас есть один вариант — использовать разные URI для этих разных ресурсов. В приведенном ниже коде экзамена мы используем URI v1/person и v2/person для их различения:
Если вы выполняете запрос GET для ресурса v1/person:
Вы получите информацию, соответствующую v1. Для другого ресурса:
Использование параметра запроса
Второй метод управления версиями использует параметр запроса:
В URI /person/param, если мы отправляем параметр с версией=1, мы возвращаем ресурс типа PersonV1:
Для того же URI параметр с version=2 возвращает ресурс типа PersonV2:
Этот метод называется управлением версиями с помощью параметров запроса.
Использование Header (заголовка)
Другим способом вы можете сделать это управление версиями, указав заголовок. Здесь мы используем заголовок с именем X-API-VERSION и пометили URI как /person/header. Когда значение заголовка равно 1, возвращается ресурс типа PersonV1:
Когда его значение равно 2, ресурс типа PersonV2 извлекается:
Мы используем атрибут в заголовке запроса, чтобы выполнить управление версиями для нас.
Использование Accept Header
Последний метод для создания версий использует Accept Header. Если потребитель включает первую информацию о версиях в Accept Header запроса GET, возвращается следующий ресурс:
В противном случае возвращается ресурс типа PersonV2:
Этот способ управления версией называется Accept Header Versioning или Media Type Versioning, поскольку MIME-типы обычно являются содержимым заголовка Accept.
Сравнение методов управления версиями
До сих пор мы видели четыре типа методов версиями:
Вам необходимо оценить эти четыре варианта в соответствии с вашими конкретными потребностями. Есть ряд важных факторов, которые следует учитывать:
Подумайте об обработке ошибок
Когда потребитель отправляет запрос в службу, важно, чтобы он получил правильный ответ. Всякий раз, когда что-то идет не так, важно отправить соответствующий ответ.
Когда потребитель запрашивает несуществующий ресурс
Если мы отправим запрос GET для поиска существующего пользователя, мы получим следующий ответ:
Если вы ищете несуществующего пользователя:
То, что вы получите, это статус 404 Not Found. Это хорошая обработка ошибок, поскольку она правильно определяет, что ресурс не найден, и не возвращает ошибку сервера.
Давайте теперь отправим запрос GET на несуществующий URI:
Как видидите, вы получаете статусы ответа 404 Not Found. Неправильный URL указывает на несуществующий ресурс.
Важные статусы ответа
Сведения об ошибках в теле ответа
Это помогает, если у вас есть стандартная структура исключений при разработке вашего сервиса.
Для конкретных ошибок конкретные структуры ответа могут быть возвращены потребителю, и это может быть стандартом во всей организации. Убедитесь, что ответ об ошибке также читается потребителям, без путаницы.
Использование Swagger или Open API Documentation
Swagger обеспечивает один из самых популярных форматов документации для веб-сервисов RESTful. Сегодня он поддерживается различными организациями и используется в большом количестве услуг. Здесь мы рассмотрим два основных аспекта Swagger:
Swagger Документация
Посмотрите на следующий Swagger JSON:
Когда вы посмотрите на документацию по Swagger, вы сможете быстро определить имеющиеся ресурсы, поддерживаемые операции и операции, относящиеся к каждому ресурсу:
Ресурс /users поддерживает операции GET и POST. Для GET вы можете увидеть поддерживаемые типы запросов и ответов. Вы также видите различные типы ответов:
Обратите внимание, что для ответа 200 схема также упоминается как массив User. User является частью раздела definitions (определения) в нижней части документа:
Swagger полностью независим от технологии, которую вы используете для реализации RESTful веб-сервиса. О все работает на основе JSON.
Представляем Swagger UI
Swagger UI — отличный пользовательский интерфейс, который очень полезен для визуализации документации Swagger для веб-службы RESTful. Посмотрите на скриншот ниже:
Обратите внимание, что мы выбрали конкретную версию веб-сервиса для просмотра его документации. Вот тот же экран более подробно:
Когда мы заходим на домашнюю страницу, она описывает перечисленные ресурсы:
Также можно увидеть набор операций, поддерживаемых для URL ресурсов:
Когда вы нажимаете на операцию GET определенного ресурса, вы получаете его детали:
Вы можете увидеть, что схема модели описана визуально. Атрибуты birthDate, id, links, name и posts также отображаются. Вы также можете выполнить пример запроса и просмотреть ответ:
Использование инструментов Swagger (стандарт Open API)
Самое замечательное в Swagger — множество инструментов, доступных вокруг него. Взгляните на следующий сервис, который мы использовали ранее, чтобы объяснить управление версиями: Вот посмотрите на автоматически сгенерированную документацию для этого сервиса:
В Swagger есть поддержка как подхода contract-first, так и подхода code-first.
По этому вопросу имеется авторское видео.
Резюме
В этой статье мы рассмотрели лучшие практики создания и проектирования RESTful веб-сервисов.