Проектирование Rest API
кодинг2016 / 12 / 10

Проектирование Rest API

Ave $USER!

Спроектировать Rest API для мобильного приложения, SPA( Single Page Application ) или просто как отдельного сервиса не так уж и просто. С учетом моделей данных и особенностей архитектуры стэка, на котором он будет запущен этот процесс становится еще сложнее. А если еще добавить аутентификацию пользователя, через авторизацию на стороннем сервисе, то процесс проектирования может очень сильно затянуться. Что бы не потеряться во всем этом, я составил небольшой список лучших практик проектирования Rest API с подробными объяснениями.

Содержание:

  1. Определения
  2. Проектирование схемы
  3. Конечные адреса
  4. Документация
  5. Шифрование

Определения

Сначала дадим некоторые определения и понятия, которые я буду использовать далее:

  • Ресурс (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.