Comments 16
Пример: {[«a», «b»]}
А зачем возвращать невалидный JSON?
Для идентификаторов используйте UUID
Не думаю, что это является частью проектирования REST API
На счет ошибок: предпочитаю разделять ошибки транспорта от ошибок бизнеспроцесса.
Т.е. если транспорт (http) доставил запрос от клиента серверу и обратно доставил ответ сервера - то http-status=200 независимо от содержимого ответа сервера.
Мотивация:
- путь запроса большой и 503 может отдать nginx посередине. Искать такие ошибки - боль.
- в качестве транспорта может быть использован не только http (я видел АПИшку работающую и на UDP, и на RabbitMQ, пробовали даже принтер со сканером использовать но неудачно)
А 4xx не должен nginx выдавать кроме 404. Так почему бы не совмещать этот блок с блоком json, где детально расписать ошибку?
Правильно ли я понял, что на запрос с истекшим токеном, с невалидными входными данными, с не найденным объектом, etc, вы возвращаете 200? О_о
Невалидные данные - это не проблема транспорта. Он доставил запрос и обратно ответ без ошибок со своей стороны.
Не забываем, что мир одним только http не ограничивается. Приводя мой пример выше: мы активно использовали WinSocket для взаимодействия между СИшными либами, а там как вы понимаете нет никаких http-status. При этом эту же библиотеку применяли для межсервисного взаимодействия по http и UDP (для броадкаста, хотя позже на кроликов перевели)
Разумеется, т.к. ничего из этого не является частью протокола HTTP (транспортного слоя, как очень правильно отметил @XMack), а исключительно бизнес-логикой приложения. По этой причине REST в его "прямом" смысле - крайне отвратительная методология.
А всего лишь надо стандартизировать ответ ошибки, чтобы он включал в себя имя точки отказа и кодифицированную причину ошибки. В теле или заголовках.
Согласитесь, поведение клиента сильно отличается в случае ответа Too many requests, и в случае ответа Unauthorized.
А для бизнес-ошибок лучше возвращать 422 Unprocessable Entity.
здесь как вам удобно)
моя мысль в том, что крайне полезно сразу в спецификации видеть допустимые бизнес-ошибки и иметь их справочник. И всегда придерживаться одного подхода.
Я в основном имею дело с вэбом, и идея 200 ОК - {"error": "user_not_found"} контринтуитивна, и вызывает так сказать эмоции. Потому что я не ожидаю такое увидеть, и мне нужно тратить лишнее время чтобы понять подход и задумку. Потому что для меня ошибки транспорта обычно не связаны с архитектурой моего приложения, или легко локализуются (через поля c указанием сервиса-источника исключения). Но я допускаю, что могут быть задачи, где такой подход ок, наравне как и подход POST-API например. Но каждый из подходов сам в себе не регламентирует описание именно бизнесовых ошибок, тем более что они еще и по ходу пьесы всплывают частенько и не добавляются в спецификации. Повторюсь, я за полный справочник ошибок, их формальную привязку к месту возможного появления, и консистентность в выбранном подходе.
Тема хорошо проработана в GraphQL. Мы используем большую часть их рекомендаций и для обычного ReST:
https://github.com/graphql/graphql-spec/blob/main/spec/Section%207%20--%20Response.md
https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md.
Например, используйте iso-8601 YYYY-MM-DDThh:mm:ss±hh.
OpenAPI поддерживает RFC 3339, section 5.6. Если не нужны специфичные фишки из iso8601 (периоды или интервалы дат, например), то лучше выбрать первый. А тут можно наглядно увидеть разницу https://ijmacd.github.io/rfc3339-iso8601/
UUID — это стандарт идентификации, используемый в создании программного обеспечения Основное назначение UUID — это позволить распределённым системам уникально идентифицировать информацию без центра координации.
Тут есть как плюсы так и минусы, поэтому всегда так делать не стоит, нужно оценивать ситуацию. Если объект в бд хранится и по полю с идентификатором есть кластеры индекс, то лучше использовать монотонно возрастающие идентификатор от сиквенса. Так при вставке не придётся постоянно пересортировывать таблицу в бд.
Также генерация UUID более затратна и если нужно для массовой операции сразу 1000 идентификатор сформировать, то это можно сделать только последовательно дернув 1000 раз функцию получения UUID, а у сиквенса можно сразу диапазон запросить за один вызов.
по умолчанию, для меня плюсы uuid перевешивают недостатки, поэтому я беру его. В случае, когда нужно оценивать ситуацию, я скорее начну рассматривать варианты использования ulid или какого-нибудь ksuid (совместимые с uuid), нежели обычного инта из сиквенса)
Даже если int занимает 4 байта, а UUID 16? Особенно в сложных системах, где много таблиц нормализованных и много связей между ними по идентификатора. Да ещё и с индексами по этим идентификаторам.
Тут накладные расходы на хранение идентификатором могут быть значительными, всегда ли оно того стоит?
Есть популярный совет - не раскрывать в API идентификаторы из вашего хранилища. В таком случае, что показывать снаружи и что использовать внутри это два разных вопроса. Ответы на которые с течением времени могут меняться независимо друг от друга.
в теории хранение под uuid в 4 раза большего места выглядит значительно. На практике это не имеет решающего значения, т.к. помимо идентификаторов в моделях обычно хранятся на порядок больше полезных данных и относительный вклад небольшой. я не помню проблем из-за того, что размер базы сам по себе просто "очень большой", или индекс по полю c uuid работает медленнее интового. Если прям нужно экономить место, ну или вы видите явные преимущества данного типа - используйте конечно. Но не забудьте еще учесть возникающие сайд эффекты - проверьте API на возможность прямого доступа без проверки владения ресурсом, ну и вероятность мержа этой таблицы с какой-то другой из третьего источника в будущем. По мне проще, переплатить сразу местом хранения, чем читать новости про очередную слитую базу, в данном случае из-за банального недосмотра прав доступа и инкрементальных id)
Не "таймзона", а часовой пояс.
12 неочевидных правил проектирования REST API