Comments 195
Здесь представлены best practices, собранные на основе моего опыта и обсуждения с друзьями, которые работали над приложениями веб-служб REST.А по моему объяснено)
Это не объяснение, это "верьте мне". Объяснение — это "мы пробовали сделать вот так, и получилось вот такое" или "мы не делали по этой практике, и получилось вот такое".
https://habrahabr.ru/company/mailru/blog/345184/
Все эти сурьёзные принципы — как споры между сторонниками Big Endian и Little Endian, философы часами переубеждают друг друга, но всё это «правильное делание» имеет очень мало отношения к реальным проблемам.
В том числе и на REST API
По этой ссылке — рекомендации да дизайн API.
www.tmforum.org/resources/standard/tmf630-api-design-guidelines-3-0-r17-5-0
Придется зарегистрироваться.
8) забыли про 500х коды. И вообще про коды тут пункт ниочём. список HTTP статусов можно и на википедии посмотреть.
Мой бестпрактис касательно кодов: В самой первой версии апи сделайте поддержку 4х статусов
- 200 — ок
- 400 — неправильный запрос
- 404 — не найдено результатов
- 500 — внутренняя ошибка сервера
Этого достаточно на первое время, а если у вас есть бюджет вы всегда сможете расширить этот список. Главное — поддержать хотя бы их.
5) пагинация через link — имхо это даже бэд практис в некоторых случаях. Если мы предоставляем limit
/offset
(skip/size и т.п.) — то этого более чем достаточно для пагинации, и создание линков усложняет код, не привнося никакого дополнительного функционала (ведь пагинация и так есть). А вот отказываться от limit
/offset
в пользу линков — плохая идея. мы заведомо ограничиваем возможности клиента, не привнося ничего взамен.
7) А что за путаница у него в методах и их индепендности? и счего бы вдруг GET был индепендным? запросив одну и ту же книгу с разницей в несколько секунд, у неё может измениться статус или рейтинг. Но это уже мелочь
7) А что за путаница у него в методах и их индепендности?
Идемпотентности, а не индепендности. А путаница вызвана тем, что в статье дано некорректное определение:
Идемпотентность: когда вы получаете один и тот же ответ, сколько раз вы вызываете один и тот же ресурс, он известен как идемпотентный.
Корректное можно найти в википедии, и оно совсем про другое:
Идемпотентная операция в информатике — действие, многократное повторение которого эквивалентно однократному.
Примером такой операции могут служить GET-запросы в протоколе HTTP. По спецификации, сервер должен возвращать одни и те же ответы на идентичные запросы (при условии, что ресурс не изменился между ними по иным причинам). Такая особенность позволяет кэшировать ответы, снижая нагрузку на сеть.
Между выделенными жирным определениями есть принципиальная разница. При повторе запроса ответы могут отличаться. Один пример: упомянутое в википедии изменение (не из-за выполнения самого GET) запрашиваемого GET-ом ресурса между запросами. Другой пример: выполнение DELETE объекта с неким id — первый запрос может вернуть 204 No Content а последующие 404 Not Found, но при этом многократное удаление не отличается от однократного в том смысле, что в обоих случаях результат одинаковый — объект с этим id более не существует.
По сути, идемпотентные запросы полезны тем, что их можно безопасно автоматически повторять при необходимости (напр. из-за сетевых ошибок).
Идемпотентность: когда вы получаете один и тот же ответ, сколько раз вы вызываете один и тот же ресурс
Идемпотентная операция в информатике — действие, многократное повторение которого эквивалентно однократному.
Четное слово, не понимаю разницу между этими двумя определениями в контексте REST, можете пожалуйста объяснить?
[EDIT]
Перечитал ваш комментарий, вопрос снимается.
404 — не найдено результатов
Не найдено результатов это какой-то другой код. Либо, к примеру, просто пустой массив.
404 это именно про ненайденный ресурс.
Например для GET — если сам запрос прошел удачно, но оибку в бизнес логике, возврещается HTTP код 200 и документ с полем ошибка, описанием ошибки и кодом ошибки (код ошибки не HTTP, а самой системы).
Иначе получается путаница транспортного уровня (HTTP) и уровня бизнес логики.
Коды 5xx выделены под случаи неудачного выполнения операции по вине сервера.
Или 422, например если валидация не прошла.
… и клиенту нужно каждый раз парсить содержимое поле "ошибка". Спасибо, нет.
чем заворачивать условный HttpClient в трайкэтчи
А зачем это делать, если не секрет?
Со стороны клиента во много раз удобнее получать всегда один и тот же ответ и просто проверять в нем одно поле.
Знаете, я регулярно пишу и SOAP и REST-клиенты. Так что нет, лично мне намного удобнее, когда бросят эксепшн при неудачном логине, чем если я забуду проверить, что логин удачный, и получу ошибку уже потом, при запросе.
А зачем это делать, если не секрет?
Не секрет — "проверять все варианты ошибок"
лично мне намного удобнее, когда бросят эксепшн при неудачном логине
Только если вам нет нужды объяснять user'у перед экраном, что же, собственно, произошло нехорошего на той стороне интернета, из-за чего самый важный и срочный запрос в его жизни не проходит. Причем в максимально удобной и понятной для него форме, чтобы он самостоятельно вырулил из сложившейся ситуации и по-возможности минимально вынес мозг call-центру. А так — да, для разговоров "server-to-server" достаточно в лог скинуть сообщение и стектрейс.
Не секрет — "проверять все варианты ошибок"
Так для этого try..catch
не нужен, достаточно обработать код возврата.
Только если вам нет нужды объяснять user'у перед экраном [...] А так — да, для разговоров "server-to-server" достаточно в лог скинуть сообщение и стектрейс.
Ну так я и пишу преимущественно unattended-клиенты, и планирую сервера для таких. Другое дело, что использование HTTP-статусов для ошибок позволяет мне выбирать из этих двух способов.
Так для этого try..catch не нужен, достаточно обработать код возврата.
© lair
… и клиенту нужно каждый раз парсить содержимое поле "ошибка". Спасибо, нет.
© lair
Надеюсь, что "код возврата" это не "содержимое поля 'ошибка'" ;)
Правильно надеетесь. Я про HTTP Status. В том же .net у вас есть три варианта:
HttpResponseMessage response;
// 1
response.EnsureSuccessStatusCode();
//2
if (response.IsSuccessStatusCode())
//3
switch (response.StatusCode)
Выбор между ними зависит от стратегии клиента.
Т.е., "код возврата" — это HTTP Status, в котором не предусмотрены ошибки бизнес-логики. Я правильно понимаю, что вы, как клиент, предпочитаете в случае ошибки на уровне бизнес-логики (например, "клиент с таким ИНН уже есть") получить ответ от сервера со статусом 200?
Нет, неправильно. Я предпочитаю получить от сервера релевантный HTTP Status (в зависимости от того, что именно пошло не так), а дополнительную информацию — в теле ответа. Соответственно, ошибка "вы шлете нам невалидный ИНН" будет выражаться либо в 400, либо в 422 (в зависимости от нашего занудства), а уже в теле будет написано "дублирующийся ИНН", или INN: "duplicate"
, или в меру нашего извращения.
и клиенту нужно каждый раз парсить содержимое поле "ошибка". Спасибо, нет.
© liar
а уже в теле будет написано "дублирующийся ИНН", или INN: "duplicate", или в меру нашего извращения.
© liar
улыбнуло.
… а что вас "улыбнуло"-то? В этой схеме для того, чтобы понять, что запрос невалиден, не надо парсить тело ответа.
А тело ответа — оно зачем в таком случае? Либо его тоже нужно парсить, и тогда ваше "Спасибо, нет" неуместно, либо его парсить не нужно и тогда, согласно старику Оккаму, неуместно само тело. Вот это и улыбнуло.
А тело ответа — оно зачем в таком случае?
Для тех ситуаций, когда нам нужна дополнительная информация об ошибке (а не только сам факт, что она произошла).
но оибку в бизнес логике, возврещается HTTP код 200 и документ с полем ошибка, описанием ошибки и кодом ошибки (код ошибки не HTTP, а самой системы).
Вот за то коллега evgenyk и говорил. Если ошибка бизнес-логики, значит пользователь что-то сделал не так, как предусматривалось разработчиками, и ему нужна дополнительная информация, чтобы он сам мог разрулить ситуацию (если она разруливаемая), которая через состояния HTTP-статуса не передается. Очень редко, когда пользователь остается доволен, видя на экране надпись "Ошибка 500. Обратитесь в службу поддержки".
Я понимаю, откуда ноги растут у вашего "и клиенту нужно каждый раз парсить содержимое поле "ошибка". Спасибо, нет." — вы серверный разработчик с соответствующим взглядом на разработку.
Если ошибка бизнес-логики, значит пользователь что-то сделал не так,
Совершенно не обязательно. Собственно, у вас вообще может не быть человеческого пользователя.
ему нужна дополнительная информация, чтобы он сам мог разрулить ситуацию (если она разруливаемая), которая через состояния HTTP-статуса не передается
Ничего не мешает передавать дополнительную информацию вместе с кодом 500 — таким образом мы получим API, который удобнее для неинтерактивных клиентов, но ничем не хуже для интерактивных.
С точки зрения backend-разработчика человеческого пользователя нет всегда. Но это ограниченная точка зрения.
С точки зрения forntend-разработчика (в частности, разработчика SPA) человеческий пользователь есть слишком часто, чтобы его игнорировать. Разделение ошибок на 500-е (т.е., любые ошибки, не предусмотренные логикой операции, которые произошли в момент выполнения зпроса на сервере) и на ошибки бизнес-логики, зависящие от конкретной запрошенной операции (200-е с необходимостью анализа содержимого поля "ошибка") позволяют запускать на клиенте сценарии решения ошибочной с точки зрения бизнес-логики ситуации с использованием полученных с сервера данных.
Другими словами, 500-я ошибка — это всегда тупиковая ошибка. Ее обработка и на неинтерактивном клиенте и на интерактивном одинакова — логируем / показываем пользователю сообщение, что все плохо. Код ошибки в 200-м ответе сигнализирует, что выполнение запрошенной операции на сервере пошло по одному из предусмотренных разработчиками сценариев, не обеспечивающих успешного завершения в силу каких-то условий. В этом случае пользователь (интерактивный) или программа (неинтерактивный) может выполнить некоторые дополнительные действия, после чего повторить операцию.
То есть, если сценарий выполнения операции предусматривает возможность дублирования ИНН и имеются рекомендации для пользователя, что нужно сделать в данном случае — это ошибка бизнес-логики (код 200). Если на сервере при добавлении записи в БД произошла ошибка из-за того, что в таблице на поле ИНН повешен ключ уникальности, а записывались данные с повторяющимся значением ИНН, которая перехватилась и обернулась в типовое сообщение об ошибке на уровне REST framework'а, потому что разраб сервиса даже не предполагал такого варианта — это 500-я ошибка.
Почему не передавать все ошибки через 500-й код? Потому что в таком случае смешивают обычные ошибки (как правило выполнение операции невозможно по независящим от клиента обстоятельствам) и ошибки бизнес-логики (зачастую операция возможна при внесении некоторых изменений в данные или предварительном выполнении других операций), очень сильно привязанные к контексту текущих выполняемых действий (т.е. к тому же контексту, в котором на клиенте происходит дальнейшая обработка данных, полученных с сервера в результате успешного завершения операции с кодом 200).
Есть мнение, что ошибки валидации входных данных не являются ошибками, как таковыми — это предусмотренный сценарий взаимодействия. Эту точку зрения можно распростанить и на ошибки бизнес-логики, предусмотренные разработчиками сервисов. Все остальное — настоящие ошибки, 500-й код, окончательный тупик.
С точки зрения backend-разработчика человеческого пользователя нет всегда. Но это ограниченная точка зрения.
Это, очевидно, неправда — это я вам говорю как backend-разработчик.
Есть мнение, что ошибки валидации входных данных не являются ошибками, как таковыми — это предусмотренный сценарий взаимодействия.
Они являются ошибками, но не являются исключениями. Я выше уже приводил пример: если во входящем сообщении нет необходимого поля — это какая ошибка (или исключение)?
Разделение ошибок на 500-е (т.е., любые ошибки, не предусмотренные логикой операции, которые произошли в момент выполнения зпроса на сервере) и на ошибки бизнес-логики, зависящие от конкретной запрошенной операции (200-е с необходимостью анализа содержимого поля "ошибка") позволяют запускать на клиенте сценарии решения ошибочной с точки зрения бизнес-логики ситуации с использованием полученных с сервера данных.
Зато теперь все промежуточные системы потеряли возможность логировать "ошибки бизнес-логики", потому что они не знают, как их выделить из успешных ответов.
Знаете, у меня вот есть маленький проект, который интегрировался с тремя системами. Все они возвращали сообщения об ошибках с соответствующими кодами HTTP (4xx и 5xx). Когда мне понадобилась диагностика, я поставил стандартный модуль, который делает автотрассировку HTTP-вызовов, и сразу увидел статистику успешных и неуспешных вызовов (а для неуспешных — еще и тела ответов). А потом я добавил туда интеграцию со Slack, который возвращает ошибки в теле ответа с кодом 200. Мне пришлось пойти и дописать специальную обработку такого тела в диагностическую мидлварь (и теперь я вынужден, униформности ради, парсить тело дважды, тоже мило).
А универсального решения, устраивающего всех, нет. Если работаешь в разнородной среде то ради униформности чего только не приходится делать. Это вы ещё на фронт не залазили.
Если работаешь в разнородной среде то ради униформности чего только не приходится делать.
Так вот, один из критериев, упомянутых Филдингом — это униформный интерфейс. Возврат 2xx в случае, если произошла ошибка — нарушение униформности.
Во-первых, как вы определяете "корректный" запрос?
Во-вторых:
The 422 (Unprocessable Entity) status code means the server understands the content type of the request entity (hence a 415(Unsupported Media Type) status code is inappropriate), and the syntax of the request entity is correct (thus a 400 (Bad Request) status code is inappropriate) but was unable to process the contained instructions. For example, this error condition may occur if an XML request body contains well-formed (i.e., syntactically correct), but semantically erroneous, XML instructions.
Иными словами, вы не признаете приведенное выше определение 422 кода?
422 для велл-формед xml, но не соответствующего dtd/schema,
Противоречит моей цитате. Несоответствие схеме — это не семантическая ошибка. Там не зря написано "unable to process the contained instructions". Я вам более того скажу, хороший современный модел-биндер вход, не соответствующий схеме, сразу завернет обратно с 400 просто чтобы избежать атаки.
Плюс к экзепшенам, правильно разделенных по типам. Они позволяют строить действительно структурированный код. Если ошибка при вызове метода API была на уровне транспорта (а в случае REST API это еще надо разобраться, ха-ха) — бросаем исключение технического типа и не обрабатываем его на уровне бизнес-логики.
на клиенте нам важно знать получилось ли выполнить операцию или не получилось. получилось — только для статусов 200
всё остальное (и не важно, пропала сеть, неправльный урл, ошибка сервера, ошибка БЛ, да хоть апокалипсис) — это не получилось, тобишь ошибка.
Вы же предлагаете примешать некоторые ошибки к неошибкам что бы вместо одной проверки удалось/неудалось делать их кучу даже в том коде который должен выполняться когда «удалось».
вы заведомо пытаетесь усложнить код. усложнить состояние приложение. ввести ситуации когда у нас есть не только fail/success а ещё и «какбы success, но немножко fail»
всё остальное (и не важно, пропала сеть, неправльный урл, ошибка сервера, ошибка БЛ, да хоть апокалипсис) — это не получилось, тобишь ошибка.
Крайне, крайне ограниченный подход. Вам, как минимум, надо знать, произошла ли ошибка по вине сервера (и можно попробовать позже) или клиента (повторять такой же запрос бессмысленно).
так это отлично 400х 500х кодами решается
например на условный
POST: /users/123/im {text: 'hello'}
500 {ok: false, message: 'SMTP relay fail: code 1234', trace: ['/path/to','/bla/bla','/lib/smtp',...]}
— ошибка кода. когда возник необработанные ексепшн
или 500: {ok: false, message: 'user is not activated yet'}
— ошибка бизнесс-логики, когда произошла нештатная ситуация (отправлено сообщение пользователю, но он ещё не активирован)
или 404 {ok: false, message: 'user not found'}
— ошибка запроса. пользователя с таким ID нету.
на клиенте все эти ошибки можно обработать адекватно. а можно и не обрабатывать но знать хотя бы что сообщение не доставлено.
а если мы на 2 и 3 случай будем отдавать 200, то клиент будет думать — а, всё ок сообщение доставлено. он даже не узнает об ошибке, если мы отдельно не пропишем обработку псевдо-успешных запросов
И да, на бэке это делать геморройнее, чем просто отдавать ошибки хттп, я заморачиваюсь с этим ради удобства клиентов.
можно в тело ответа вставлять поле errors
и при это отдавать правильный статус
а можно отдавать ошибку, но "как будто не ошибку" и на клиенте обрабатывать не только успешные и провалившиеся запросы к серверу, а ещё и "немножко успешные, но чуть-чуть провалившиеся"
Кстати нет, на беке проще отдавать всегда 200 и не париться. HTTP коды вводят для улучшения удобства работы с api
Нет, клиент проверяет поле Errors в ответе и если оно не пустое, значит произошла ошибка. Но главное, что он теперь точно знает, что ошибка это не системная и не транспортная. Получается, что неуспешный ответ может прийти только во время отладки (под контролем разработчика) или если всё сломалось. По моему опыту это значительно удобнее.
To есть то что клиент теперь не может разбираться в разных категориях ошибок — это по-вашему хорошо? «Удобнее» — возможно (хотя тоже спорно)
Если код < 400 значит ОК, если нет — то ошибка — по-моему еще более простой в реализации на клиенте подход.
Еще такой момент, объясните пожалуйста что вы понимаете под «системная» ошибка, а то создается впечатление что вы с ветряными мельницами боретесь.
И да, на бэке это делать геморройнее, чем просто отдавать ошибки хттп, я заморачиваюсь с этим ради удобства клиентов.
На бэке послать 200 и кастомную ошибку обычно наоборот проще чем нормально настроить RESТ.
У вас похоже сложилось ложное впечатление что вам предлагают использовать статус коды ВМЕСТО поля errors. Это не так, их надо использовать СОВМЕСТНО.
В вашем же примере — в чем разница между «отправка неверного шэйпа тела» и «непройденная валидация в модели — ошибка пользователя»?
В вашем же примере — в чем разница между «отправка неверного шэйпа тела» и «непройденная валидация в модели — ошибка пользователя»?
Вот как бы да. Вот мы смотрим на тело {first_name: "John"}
— как нам понять, в нем last_name
нет потому, что его не ввели, или потому, что клиентское приложение его не отправило?
Во-первых, дефолтный сериализатор в .net из коробки это не различает: на запись вам надо будет выставить флаг, чтобы он так делал, а на чтение вам придется руками разбирать JObject
, чтобы понять, было там значение, или нет. Что хуже, во-вторых, вам придется заставить сделать то же самое каждого вашего клиента — и они не скажут вам за это спасибо. В-третьих, вы только что на пустом месте увеличили накладные расходы: теперь я должен всегда передавать все свойства, даже если заполнено только одно, а представьте, что дело происходит с мобильника.
(и это еще не считая тех, гм, интересных разработчиков, которые решили, что property: null
— это "запишите в свойство null`, а нет свойства — это "не трогайте свойство")
Не знаю про клиенты .net, но как по мне он должен формировать запрос в соответствии с объявленным сервером API.
С этим никто не спорит. Но объявленное вами API сложно в реализации.
А если не объявлено обязательным, то это уже не ошибка клиента и места 4xx ошибкам нет, ответ должен быть 2xx
… а где, бишь, это написано, и как с этим соотносится код 422?
Более того, речь как раз о том, что оно объявлено обязательным и непустым, и это значит, что сервер не может различить ситуацию "клиент не передал" от ситуации "пользователь не ввел" — и, как следствие, всегда должен возвращать 400/422.
Отправка неверного шэйпа — например, несоответствие тела json schema — это 400 или 422. Валидация модели — с успешно пройденной валидацией на схему несоответствие каким-то бизнес-правилам, глобальным или для конкретного кейса, требующего, как правило, анализ не только тела запроса, но и текущего стейта приложения. Чёткой грани в общем случае нет, но по дефолту в моих реализациях 400 или 422 это невозможность собрать в контроллере объект типа Command или Query, или просто определить параметры для вызова метода модели. Это в архитектуре, где у контроллера основная функция служить тупым адаптером к слою приложения, преобразовывать http-сообщения.
Отправка неверного шэйпа — например, несоответствие тела json schema — это 400 или 422.
Из вашего же примера:
...«поле ОКПО обязательно для типа клиента „юрлицо“» должны приходить с 200 кодом.
На каком уровне будет эта проверка? У вас будет возможность «собрать в контроллере объект типа Command или Query» и пустить его в модель обрабатывать бизнес логику, или он сразу отвалится на уровне валидации, сказав что без нужного поля в теле запроса он не пройдет?
Общее правило наверное такое для 4xx — выдавать их в ситуациях когда программный клиент (его разработчик) может проверить валидность запроса на своей стороне, но по каким-то то причинам или не проверял, или проверил, но неправильно.
Снова возвращаясь к вашему примеру: «поле ОКПО обязательно для типа клиента „юрлицо“» — по сути это же тот случай когда клиент не проверил на своей стороне что отправляет. И получается приходим к проблеме выбора какие ошибки отдавать для каждого поля — http или свои.
Опять же, как вы сказали, валидация скорее всего будет обрабатываться стандартными инструментами и отсекать «неправильные» запросы до начала их «полноценной» обработки.
В результате получим апи в котором часть эндпоинтов возвращает ошибки с 200 кодом, а часть с кодами хттп, и правила по которым эти ошибки различаются могут быть совершенно не очевидны тем, кто будет использовать или поддерживать это апи.
Еще можете пожалуйста объяснить почему на клиенте может быть полезно различать между 4хх и 200 ошибками в том подходе, который вы предлагаете.
По-вашему получается, что 200-е нельзя разделить между собой, а 500-е — можно.
Но это все ни о чем. Смысл заключается в фиксированных классах ошибок — чтобы клиент мог правильно обработать любую, в т.ч. новую, ошибку. В общем, если честно, Ваш пример настолько корявый, что обсуждать даже не хочется. Уберите статус коды, и увидите, что они просто лишние.
А сама проблема лежит глубже — REST смешивает транспортный и прикладной уровень, и подход "200 на все" эту проблему решает (правда, делая REST не нужным вообще).
А сама проблема лежит глубже — REST смешивает транспортный и прикладной уровень, и подход "200 на все" эту проблему решает (правда, делая REST не нужным вообще).
а к чему это тогда? используйте RPC или что вам ближе. Топик всё-же про REST. и выбирая его как протокол взаимодействия всё же стоит придерживаться рекомендаций этого стандарта.
А так да, вас никто не держит за руки. Кто-то пилит псевдо-REST через одни POST запросы, передавая {type: GET|POST|UPDATE|etc}
в теле. Кто-то отдаёт 200 на всё, даже ошибки. Кто-то делает 1 ендпоинт а все параметры переносит в query аля GET: /api/users/:id/books/
-> /api?model=users&id=:id&field=books
. Каждый строчит как он хочет
Но когда я начинаю работать над уже существующим проектом, я молюсь чтобы авторы легаси использовали стандарты а не своё "видение". Ибо "видение" очень часто в итоге превращается в боль
5xx — накосячил сервер
Ни разу не rocket science
Смысл заключается в фиксированных классах ошибок — чтобы клиент мог правильно обработать любую, в т.ч. новую, ошибку.
Можете привести пример где ваш подход делает «чтобы клиент мог правильно обработать любую, в т.ч. новую, ошибку», а стандартный REST подход — нет.
Сначала объясните, пожалуйста, что Вы понимаете под стандартным REST-подходом. Чувствую, имеет место недопонимание. На всякий случай напоминаю, я отвечал на коммент, где предлагалось делать так:
500 {ok: false, message: 'SMTP relay fail: code 1234', trace: ['/path/to','/bla/bla','/lib/smtp',...]} — ошибка кода. когда возник необработанные ексепшн
500: {ok: false, message: 'user is not activated yet'} — ошибка бизнесс-логики
ошибки бизнес-логики типа «договор закрыт», " клиент с таким ИНН уже есть", «поле ОКПО обязательно для типа клиента „юрлицо“ обязательно» должны приходить с 200 кодом
С каким кодом должна приходить ошибка «клиента с таким ИНН нет»?
Это ошибка бизнес логики, или клиента?
Если у клиента нет каких-то прав на выполнение запроса — это 403 или 200? Бизнес логика или транспорт?
Возможно для вашего случая — REST вообще не лучшее решение, и действительно удобнее использовать свою вариацию, но тогда для вас будут закрыты многие инструменты и подходы которые «окружают» эту методологию.
Лучше проверять Errors!=null, чем заворачивать условный HttpClient в трайкэтчи и проверять все варианты ошибок, расписывать на каждую свое действие и так же парсить поле «ошибка»плюс принудительно отключать эксепшены на неуспешные ответы и каждый раз проверять IsSuccess
Вы о чем вообще? Проверить что статус код < 400, a потом уже точно так же обрабатывать поле error — религия не позволяет?
А с вашим подходом недолго скатиться в идиотизм в стиле `200 OK User not found`
А с вашим подходом недолго скатиться в идиотизм в стиле 200 OK User not found
Что, заметим, лично мной виденное поведение у как минимум одного API: ты, значит, шлешь запрос на логин, а тебе в ответ 200 OK, в котором {code: "Invalid login/password"}
. Сколько раз я все проклинал при отладке.
И что?
Использование разных методов для запросов.
Это усложняет.
Но такие как Вы видимо этого не осознают.
Вам лишь бы код клепать.
Читай документацию по API и не будешь проклинать.
Использование разных методов для запросов. Это усложняет.
Да, усложняет. Но взамен мы получаем более читаемые запросы (и более простую работу на промежуточных узлах). Каждый выбирает для себя.
Читай документацию по API и не будешь проклинать.
Вот в этом и проблема, которую некоторые пытаются решить: минимизировать количество документации, чтение которой необходимо для работы с API. Опять-таки, всех разработчиков мидлвари не заставишь читать документации на все API, они бы что-нибудь униформное предпочли.
Основная проблема — ошибок в http мало, в любом случае свои структуры придумывать для более детальной информации. Но все же лучше совмещать оба подхода: использовать примерно 10 кодов ошибок, и в теле дополнительно больше информации давать.
Если же про различие ошибок бизнес-логики и транспортных, то выделите разные коды. Например, 422 — ошибки валидации (бизнес-логики), а 502 — ошибки бекенда, 504 — транспортные.
А тело ответа может быть таким (пвсеводокод):
<error_code>5238<error_code/>Красных надувных шариков нет на складе.
Когда я отдаю всегда 200 это значит, что все остальные ответы относятся к сетевым или фатальным проблемам и их можно фильтровать одной пачкой, типа повторите позже. Грубо говоря все остальные коды не могут появиться в жизни, если не отвалилась сеть или не легли какие-то из сервисов.
И у меня всегда одинаковый ответ состоящий из
Data<T> и Errors[]
, чтобы в типизированных языках на клиенте не пришлось под каждый ответ пилить отдельную модель.Если отдать в ответ 404, то как на клиенте понять в магазине нет соли или нет магазина?
А не надо в ответ на запрос "есть ли соль" отдавать 404, если ее нету — если, конечно, у вас правильно сформулирован запрос.
Но вообще, конечно, просто надо сначала прийти на корень магазина, и если там нет 404, то магазин есть. А соли — нет.
Когда я отдаю всегда 200 это значит, что все остальные ответы относятся к сетевым или фатальным проблемам и их можно фильтровать одной пачкой, типа повторите позже
… и вы, конечно, не учитываете тот факт, что запросы, на которые пришли ответы из 400-ой группы, так просто повторять нельзя?
надо сначала прийти на корень магазина, и если там нет 404, то магазин есть
Может, задеплоили не ту версию магазина. В котором эндпойнт "отсыпьте-соли" отсутствует.
И вот мне в ответ на запрос "отсыпьте-соли/2кг" возвращается 404. Если это значит "соль не завезли" — ну бог с ним, бывает. А если "эндпойнт не существует" — то надо бить в набат админу и слать ошибки в логи.
Чтобы по-разному обработать ошибку транспорта и отсутствие данных в базе (ошибка приложения), придется использовать код 200 для ошибки приложения.
Это я уже запутался. Вы правы, 404 тут как раз и должен возвращаться. Можете 418 попробовать (;
А на практике у меня обычно
404 {ok: false, message: 'not found'}
для отсутствия ендпоинта
и 404 {ok: false, result: null}
для отсутствия соли
это меня натолкнуло на мысль что стоит подумать над отсутствием соли. вероятно я измению этот ответ в ближайшем будущем.
к слову, для правильного запроса, по которому должен возвращаться массив. например корзина. в случае отсутствия элементов у меня
200 {ok: true, result: []}
Может, задеплоили не ту версию магазина. В котором эндпойнт "отсыпьте-соли" отсутствует.
А вы по приходу на корень магазина не проверяете доступные эндпойнты? Ну так горе вам.
И вот мне в ответ на запрос "отсыпьте-соли/2кг" возвращается 404. Если это значит "соль не завезли" — ну бог с ним, бывает.
Так у вас две семантических ошибки. "2 кг" — это не идентификатор ресурса, ему нечего делать в пути; поэтому и отсутствие соли — это не "404 не найден".
Чтобы по-разному обработать ошибку транспорта и отсутствие данных в базе (ошибка приложения), придется использовать код 200 для ошибки приложения.
Что мешает использовать код 500 для ошибки приложения?
А вы по приходу на корень магазина не проверяете доступные эндпойнты
Расскажите, как вы это делаете? Я не слышал о таком подходе.
"2 кг" — это не идентификатор ресурса
Так и знал, что пример с солью меня подведет.
Ну, замените на GET /project/123 — что изменится?
Аналогично, две ситуации:
- эндпойнт /project/ не создан в коде приложения (транспортная проблема — очень серьезно)
- проект 123 отсутствует в базе (ошибка приложения).
Что мешает использовать код 500 для ошибки приложения?
Например, семантичность :).
Даже если забыть про семантику — у нас остаются все те же 2 ситуации:
- База упала, ну или там out of memory — ошибка 500 на транспортном уровне, надо сообщать админу
- Проект 123 не найден — обычно ничего делать не надо.
А зачем вы вообще пытаетесь на клиенте определить критичность ошибки для сервера?
На клиенте важно только то, удаётся выполнить запрос или нет. Причина неудачи интересна не более, чем в разрезе временная ли она и стоит ли пытаться автоматически повторить идемпотентный запрос.
Проблемы сервера, потенциальные — вроде запросов к несуществующим ресурсам, или реальные — вроде out of memory или невозможности подключиться к БД, гораздо удобнее определять на стороне сервера, и там же реализовать их мониторинг и рассылку алертов. Клиент о таких вещах думать не должен от слова совсем.
Проблемы с соблюдением протокола клиенту интересны только в одном, достаточно редком кейсе: когда клиент свой, он всего один, и общается со сторонним сервером, который нередко без предупреждения меняет API. В остальных случаях клиенту это не нужно (если сервер свой, то проблемы с протоколом удобнее ловить на стороне сервера, если клиентов много то получать от каждого отчёт что сервер не то вернул очень неудобно, если API сервера стабильно то реализовывать в клиенте дополнительный контроль API избыточная паранойя).
Расскажите, как вы это делаете? Я не слышал о таком подходе.
По ссылкам. Пришли на /
, запросили ссылки, по типу нашли отвечающую за выдачу соли, перешли туда, запросили доступные действия. HATEOAS, одна из частей имплементации REST.
Ну, замените на GET /project/123 — что изменится?
Да ничего. Надо было сначала идти на project
, а потом на 123
, если вам так важно знать, где проблема.
Аналогично, две ситуации: [...] очень серьезно [...] ошибка приложения
Вот вопрос только, для кого эти две ситуации реально отличаются. То бишь, с чьей стороны мы смотрим на API.
Например, семантичность
Семантически все верно: "The 500 (Internal Server Error) status code indicates that the server encountered an unexpected condition that prevented it from fulfilling the request."
База упала, ну или там out of memory — ошибка 500 на транспортном уровне, надо сообщать админу
Не надо сообщать админу, админ сам все мониторит. А вы получили свой 503 — и ждите. И, заметим, никакого 500.
Не соглашусь по двум пунктам:
- 400 — BadRequest, указывает на то что сервер не смог понять запрос. Используется когда в эндпоинт нужен строго определенный входной параметр, а вместо него присылают что-то другое. Если сервер смог понять что ему прислали и это по структуре совпадает с тем что должно быть, то 400 быть не должно. На ошибки валидации есть код 422 — Unprocessable Entity, который как раз и означает что переданная сущность не может быть обработана (из-за ошибок)
- Ошибки валидации должны быть всегда, а не только во время отладки. Например, после выпуска приложения вы обновили API и ранее необязательное поле сделали обязательным (не лучший пример, но возможен). Нормальное приложение сможет это обработать и показать пользователю ошибку валидации, которую нашел сервер. Если сервер не будет присылать ошибки, то пользователь не узнает что именно произошло не так и может не выполнить свою задачу
2. Так в моем случае как раз приходят ошибки которые можно напрямую отдавать юзеру, потому что они осмысленные. При изменившейся валидации я верну 200, но поле Errors будет содержать новые требования, который юзер сразу увидит. А если возвращать 400, то это уже эксепшн на клиенте, который неизвестно как предвосхитить.
При изменившейся валидации я верну 200, но поле Errors будет содержать новые требования, который юзер сразу увидит.
Ну то есть вы все-таки пишете логику обработку ошибок на клиенте, даже если их не может быть.
А если возвращать 400, то это уже эксепшн на клиенте, который неизвестно как предвосхитить.
Эмм, это проблема вашего клиента, выкиньте его и возьмите нормальный. Стандартный дотнетовский HttpClient
так себя не ведет.
Ну то есть вы все-таки пишете логику обработку ошибок на клиенте, даже если их не может быть.
Я предлагаю на клиенте засунуть запрос в трайкэтч и в кэтче предлагать юзеру трайэгэйн, потому что проблема точно не на клиенте. А если ответ успешен и Errors!=null то показывать его содержимое.
дотнетовский HttpClient так себя не ведет
Как это не ведет? При .EnsureSuccess… Он именно так себя и ведет. Да и на остальных, типа рестшарпа, надо вручную отключать выброс эксепшнов на неуспешных ответах.
Хорошо. Я вижу в вашем подходе на клиенте мешанину из транспортных и программных ошибок, у которых надо сортировать не только коды, но и описания. Какие плюсы у вашего подхода, кроме псевдостандартности?
Как это не ведет? При .EnsureSuccess… Он именно так себя и ведет.
А кто вас заставляет делать EnsureSuccessStatusCode
?
Какие плюсы у вашего подхода, кроме псевдостандартности?
То, что я знаю, что если сервер вернул мне 200 — тело можно парсить, и там то, что я запрашивал. И если меня интересует happy path, я могу просто проверять на ответы 200-ой группы, и считать все остальное фейлами, не тратя ресурсы на парсинг ответа. И еще то, что системы мониторинга тоже не надо парсить тело, чтобы определять статус происходящего.
Моей системе серверного мониторинга интересны случаи, когда запрос не был успешно выполнен. Почему — дело пятнадцатое.
Как по мне, то ситуации, когда малоквалифицированный пользователь не заполнил обязательное поле серверному мониторингу не интересна, по сути это ложноположительное срабатывание серверного мониторинга, направленного на обеспечение работоспособности сервера. Она может быть интересна только разработчикам (владельцам) клиента для анализа того, как так получилось, что клиент решил отправить запрос с незаполненным обязательным полем.
(пропустил почему-то)
Я предлагаю на клиенте засунуть запрос в трайкэтч и в кэтче предлагать юзеру трайэгэйн, потому что проблема точно не на клиенте.
Откуда вы знаете, что она не на клиенте? Какой смысл делать еще одну попытку, если сервер вам сказал "not authorized" или, того веселее, "invalid media type"? А разделять между "мы не достучались до сервера" и "сервер сказал, что мы слишком много запросов делаем" тоже не надо?
А если ответ успешен и Errors!=null то показывать его содержимое.
… а там, значит, написано "У вас нет прав на эту операцию". И что пользователю дальше делать?
А кто вас заставляет делать EnsureSuccessStatusCode?
Никто не заставляет. Так просто удобнее отсеять сразу все транспортные проблемы.
invalid media type
Оно не может вылезти во время эксплуатации. А если вылезло, то все настолько плохо, что все равно надо дебажить.
not authorized
Это в любом случае должно обрабатываться отдельно, особенно учитывая всякие акцес и рефреш токены и что, например в OpenID Connect эта ошибка описывается вообще в хэдерах, а не в теле ответа.
Откуда вы знаете, что она не на клиенте?
Потому что все ошибки такого плана должны отлавливаться при разработке клиента и в эксплуатации не возникать.
мы слишком много запросов делаем
Это вообще идеальный пример правильности моего подхрда. Такую ошибку как раз надо юзеру показывать. Даже гугл в брауезере не стесняется это делать. А как вы ее через обработку кодов обработаете?
считать все остальное фейлами, не тратя ресурсы на парсинг ответа.
В том-то и проблема, что не только тратя, но тратя в 2 раза больше, чем можно тратить, используя подход REST over HTTP.
Я же не просто так это пишу, а по собственному опыту использования множества разных апи.
Никто не заставляет. Так просто удобнее отсеять сразу все транспортные проблемы.
Если у вас случились транспортные проблемы, вы не получите ответа вообще (кроме случаев с прокси и их специфическими ошибками).
Оно не может вылезти во время эксплуатации. А если вылезло, то все настолько плохо, что все равно надо дебажить.
Ну то есть ретраить-то бесполезно.
Это в любом случае должно обрабатываться отдельно
То есть тоже ретраить бесполезно.
Потому что все ошибки такого плана должны отлавливаться при разработке клиента и в эксплуатации не возникать.
В реальности так не бывает, и между сервером и клиентом постоянно случаются расхождения.
Такую ошибку [мы слишком много запросов делаем] как раз надо юзеру показывать.
То есть опять нельзя ретраить. Не многовато ли получается исключений из вашего правила "Я предлагаю на клиенте засунуть запрос в трайкэтч и в кэтче предлагать юзеру трайэгэйн".
А как вы ее через обработку кодов обработаете?
Ну так тривиально же: ловим 429, дальше блокируем все запросы до истечения Retry-After
, если он есть.
В том-то и проблема, что не только тратя, но тратя в 2 раза больше, чем можно тратить, используя подход REST over HTTP.
… и где я их трачу в два раза больше, покажите, пожалуйста?
Я же не просто так это пишу, а по собственному опыту использования множества разных апи.
Likewise.
В реальности так не бывает, и между сервером и клиентом постоянно случаются расхождения.
В этом-то и проблема ваших суждений. Когда я писал мобильных клиентов, они никогда не расходились с апи, т.к. это во-первых странно, а во-вторых бессмысленно.
Когда я писал мобильных клиентов, они никогда не расходились с апи
Завидую вам. В моей реальности клиент расходится с сервером даже когда их пишет одна команда (потому что есть k версий серверов и n версий клиентов); а как только за клиент и сервер начинают отвечать разные команды (что уж говорить про разные компании?) — там даже в рамках одной версии есть недопонимания.
Иными словами, если клиенты никогда не расходятся с API, откуда все эти некорректные запросы в моих логах?
Так зачем писать логику разбора и обхождения этой ошибки на клиенте, если ее никогда не будет при моем подходе?
Чтобы в ситуации, когда на сервере валидацию поменяют, а на клиенте — нет, вам было понятно, что происходит. А если вам на эту ситуацию положить, то вы говорите "все незнакомые статус — в эксепшн", и больше ничего не делаете.
Если отдать в ответ 404, то как на клиенте понять в магазине нет соли или нет магазина
Если у вас нет магазина — то у вас должен быть зафэйленный деплой бэкенда с кучей логов и алертами. А клиент о том что магазин не задеплоился знать вообще не должен.
Когда я отдаю всегда 200 это значит, что все остальные ответы относятся к сетевым или фатальным проблемам и их можно фильтровать одной пачкой, типа повторите позже
POST потом тоже будете «повторять позже»?
POST потом тоже будете «повторять позже»?
Именно! Потому что раз произошла транспортная ошибка, то я данные вообще не получил и можно их отправить еще раз. В этом и есть главный плюс разделения ошибок.
Если же мы делаем REST API over HTTP, то просто используем HTTP как транспортный протокол (в целом это скорее прикладной протокол) и руководствуемся исключительно практическими соображениями, в пределе дав клиентам один урл типа /api с методом POST и возвращая 200 в случае если запрос вообще дошёл до приложения и принят им для обработки, начат анализ тела запроса.
На практике чаще всего смешивают оба подхода, или вообще говорят о REST HTTP API, на деле не соблюдая принципов REST вообще.
Это всё абсолютно верно, только я не понимаю, в чём вообще смысл использовать REST API over HTTP. Как по мне, в момент принятия решения использовать HTTP исключительно в качестве транспорта REST теряет большую часть своей привлекательности и выбор какого-нибудь RPC протокола over HTTP становится более разумным.
Безусловно, только сколько пользы остаётся от этих принципов после того, как мы потеряли возможность использовать громадное количество существующих инструментов, поддерживающих эти принципы? Пока REST использует соответствующие методы HTTP все эти инструменты могут перехватывать запросы, модифицировать их, кешировать, автоматически повторять идемпотентные… но как только мы сделали "over HTTP" и для всего теперь используем POST — все эти инструменты стали неприменимы.
Теперь для использования заложенных в REST принципов нам нужно писать собственные инструменты. А если всё-равно писать собственное, то не проще ли это делать уже не ограничивая себя REST-ом, если только он не подходит действительно идеально для текущего проекта (и я лично таких проектов не встречал — всегда что-нибудь да "выпирает" из "чистого RESTful")?
В таком случае запрос OPTIONS вернёт клиенту 200 со всеми вытекающими.
Во-первых, стандарт.
Во-вторых, не надо писать документацию.
В-третьих, легкая и быстрая интеграция: скормил метадату «клиенту» и тот знает что от куда брать и как с чем связано.
Такое ощущение, что при придумывании REST интерфейса основное время уходит на проверку следования рекомендациям и, при смене авторитета, изменениям в реализации.
Может быть, RPC подход не так уж и плох? Или SOAP.
304 Not Modified — используйте этот код состояния, когда заголовки HTTP-кеширования находятся в работе
когда имеете дело с HTTP-кешированием (или когда работаете с HTTP-кеширвоанием)
до этого момента в тексте не замечал, что это перевод, а после — сразу видно, как будто немного лень стало переводить
200 OK — это ответ на успешные GET, PUT, PATCH или DELETE.
Хотя до этого, в списке методов PATCH вообще не упоминался.
Касательно предмета разговора, считаю использование методов кроме GET и POST избыточным, по ответам использую в основном 200, 400, 401, 404, 413, 422, 500.
В академическом смысле REST направлен на управление ресурсом, а под ресурсом, как правило, пониматься документ не имеющий бизнес-логики, которая создаст сайд-эффекты.
Базовый пример: изменить статус заказа на «Отгружен»
REST: говорим серверу изменить статус заказа с любого на 2
PUT /order/1 HTTP/1.0
{
"status": 2,
"seller_id": 101,
"client_name": "Mr. Holmes",
"client_address": "Baker st, 221b"
// ...
}
Реальный мир: перевод заказа в какой-то статус имеет действия бизнес-логики (управление остатком товара на складе, уведомления клиенту и менеджеру, отправка данных в API транспортной компании и тд), поэтому в своих проектах задача «перевести заказ в статус Отгружено» решаю POST запросом с объектом:
POST /order/ship HTTP/1.0
{
"order_id": 1
}
При реализации по REST сервер должен сам сделать diff текущего состояния ресурса с измененным, решить что там за операция бизнес-логики и выполнить ее. Мне такой подход не нравится, поэтому на бизес-операции делаю отдельные точки входа, из соображений 1 запрос = 1 бизнес-действие. Так удобнее поддерживать, тестировать и разрабатывать.
PUT /order/1/status HTTP/1.0
{ "value": 2 }
Не очевидно. API делается для внешних разработчиков, поэтому преимущества семантического программирования тут показывают себя во всей красе. После прочтения какого из методов проще понять что происходит при его вызове: /order/1/status/2
или /order/1/ship
? Мне больше нравится второй вариант
А почему не POST /order/1/ship HTTP/1.0
?
В классических подходах современных MVC фреймворках роутинг строится по принципу /{Controller}/{Method}
. Реализовать /order/ship
проще чем /order/1/ship
. Предложенный вариант тоже хорош, на первый взгляд мне нравится, но реализация требует дополнительных усилий по конфигурации проекта, а мне этого делать обычно лень. Кастомный конфиг роутинга может стать запутанным и поддержка будет сложнее.
А кто-нибудь может объяснить откуда ажиотаж (вроде уже немного спал) вокруг GraphQL? Никаких преимуществ против нормально документированного REST-like я не вижу
GraphQL это следующий шаг в REST API. Со своими плюсами и минусами.
Плюсы связаны в основном с развитием технологий — непример в GraphQL можно интегрировать real-time за счёт вебсокетов, и это всё ещё находится в рамках GraphQL (в отличие от рест, где сокеты — это что-то отдельное).
Самодокументирования, типизация, строгая объектная модель — это всё про GraphQL.
Минусы в основном сосредоточены в проблемах реализации адекватной ACL модели, и необходимостью продумывать ограничения тяжести запросов на предмет DDoS сервера. Впрочем, в REST эти проблемы тоже есть.
Основное "из жизни" преимущество GraphQL — в половине случаев не надо менять бэк, если немного изменилась конфигурация необходимых фронту параметров.
Итоговая производительность у graphql-клиента будет выше за счёт объединения запросов
Существует неправильное представление о том, что все данные, доступные через сеть, считаются REST, но это не так.
Нет, это существует мейнстримовое представление, что только REST API по этим гребаным лучшим практикам — REST, а все остальное непойми что.
Мне не очень нравятся занятия любовью с выбором метода запроса или чтения кодов ответов. Почему метод должен обязательно быть в строке адреса?
Дайте мне просто метод, который нужно дернуть.
Мне вот нравится ВК API.
Оно что, не REST?
Еще как REST.
Что мне это напоминает?
Да любую мейнстримовую истерию.
Да хоть на счет того же Agile.
Это хомякам кто-то оплачивает эти статьи?
Или они сами выбрали течь в чужих сомнительных парадигмах?
Мне вот нравится ВК API. Оно что, не REST? Еще как REST.
По какому формальному определению?
ru.wikipedia.org/wiki/REST
То есть вы утверждаете, ВК API (какой конкретно, кстати?) выполняет пять из шести ограничений по Филдингу?
Я не ВК.
Я смотрю на сам «протокол».
2. Я считаю да.
Да и некоторые ограничие какие-то тупорылые и непонятные.
Вы считаете нет, что он нарушает?
Какой API? Да тот, который в документации освещен по умолчанию.
ВК API может как отвечать всем требованиям, так и не отвечать.
Если он не отвечает требованиям этого определения, то он не REST в рамках этого определения.
Да и некоторые ограничие какие-то тупорылые и непонятные.
А это не важно, они все равно часть определения. Если они вам не нравятся — значит, это определение вам не подходит. Ок, дайте другое.
Какой API? Да тот, который в документации освещен по умолчанию.
Ссылку дайте, пожалуйста, на конкретное описание. Я сходу нашел кучу разных, непонятно, о чем именно речь идет.
Меня интересует, как делать запросы и получать ответы.
Да, документация по API немного запутанная. Не сразу поймешь куда попадешь.
Вот это vk.com/dev/methods
Меня интересует, как делать запросы и получать ответы.
Оно и видно. Вас не интересует, REST ли это, вас интересует, как делать запросы и ответы. Ну и прекрасно. Просто оставьте REST в покое и пользуйтесь теми API, которые вам удобны.
Вот это vk.com/dev/methods
Выглядит как типичный RPC.
Потому что RPC — это просто констатация межпроцессного взаимодействия.
Это не конкретный протокол.
А REST — это RPC по HTTP.
Многие под REST и RPC почему-то понимают конкретный протокол.
Но это заблуждение.
Потому что RPC — это просто констатация межпроцессного взаимодействия.
Конечно, нет. RPC — это remote procedure call, вполне конкретный стиль межпроцессного взаимодействия. Скажем, если вы в SOA кидаетесь документными событиями, у вас будет межсистемное взаимодействие, но не будет RPC.
А REST — это RPC по HTTP.
Просто нет. Во-первых, вы не найдете определения, где это было бы сказано, во-вторых, вот: https://www.quora.com/What-is-the-difference-between-REST-and-RPC
Это не только HTTP.
2. Иногда просто нужно думать головой и уметь складывать цельную картину.
3. Из статьи о REST на вики:
В сети Интернет вызов удалённой процедуры может представлять собой обычный HTTP-запрос (обычно «GET» или «POST»; такой запрос называют «REST-запрос»), а необходимые данные передаются в качестве параметров запроса[2][3].
вызов удалённой процедуры — ссылка на RPC.
4. Да потому что интернеты захватили упоротыши.
Вон еще один статью накалякал с бредом.
Это как форсед мем.
5. По ссылке — это все ерунда.
Если мой ответ не пробъет Вашу броню, то можете не отвечать мне.
Почитай что такое RPC на википедии. Это не только HTTP.
Я где-то утверждал, что RPC — это только HTTP? Вроде нет.
Из статьи о REST на вики:
Если подниметесь буквально на строчку выше вашей цитаты, там написано буквально: "REST является альтернативой RPC" (выделение мое)
Да потому что интернеты захватили упоротыши. [...] По ссылке — это все ерунда.
Конечно, один вы знаете, как правильно определяется REST. Было бы здорово, конечно, увидеть-таки ссылку на признанное формальное определение.
Везде где могу — использую или реализую JsonRPC 2.0 (http://www.jsonrpc.org/specification). Больше нет неоднозначностей с объектами, нет ограничений в методах, нет необходимости определять — ошибка на уровне протокола хттп или бизнес-логики. Да и вообще без разницы, отправка идет по чистому хттп или через веб-сокеты. А еще нет смешения свойств объекта ответа с информацией о результате выполнения. Все просто и прозрачно. А такое в энтерпрайз не берут.
Мне искренне непонятно как вообще на REST можно построить API для типичных страниц приложения — где надо показать 10 наиболее релевантных зайчиков, статистику по белочкам, детали по первому зайчику, и вот это всё. Если делать в стиле REST — это надо будет на каждой странице в 20 эндпоинтов ходить.
Да, есть случаи когда REST неплохо подойдет. Скажем, как API какого-нибудь популярного приложения типа Trello или Gmail-а. Там это API нужно, в основном, чтобы быстро разобраться, зацепиться, и сделать какую-нибудь простую штуку типа «письмо пришло — создали таску».
При этому, можно легко убедиться, что тот же Gmail, имея публичное REST API, в веб-приложении пользуется совсем другими внутренними API, которые ни разу не REST. И так — делают все нормальные люди. Потому что REST не подходит для построения API для веб- или мобильного приложения. GraphQL — подходит, JSON RPC — подходит, а REST — нет.
Яростная пропаганда REST, как лучшего похода для API — это очень вредная штука. Я лично видел как из-за этого факапят проекты в человеко-годы.
— как в django rest framework:
site.com/books?author=George Orwell&year__lte=1945&ordering=price
— исползьуя POST(некоторые и так делают):
POST site.com/filters/books
BODY: {
«filter»: ...,
«ordering»: ...,
}
— используя urlencode (лично мне этот метод нравится, т.к. не нужно запоминать извращённую djangoвскую нотацию и можно кешировать, также нет конфликтов полей книги и служебных слов(ordering), но минус — читабельность)
site.com/books?filter=author%3DGeorge%20Orwell%26year%3C%3D1945&order_by=price
— создавать фильтр POST'ом, записывая в базу и делать дальнейший запрос как-то так:
site.com/filters/1234
ИМХО, самый извращённый вариант, зачем хранить ненужный мусор в БД?
И тыщи других вариантов.
1. Конечные точки в URL – имя существительное, не глагол
Можно ли сделать из этого вывод, что нужно использовать
https://my-site.com/subscribtions/{subscribe_id}?unsubscribe
вместо
https://my-site.com/subscribtions/{subscribe_id}/unsubscribe
?
Совсем по уму, уж если у вас есть множество subscriptions
с отдельными элементами, достаточно делать DELETE
на этом элементе.
Недавно с коллегами обсуждали такой кейс, будет интересно выслушать мнения :)
Есть сущность «купон», в базе есть как идентификатор (первичный ключ), так и уникальный код купона. Так как пользователь знает только этот код, он может оперировать только им. Соответственно сейчас наши эндпоинты выглядят как GET whatever/api/coupons/code/ для получения инфы по купону. На сколько это легально, учитывая, что код купона может содержать экранируемые символы (пробел, например)?
coupon-code
будет как отдельная сущность. whatever/api/coupon-code/abcd/coupon
, по аналогии с whatever/api/product/1/picture
.При правильном URL-кодировании проблем быть не должно, но кажется в nginx бывают какие-то проблемы с экранированным слэшем.
В целом, что у вас есть в базе уровня HTTP REST API не касается. Какой-то суррогатный идентификатор (например serial/auto_increment int) на уровне СУБД вовсе не должен обязательно выставляться в веб. Какой-то гарантированно уникальный, условно естественный код (например вторичный ключ в базе), вполне годится для использования в uri как уникальная часть идентификатора ресурсов определенного типа во всех ситуациях на уровне HTTP REST API, в виде, напрмиер whatever/api/coupons/{code}, то есть о вашем первичном ключе вашим клиентам вообще знать незачем, у них есть способ однозначно идентифицировать каждый купон.
2. Множественное число
Старый спор, из серии какое число выбрать для названия таблицы базы данных, и по-моему единственное число в итоге имеет больше плюсов
Мультимедийный способ управления версиями:
Жесть, не надо такого делать никогда. Как вы в респонсе урлом объясните клиенту номер версии? А ваш фреймворк такое кстати поймёт когда будет определяться во что конвертировать объект в xml или json?
PUT: этот метод является идемпотентным. Вот почему лучше использовать этот метод вместо POST для обновления ресурсов. Избегайте использования POST для обновления ресурсов.
пост по спецификации всегда создаёт подресурс (субординат) по урлу, так что семантически он не для обновления и сюрприз тут кроется в том, что браузер именно так и думает и при кэшировании эта разница всплывёт неприятным образом для тех кто использует post для обновления а не создания подресурсов
401 Unauthorized — Если не указаны или недействительны данные аутентификации. Также полезно активировать всплывающее окно auth, если приложение используется из браузера
когда-то скопипастил с хабра, кажется с блога Яндекса.
статус 401 Unauthorized обязан сопровождаться заголовком WWW-Authenticate и, таким образом, применим только тогда, когда клиент аутентифицируется посредством HTTP-аутентификации; во всех остальных случаях следует использовать 403 Forbidden;
В общем передавайте привет «Krishna Srinivasan» из солнечной Индии :) (и прочитайте ту статья в блоге Яндекса она полнее и точнее)
пост по спецификации всегда создаёт подресурс (субординат) по урлу
Да откуда вообще вы берёте эту информацию? В спецификации я вижу другое:
The POST method requests that the target resource process the
representation enclosed in the request according to the resource's
own specific semantics
Так что POST можно использовать для чего угодно.
браузер именно так и думает и при кэшировании эта разница всплывёт неприятным образом для тех кто использует post для обновления а не создания подресурсов
Устаревшая версия ресурса может "всплыть" при любом способе обновления, единственный способ этого избежать — отключить кеширование вовсе. PUT инвалидирует лишь тот кеш, через который прошёл, и лишь для одного ресурса. Это редко когда бывает достаточно.
REST API Best Practices