Адаптация статьи REST WORST PRACTICES, © Jacob Kaplan-Moss. Статья написана применительно к Django, но информация будет актуальна для широкого круга специалистов.
Думаю что лучший способ понять как нужно делать, изучить как делать НЕ нужно. Представляю вашему вниманию вредные советы проектировщикам REST API.
Несколько недель назад я отправил этот текст своему коллеге, который просил моего совета при проектировании REST API в Django. С тех пор я цитировал сам себя несколько раз, в связи с чем решил опубликовать эти советы, возможно, кому-то еще они покажутся ценными.
В мире REST, ресурс — это центральное понятие. Очень заманчиво просто взять модель (строка в таблице), сказать что это и есть ресурс — одна модель, один ресурс. Это решение проваливается как только возникает задача — представить некий вид составного ресурса и уж точно проваливается в сильно денормализованных моделях.
Представьте модель Супергерой: вот единственный запрос GET /heros/superman/ который должен вернуть все его характеристики: список друзей, список связанных с ним артефактов и т.д. Смотрите, данные, ассоциированные с ресурсом, на самом деле могут приходить из нескольких моделей. Тут же полезно вспомнить паттерн фасад — ресурс это фасад для моделей, а не конкретная модель.
Способ контроля доступа должен быть подключаемым. Модуль контроля доступа должен определять какие операции над какими объектами допустимы — это единственный способ заставить работать что-то сложное.
Так вы сможете реализовать любой сценарий и сохранить контроль над системой разграничения доступа. Например, в одной системе вам может потребоваться реализовать анонимное чтение, чтение/запись при доступе с токеном разработчика и полный доступ при HTTP-аутентификации с данными администратора через админку.
Бывает очень заманчиво, по различным причинам, использовать различный формат результатов в зависимости от типа ресурса. Но это очень плохая идея, поскольку такой подход делает клиентский код очень сложным.
Вспомните Yahoo APIs — формат ResultSet/Result везде одинаков. Так же и Atom (GData).
Основная мысль — клиентский код не должен знать как парсить множество различных форматов данных.
JSON это, конечно, круто и его точно нужно поддерживать с самого начала. Но с развитием системы должна быть возможность выбора формата выходных данных, например, AtomPub.
Причем, формат вывода должен определяться нативно, через заголовок HTTP Accept, а не через лишние конструкции вида ?format=xml.
Большинство виденных мною REST API отображают основные стандартные методы HTTP (POST/GET/PUT/DELETE) в соответствующие CRUD-методы (create/retrieve/update/delete). Это не очень хорошая идея, потому что одни ресурсы могут использовать паттерн POST-как-создатель-связанных-ресурсов, а другие могут использовать POST-как-редактирование для обратной совместимости с HTML-формами. Любая форма должна быть допустима.
Более того, такие системы не допускают использование расширенных HTTP-методов. WebDAV определяет несколько полезных методов, так же новый метод HTTP PATCH уже официально часть стандарта. Ни кто не говорит о том что вы должны ограничивать себя четырьмя основными методами HTTP, речь идет лишь о том чтобы эти методы широко поддерживались, т.к. нестандартные HTTP-запросы могут просто игнорироваться веб-сервером.
Итак, вы хотите чтобы один ресурс ссылался на другой. Например, PhotoAlbum ссылается на объекты Photo. Обычно вы делаете это как-то так:
Просто указываете ID объектов, да? Печально, но это означает что клиентский код должен «просто знать» о том как конструировать URL на ресурсы Photo. Это так же означает что клиентский и серверный код становятся зависимыми. Как бонус, вы получаете все проблемы, связанные с несовместимостью API и клиентского кода. Почти все API на нашей планете допускают эту ошибку, поэтому малейшее изменение в формате URL автоматически сломает всех клиентов вашего API.
Просто используйте готовые URL:
Или, если волосы встают дыбом от дублирования, сообщите формат URL:
Любой большой API должен иметь выделенный сервер (серверы) для своей работы: характеристики производительности и факторы, от которых они зависят, так отличаются от обычных веб-приложений, что для больших API требуются отдельные, специальным образом настроенные, серверы. Это факт. Более того, если API-сервер падает, это не должно влиять на общедоступный веб-сайт.
Всем успехов в проектировании API!
Думаю что лучший способ понять как нужно делать, изучить как делать НЕ нужно. Представляю вашему вниманию вредные советы проектировщикам REST API.
Несколько недель назад я отправил этот текст своему коллеге, который просил моего совета при проектировании REST API в Django. С тех пор я цитировал сам себя несколько раз, в связи с чем решил опубликовать эти советы, возможно, кому-то еще они покажутся ценными.
Объединяй модели и ресурсы
В мире REST, ресурс — это центральное понятие. Очень заманчиво просто взять модель (строка в таблице), сказать что это и есть ресурс — одна модель, один ресурс. Это решение проваливается как только возникает задача — представить некий вид составного ресурса и уж точно проваливается в сильно денормализованных моделях.
Представьте модель Супергерой: вот единственный запрос GET /heros/superman/ который должен вернуть все его характеристики: список друзей, список связанных с ним артефактов и т.д. Смотрите, данные, ассоциированные с ресурсом, на самом деле могут приходить из нескольких моделей. Тут же полезно вспомнить паттерн фасад — ресурс это фасад для моделей, а не конкретная модель.
Жестко зашивай конкретную подсистему контроля доступа в код API
Способ контроля доступа должен быть подключаемым. Модуль контроля доступа должен определять какие операции над какими объектами допустимы — это единственный способ заставить работать что-то сложное.
Так вы сможете реализовать любой сценарий и сохранить контроль над системой разграничения доступа. Например, в одной системе вам может потребоваться реализовать анонимное чтение, чтение/запись при доступе с токеном разработчика и полный доступ при HTTP-аутентификации с данными администратора через админку.
Делай формат возвращаемых данных зависимым от типа ресурса
Бывает очень заманчиво, по различным причинам, использовать различный формат результатов в зависимости от типа ресурса. Но это очень плохая идея, поскольку такой подход делает клиентский код очень сложным.
Вспомните Yahoo APIs — формат ResultSet/Result везде одинаков. Так же и Atom (GData).
Основная мысль — клиентский код не должен знать как парсить множество различных форматов данных.
Пусть твой API поддерживает только один формат возвращаемых данных
JSON это, конечно, круто и его точно нужно поддерживать с самого начала. Но с развитием системы должна быть возможность выбора формата выходных данных, например, AtomPub.
Причем, формат вывода должен определяться нативно, через заголовок HTTP Accept, а не через лишние конструкции вида ?format=xml.
Перегружай семантикой стандартные методы HTTP-протокола
Большинство виденных мною REST API отображают основные стандартные методы HTTP (POST/GET/PUT/DELETE) в соответствующие CRUD-методы (create/retrieve/update/delete). Это не очень хорошая идея, потому что одни ресурсы могут использовать паттерн POST-как-создатель-связанных-ресурсов, а другие могут использовать POST-как-редактирование для обратной совместимости с HTML-формами. Любая форма должна быть допустима.
Более того, такие системы не допускают использование расширенных HTTP-методов. WebDAV определяет несколько полезных методов, так же новый метод HTTP PATCH уже официально часть стандарта. Ни кто не говорит о том что вы должны ограничивать себя четырьмя основными методами HTTP, речь идет лишь о том чтобы эти методы широко поддерживались, т.к. нестандартные HTTP-запросы могут просто игнорироваться веб-сервером.
Используй индексы для определения связей между ресурсами
Итак, вы хотите чтобы один ресурс ссылался на другой. Например, PhotoAlbum ссылается на объекты Photo. Обычно вы делаете это как-то так:
{
'album': 'whatever',
'photos': [1, 2, 3, 4]
}
Просто указываете ID объектов, да? Печально, но это означает что клиентский код должен «просто знать» о том как конструировать URL на ресурсы Photo. Это так же означает что клиентский и серверный код становятся зависимыми. Как бонус, вы получаете все проблемы, связанные с несовместимостью API и клиентского кода. Почти все API на нашей планете допускают эту ошибку, поэтому малейшее изменение в формате URL автоматически сломает всех клиентов вашего API.
Просто используйте готовые URL:
{
'photos': ['http//example.com/ph/1', ...]
}
Или, если волосы встают дыбом от дублирования, сообщите формат URL:
{
'photos': [1, 2, 3],
'photo_uri_template': 'http://example.com/ph/{id}'
}
Жестко привяжи REST API к своему приложению
Любой большой API должен иметь выделенный сервер (серверы) для своей работы: характеристики производительности и факторы, от которых они зависят, так отличаются от обычных веб-приложений, что для больших API требуются отдельные, специальным образом настроенные, серверы. Это факт. Более того, если API-сервер падает, это не должно влиять на общедоступный веб-сайт.
Всем успехов в проектировании API!