Как правильно делать REST API

автор superman 21 ноября 2014 г. 23:46:57

Теги: Rest_apiRestApi



В последнее время сложно кого-то удивить мобильным приложением для сайта или очередным удобным сервисом, доступным практически на всех устройствах. За счет чего достигается такое удобство ? Конечно это все веб-сервисы. Причем в понятии именно веб-сервисы для машин, а не для человека.

Пожалуй наиболее человечный способ представления информации используется в REST API сервисах. Для их стабильной и быстро работы необходимо уделить немалое время для проектирования самого сервиса. Часто можно увидеть ситуацию, когда проект уже сделан, но сейчас срочно понадобился сервис. В этом случае обычно разработчики делают на "скорую" руку реализацию такого сервиса, обходясь GET и POST запросами, и не всегда строго соблюдая философию REST API.

В этой статье постараемся как можно подробнее рассказать об основных практиках построения REST сервиса.

Первое про что никогда не стоит забывать в начале разработки сервиса, это об его версии сервиса. Не думайте, что ваш сервис не будет развиваться и всю свою жизнь проработает исключительно в одной реализации. Советую сразу же предусмотреть в url строке указание версии API:

http://example.com/api/v1/

По параметру "v1" мы сразу можем понять с какой версией API мы хотим работать. Придерживаться стиля "cool uri don't change" нам советует и W3C.

Есть и другой способ передачи версии API в теле HTTP заголовка "Accept". Например такой способ применяет Github. Т.е. вы можете сказать Github с какой версией API хотите работать например вот-так:

curl https://api.github.com/users/superuser -I -H "Accept: application/vnd.github.v3.full+json"

Используйте имена существительные при именовании ресурсов сервиса, а не глаголы. Приведем пару плохих примеров:

/getPrices
/listUsers
/getAllGoods?orderBy=id

А теперь посмотрим как это должно выглядеть правильно:

  • GET /prices получить список цен
  • POST /prices добавить цену в коллекцию цен
  • GET /prices/1 получить цену с идентификатором 1
  • PATCH/PUT /prices/1 изменить цену с идентификатором 1

Давайте имена ресурсам сервиса во множественном числе. Зачем ? Сейчас поясню. Как вы думаете будет самое верное решение в наименовании ресурса сервиса, учитывая что мы получаем список объектов ? Мы получаем не один объект, а список. Конечно логичнее будет использование множественного числа в наименовании ресурса. Например вместо GET /price логичнее конечно же GET /prices. В конце концов, если вы не будете придерживаться одинакового для всех правила наименования ресурсов сервиса вы можете просто запутаться!

В итоге возьмите за правило использовать к примеру GET /prices вместо GET /price для действий получения,удаления и обновления объектов в сервисе.

Следите за тем, чтобы GET и HEAD методы вызова сервиса всегда должны быть безопасными. Как это понимать ? Иными словами эти методы не должны менять состояние сервиса. Разберем пример:

GET /deleteProduct?id=1

Что произойдет в этом случае ? Верно, удаление продукта с идентификатором 1. Закроем глаза на наименование ресурса сервиса и на технику передачи параметров. И давайте представим что же будет, если поисковая система (Google или Yandex к примеру) вздумает перейти по этой ссылке ? Верно, будет так же удален продукт. А если поисковая система додумается перейти по ссылке, подставляя в нее параметр от 1 до 100 ? Короче говоря, утром вы будете явно в шоке. Документ RFC2616 как раз просит именно о том же, делайте пожалуйста методы GET и HEAD безопасными.

Используйте вложенные ресурсы. Зачем ? К примеру если вы хотите получить название всех альбомов из определенного артиста, то логичнее uri ресурса будет выглядеть так:

GET /artists/12/albums

В этом примере 12 - идентификатор артиста.

Используйте "пейджинг" (paging)! Получить очень большие объемы данных за один запрос из сервиса не самая лучшая идея. В конце концов это обернется очень большой нагрузкой на сам сервис, т.к. ему придется сериализовать очень большой объем данных в JSON формат. Гораздо лучшей идеей будет если вы разобьёте результат на небольшие страницы. Сервису проще отдавать вам небольшие объемы данных, ну а вам проще с ними работать. Так делают много известных сайтов, к примеру Facebook, Twitter и GitHub. Одной из идей реализации пейджинга в сервисе будет использование ссылок "Следующая страница" и "Предыдущая страница" в параметре Link HTTP заголовка. Посмотрите как это делает GitHub.

Используете корректные коды состояния HTTP. Да, используйте всегда корректные коды при возврате данных, как при удачном завершении запроса так и при ошибочном.

Вот примеры кодов состояние при удачном завершении запроса:

  • 201 Created обычно используется, когда была создана новая запись (по аналогии с оператором INSERT в SQL)
  • 202 Accepted должен использоваться, когда запрос принят и поставлен в обработку в фоновом режиме (например при асинхронном выполнении)
  • 204 No content должен использоваться, когда запрос успешно обработался но нет никакого контента для ответа. Это подходит для ответа при выполнении удаления какого-то элемента сервисом.

Примеры кодов состояния HTTP при ошибочном завершении:

  • 400 Bad request обычно возвращается при ошибке в обработке запроса, например передан ошибочный JSON
  • 401 Unauthorized возвращается, когда запрос не прошел аутентификацию. Например передан неверный токен или имя пользователя и пароль)
  • 403 Forbidden возвращается, когда запрос успешно прошел аутентификацию, но дальнейшее выполнение запрещено.
  • 406 Not acceptable обычно возвращается, когда формат запроса не поддерживается. Например если вы передали XML, а ресурс принимает только JSON
  • 410 Gone возвращается, когда запрашиваемый ресурс был полностью удален и никогда не буде доступен вновь
  • 422 Unprocesable entity возвращается, когда произошла ошибка валидации при создании объекта.

Полный список возможных кодов состояния HTTP всегда можно посмотреть на RFC2616.

Всегда возвращайте подробное описание об ошибке. Когда произошла ошибка, верните описание этой ошибке вместе с ошибочным кодом завершения. Например вот так:

HTTP/1.1 401 Unauthorized
{
    "status": "Unauthorized",
    "message": "No access token provided.",
    "request_id": "594600f4-7eec-47ca-8012-02e7b89859ce"
}

Придерживайтесь этих простых правил. Приятной разработки!


comments powered by Disqus

2014-2018 © thenextcode.ru