
Ave $USER!
Спроектировать Rest API для мобильного приложения, SPA( Single Page Application ) или просто как отдельного сервиса не так уж и просто. С учетом моделей данных и особенностей архитектуры стэка, на котором он будет запущен этот процесс становится еще сложнее. А если еще добавить аутентификацию пользователя, через авторизацию на стороннем сервисе, то процесс проектирования может очень сильно затянуться. Что бы не потеряться во всем этом, я составил небольшой список лучших практик проектирования Rest API с подробными объяснениями.
Содержание:
Определения
Сначала дадим некоторые определения и понятия, которые я буду использовать далее:
- Ресурс (Resource) - Экземпляр одиночного объекта. Например: товар
- Коллекция (Collection) - Коллекция однотипных объектов. Например: товар
- Схема (Schema) - Описание модели объекта или коллекции объектов. Например: для товара схема описывает название, цену, тип, количество на складе, список свойств товара и т.д.
- Конечный адрес (Endpoint) - URL который представляет ресурс или коллекцию
Проектирование схемы
На самом деле, проектирование Rest API начинается гораздо раньше чем вы можете подумать.
Проектирование начинается со схемы ресурсов и коллекций и их связей. Например вы проектируете социальную сеть Switter.
Базовые сущности социальной сети - это профили пользователей Profile
.
Между профилями существуют связи в виде дружбы типа многие ко многим и разрешается она через вспомогательную схему Friends
.
Внутри социальной сети можно обмениваться сообщениями Message
.
Сообщение имеет отправителя и один или несколько адресатов.
Все участники беседы Profiles
объединяются в MessageGroups
через ProfilesMessageGroups
,
и каждое сообщение Messages
имеет свою MessageGroups
. Визуально это выглядит так:
Скачать проект MySQL Workbench
Из схемы видно, что два профиля дружат, если в Friend
существует запись в обе стороны с подтверждением.
Аналогично легко получить список переписок и всех участников каждой переписки и их сообщения.
Таким образом, перед проектирование Rest API необходимо определить схемы всех сущностей вашего приложения.
Конечные адреса
Есть несколько общепринятых правил составления конечных адресов.
1. Никаких глаголов в конечных адресах
Все возможные действия в Rest API выполняются с помощью HTTP методов:
- GET - получить
- POST - создать
- PUT - заменить
- PATCH - частично изменить
- DELETE - удалить
Отсюда видно, что никаких изменений данных при GET запросе не должно происходить.
2. Только существительные во множественном числе
Не стоит смешивать единственное и множественное число ресурсов, это нарушает семантическое значение и может ввести в заблуждение.
Используйте всегда множественное. Например у вас есть ресурс users
,
зайдя на него по конечному адресу /api/v1/users
вы получите список пользователей, естественно ограниченный фильтрами по умолчанию,
как если бы вы зашли по адресу /api/v1/users?offset=0&limit=50
.
Но перейдя на адрес /api/v1/users/1
мы как бы говорим: “получить объект с id=1 из ресурса пользователи”
3. Используйте подзапросы в ресурсах с отношениями
Если мы хотим получить друзей пользователя под номером 1, то адрес будет следующим /api/v1/users/1/friends
.
Этот подход позволяет избежать дополнительных запросов на сервер.
4. Используйте HTTP заголовки для сообщения об ожидемом формате ответа
И клиент и сервер должны знать в каком формате общаться и этот формат должен быть предоставлен в HTTP заголовке.
Content-Type - формат запроса
Accept - список поддерживаемых форматов для ответа
Постарайтесь избежать использование формата в URL
5. Используйте HATEOAS
HATEOAS - это Hypermedia as the Engine of Application State,
что по сути это метаинформация о структуре вашего сервиса, которая может быть использована для автоматического исследования (service autodiscover).
Что дает возможность некоторым приложениям без человеческого вмешательства исследовать ваш сервис.
Например имеем ответ от сервера с адреса /api/v1/users/1
:
{
"firstName": "Ivan",
"lastName": "Ivanov",
"autodiscover": {
"friends": "`/api/v1/users/1/friends",
"photos": "`/api/v1/users/1/photos",
"messages": "`/api/v1/users/1/messages",
}
}
в секции autodiscover
мы видим список ресурсов, которые относятся к текущему.
6. Предоставьте возможности фильтрации, сортировки, выбора полей, ограничения и смещения записей для колекций
Для таких целей, лучше поддерживать какой-нибудь язык запросов, позволяющий расширить стандартный, например graphql. Я же приведу примеры на псевдоязыке:
- Фильтрация - Поддерживайте фильтрацию с выражениями
/api/v1/users?filter=(firstName='Ivan',lastName=/^Ivan/,age>=18)
- Сортировка - Поддерживайте сортировку по возрастанию и убыванию для нескольких полей
/api/v1/users?sort=(firstName:1,DOB:0)
- Выбор полей - Поддерживайте выбор полей, это не только уменьшит трафик, но и позволит быстрее работать клиенту, обрабатывая меньшее количество данных
/api/v1/users?fields=[firstName, lastName, DOB]
- Ограничение и смещение - Поддерживайте параметры limit и offset, для контроля количества отдаваемых сущностей,
так же это более гибкий подход, чем постраничный
/api/v1/users?offset=20&limit=10
7. Поддерживайте версионность API
Время от времени, стандарты и интерфейсы вашего API могут меняться и что бы избежать путаницы используйте версионность.
Избегайте API без версий или API с дробными значениями /api/v100500/users
8. Обрабатывайте ошибки по HTTP кодам
Протокол HTTP поддерживает огромное количество кодов состояний Не обязательно использовать их все, достаточно этих 13:
- 200 – OK – ответ отправлен успешно, обычно ответ на: GET
- 201 – Created – создание успешно завершено, обычно ответ на: POST, PUT
- 204 – Accepted – удаление или редактирование успешно завершено, обыно ответ на: PATCH, DELETE
- 205 – Reset Content – после выполнения операции необходимо обновить данные
- 304 – Not Modified – ничего не изменилось, можно взять данные из кэша приложения
- 400 – Bad Request – неправильный запрос, подробности обыно предоставляют в теле ответа
- 401 – Unauthorized – пользователь не авторизован
- 402 – Payment Required – пользователю необходимо произвести оплату для продолжения
- 403 – Forbidden – пользователь авторизован, но не имеет привиллегий для выполнения операции
- 404 – Not found – ресурс не найден
- 415 – Unsupported Media Type – например, если пользователь загружает текстовый файл вместо ожидаемой картинки
- 500 – Internal Server Error – этот код лучше не использовать, но записывать в логи все данные об ошибке
- 501 – Not implemented – используется для сообщения, что данная операция не реализована на сервере, например если ваше приложение в стадии активной разработки
При ответе об ошибки помещайте дополнительную информацию в тело запроса (payload - полезная нагрузка)
9. Поддерживайте переопределение HTTP методов
Достаточно редкий случай, но некоторые прокси серверы поддерживают только GET и POST запросы.
В таком случае можно реализовать служебный заголовок X-HTTP-Method-Override
, для замены POST метода на метод указанный в нем.
4. Документация
Какой бы user-friendly не был ваш API он обязательно должен быть документирован, от схем до значений HTTP кодов. Существуют множество способов документирования API, наиболее популярный и на мой взляд понятный - это swagger, ко всему у них можно найти спецификацию Rest API (стандарт, которому они следуют).
5. Шифрование
Старайтесь использовать SSL шифрование. Сертификаты легко и бесплатно можно получить на StartSLL или LetsEncrypt.