Comments 18
Когда критериев много — используем метод POST и передаём параметры в теле
Какой-то странный REST у нас получается. Запрос - явный GET, но из-за длинных параметров (и невозможности передать параметры в теле) мы должны использовать POST.
Тогда проще использовать POST для всего. И получим RPC (JSON-RPC)
Спасибо за комментарий! Цель статьи обозначить вектор проектирования, конкретная реализация в разных компаниях и командах может отличаться, всё зависит от контекста.
Использование POST для всего подряд усложняет настройки кэширования, ссылки/закладки и другие оптимизации опирающиеся на rest стандарты
Опять притащили коды статусов HTTP как коды ответов на запросы к уровню приложения.
HTTP всегда должен отвечать 2хx, если запрос с точки зрения HTTP выполнен, 4хх - если ошибка со стороны клиента, 5хх - что-то не так на сервере.
А вот в теле ответа быть уже статус ответа на сам REST запрос, причем желательно не просто одним числом, а поподробнее.
А у вас программист, вызывающий сервис, потом голову будет ломать, что же он перепутал: эндпоинт или фильтр запроса и с многоэтажными матами пытаться парсить строки ответов, следующие за HTTP-статусами в заголовке. Не надо смешивать уровни OSI.
Ну да, точно)
А потом после таких "проектировщиков" в ELK приложение 100% отвечает 200 ОК, при том что там 500-х половина. Successfully Failed антипаттерн какой-то.
Ещё и модель OSI прилетаете. Http, на секундочку, уровень "прикладной", то есть как бы как раз того самого приложения.
Работайте кодами ответов HTTP и не слушайте вредных советов и команда SRE скажет вам спасибо за это.
Не успел вовремя ответить, пример с ELK очень в тему, сам с данной проблемой много возился, спасибо!
А можно подробнее что за ELK проблема? Я вот тоже склонен считать, что HTTP коды должны относиться к HTTP только. Если endpoint не найден - 404, если ресурс не найден - 200 + status: error
Имхо в статье много противоречий, исключений и заплаток.
Добрый день!
Имхо в статье много противоречий, исключений и заплаток.
Относительно какой теоретической базы противоречия?
В конце статьи указал основные материалы на которые опирался при написании. Там где были расхождения, приложил ссылки для углубления.
А можно подробнее что за ELK проблема?
Допустим у меня есть базовый HttpClient и у меня стоит задача: отказоустойчивость, логгирование ошибок, метрики. Я как разработчик хочу просто вызвать метод типа PostAsync(url, query, body ...) и получить в ответе модель, которую создал. Чтобы это было просто и удобно обычно создают или используют готовые библиотеки.
Как библиотека поймет что нужно залоггировать/сделать ретрай запрос? В согласованных системах достаточно будет проверки статус кода.
В вашем же случае нужно вычитывать всё тело запроса и искать там заветное слово: "ok": true|false? "success": true|false ? Вот например API slack, где я с этим сталкивался:
https://docs.slack.dev/apis/web-api/#responses
(elk не прикладываю, так как по итогу использовал библиотеку, а не api на прямую)
Когда в теле все же уместно слать 200 и status/status_code?
Когда есть ясная архитектурная особенность, например работа с большим количеством данных => batch методы.
Я в статье рекомендую:
Атомарность: либо всё, либо ничего.
Но если бизнес-сценарий особенный и например в массовых методах нужно выполнить хоть что-то (например принять хотя бы часть валидных данных на индексацию), то уместно отступать от этой рекомендации, но сложность задачи при этом возрастает в разы.
Противоречия не "с теоретической базой", а логические:
GET /resources/:id - не найден = 404, GET /resources/ - не найдено ни одного = 200
PUT /v1/resources/{id} - 404, если не позволено создавать ресурс "со своим идентификатором"? внезапно.
Правила
Глагол после
{id}
:POST /v1/resources/{id}/apply-discount
.Метод — чаще POST (есть побочный эффект).
Исключения: вычислительные методы и счётчики, которые не меняют состояние → GET.
Расчёт стоимости (вычисление, без изменения состояния)
POST /v1/resources/calculate
Риск — лимит длины URL при больших батчах. Лучше так:
POST /v1/resources/batch-get
Но как же "без изменения состояния"? Я понимаю, что мера вынужденная, но "как же принципы"?
Согласно RFC 9110 рекомендуется использовать POST с телом, вместо
DELETE
.
Даже в RFC противоречия...
Уместный комментарий! Чтобы из статьи получилась шпаргалка, я постарался сократить теорию до фактов. Для углубления прикрепил "полезные ссылки".
GET /resources/:id - не найден = 404, GET /resources/ - не найдено ни одного = 200
PUT /v1/resources/{id} - 404, если не позволено создавать ресурс "со своим идентификатором"? внезапно.
GET /resources/{id} -> 404, когда конкретного ресурса не существует
GET /resources/ -> 200 с пустой коллекцией ([] или { items: [], total: 0 }), когда коллекция существует, но в ней пока ничего нет.
{id} - конкретный ресурс, он либо есть, либо его нет
resources - ресурс-коллекция, она можно сказать существует всегда (если конечно существует родительский ресурс /parent/{parentId}/resources).
Коллекцию мы часто перечисляем (через пагинацию) и будет странно на этапе перехода по страницам внезапно получить 404. Эту тему можно развивать дальше, например в сторону фильтров, поиска.
POST /v1/resources/batch-get
Но как же "без изменения состояния"? Я понимаю, что мера вынужденная, но "как же принципы"?
Это осознанный компромисс и допустимая практика. Да GET - обязан быть безопасным. Но POST тоже может быть таким, если вы его так определили
А потом после таких "проектировщиков" в ELK приложение 100% отвечает 200 ОК, при том что там 500-х половина. Successfully Failed антипаттерн какой-то.
Ну, вас же не пугает, что еще и ошибки сокетов могут быть. А то так же получается, пакетик пришел в ответ - значит, все работает.
В вашем же случае нужно вычитывать всё тело запроса и искать там заветное слово
Это из другого вашего комментария, но я здесь цитирую. Так в чем проблема получить тело и десериализовать его из JSON в объект типа Result, который содержит Success или Fault? Это все равно придется сделать. Ради чего ориентироваться на статус HTTP здесь, чтобы на пару строк выше логирование вставить?
Ещё и модель OSI прилетаете. Http, на секундочку, уровень "прикладной", то есть как бы как раз того самого приложения.
На секундочку, HTTP является прикладным уровнем только для браузеров, да и то в современных реалиях вряд ли уже. Для REST это по сути транспортный протокол, только более высокого уровня.
Работайте кодами ответов HTTP и не слушайте вредных советов и команда SRE скажет вам спасибо за это.
Я вполне, кстати, реальный конкретный пример привел, когда писал, что мне пришлось ломать голову, как обрабатывать 404 ресурса и 404 уровня приложения, когда все было в кучу свалено.
На самом деле вот то, что мы здесь обсуждаем и есть одна из самых больших проблем REST (не как концепции, а как реализации REST over HTTP), которая приводит к жутковатому переплетению транспорта и уровня приложения: когда часть запросов посылается в JSON, а часть (в GET) компонентом URL; вышеуказанная проблема "как возвращать ошибку" и т.д.
Так в чем проблема получить тело и десериализовать его из JSON в объект типа Result, который содержит Success или Fault?
В целом этим и приходится заниматься когда интегрируешься с подобными API которые возвращают 200 и ошибку в теле.
Но чаще всего большинство современных интеграций оперирует статус-кодом, что позволяет работать с ними "из коробки"
Я вполне, кстати, реальный конкретный пример привел, когда писал, что мне пришлось ломать голову, как обрабатывать 404 ресурса и 404 уровня приложения, когда все было в кучу свалено.
Отличный пример в тему статьи! Тоже бывало ломал голову тот ли dns адрес получилось собрать.
Есть практика: "когда сервер намеренно не хочет раскрывать, что ресурс существует", я опираюсь чаще всего на неё, но не пропагандирую)
На самом деле вот то, что мы здесь обсуждаем и есть одна из самых больших проблем REST
Согласен с вами! Тема очень холиварная
И еще вопрос: как в тех же самых логах отличать 404 некорректного пользовательского ввода, которого всегда дофига, и реальную (архиважную!) проблему потери ресурса вследствие слетевшей/изменившейся конфигурации на одной из сторон? Лезть анализировать тело ответа уже на стороне ELK?
HTTP это не просто транспорт, а протокол прикладного уровня, на который опираются разработчики браузеров, CDN, прокси. Да и при обычной интеграции многие инфраструктуры на это завязаны: ретраи, SLO, алерты и тд. Всегда 2хх, остальное в body, как правило, ломает поведение экосистемы веба. Понимаю, что требование к приложениям у нас могут быть разные, но вся эта практика, книги и гайдлайны не на пустом месте появились - на это я и обращаю внимание читателя
А при чем здесь браузеры, CDN, прокси? Первые вообще из схемы исключаем, ну, если только кто-то не захочет просто браузер как инструмент визуализации использовать для запроса к сервису. CDN и прокси все равно по большей части, они на другом уровне работают. Мож, конечно, у них логирование по-другому сработает, если на некорректный (с точки зрения фильтра запроса) ответить 200, а не 404, но не думаю, что это имеет большое значение.
Как вообще книга? Советуешь к прочтению? Я сейчас другую читаю (Арно Лоре. Проектирование веб-API), если читал её, то что думаешь о ней? Я пока на первых главах
Книга интересная, но думаю, что достаточно прочесть хотя бы одну по проектированию API и дальше углубляться в существующие практики. Потому что, когда я начал сверять полученные идеи с собственными подходами и гайдлайнами крупных компаний, то местами нашёл расхождения, но везде прослеживается общий смысл. На этой почве и пришла идея этой статьи, показать что API может быть последовательным и предсказуемым, а конкретная реализация уже может зависеть от местных правил в компании
Просмотр ресурса (есть побочный эффект записи в лог/метрику)
POST /v1/resources/{id}/view
Почему здесь POST?
Шпаргалка по проектированию REST API