Но здесь минус — теряется ясность мысли. К примеру, если заказ можно только подтвердить и метод предназначен только для этого.
Еще пример:
POST notifications/1/resend
Ясно что переотправка оповещения. К примеру, если не пришло СМС — отправить повторно (считаем что на сервере по какой-либо причине статус отпавки — не определено и вынуждены доверять пользователю после 30 сек.).
Не комильфо. По канону будет так:
POST notifications/1/sending-attempt
Т.е. как бы создаем новую попытку отправки. По идее и таблицу попыток нужно бы добавить, но можно пока и без таблицы — главное API.
Ты или придерживается REST и тогда используешь его как положено, либо не придерживаешься и делаешь всё как бог на душу положит. Тут нет правильного или не правильного ответа, просто не надо называть вещи не своими именами.
Вроде как у тебя понимание REST есть, но "теряется ясность мысли" — это я не понимаю. Что значит теряется. REST это не про ясность, это про определённые гарантии, в основном про идемпотентность. То бишь можно ли данный запрос повторять, если возникла ошибка или нельзя.
Здравствуйте, vsb, Вы писали:
vsb>Вроде как у тебя понимание REST есть, но "теряется ясность мысли" — это я не понимаю. Что значит теряется. REST это не про ясность, это про определённые гарантии, в основном про идемпотентность. То бишь можно ли данный запрос повторять, если возникла ошибка или нельзя.
См. второй пример — оба варианта POST. Вопрос в другом — глагол или имя существительное.
В первом примере POST и PATCH. Оба считаются не идемпотентыми.
Здравствуйте, Shmj, Вы писали:
S>Но здесь минус — теряется ясность мысли.
S>Как вы на все это смотрите?
Как уже выше отметили, практическая ценность REST только в идемпотентности по большому счёту. Что там тебе кажется логичным и последовательным в именовании — это твоя личная "логичность и последовательность", не стоит заблуждаться по этому поводу.
Если мне от твоего сервиса что-то надо, я к тебе приду и спрошу что там надо дёргать (потому что во-первых см. выше, во-вторых стопудово документации нет). Если тебе так не нравится — задокументируй, читаю я хорошо
vsb>В твоём случае нет никакой причины ему быть не-идемпотентным.
Подтвердить заказ, который только что отменили — должно вызвать ошибку. vsb>POST во втором случае не нужен. Делай PUT notifications/1/attempt/{uuid} и повторяй его, если получена ошибка (кроме 4xx).
Ключ идемпотентности можно применить и к POST. Вкусовщина.
Здравствуйте, rosencrantz, Вы писали:
R>Как уже выше отметили, практическая ценность REST только в идемпотентности по большому счёту.
Идемпотентность не специфична только для REST.
R>Что там тебе кажется логичным и последовательным в именовании — это твоя личная "логичность и последовательность", не стоит заблуждаться по этому поводу.
Не путаю. Я поэтому и написал "обычно". Да, можно спроектировать API, в котором PATCH не-идемпотентен (я привёл такой пример в своём сообщении). Хотя лично я считаю, что это ошибка и такие API проектировать не надо. PUT и PATCH нужно делать идемпотентными, а все не-идемпотентные операции переложить на POST и там можно даже не притворяться, что у нас REST, а просто писать, как удобно, всё равно там никаких гарантий. Но в идеале таких операций быть не должно.
vsb>>В твоём случае нет никакой причины ему быть не-идемпотентным.
S>Подтвердить заказ, который только что отменили — должно вызвать ошибку.
Это можно о любой операции сказать. Идемпотентность не означает, что эта операция всегда срабатывает. PUT тоже не будет работать, если это нарушает какие-то условия уникальности, к примеру. Если возникают проблемы, ты возвращаешь ошибку 4xx, которую уже можно и нужно показывать пользователю в каком-то виде, ну или ещё как-то обрабатывать, но не повторять,
S>Ключ идемпотентности можно применить и к POST. Вкусовщина.
Можно, но принято эту семантику накладывать на PUT.
Даже и не пытался спорить. Я про твои логичные последовательные попытки нагородить эту онтологию из confirmation, notification, notification-attempt, и т.д.
RESTFul API — это не стандарт, не спецификация, т.е. никто не накладывает жестких ограничений
лучше всего делать так, как удобно вам, т.к. в подавляющем большинстве случаев речь идет об АПИ в рамках одной компании, т.е. для связи бэка и фронта/мобилок
это обычно обходится быстрее и дешевле, т.к. меньше времени уходит на буквоедство, а фичи быстрее появляются в продакшене; сам проходил через это, когда обсуждали очень долго как лучше назвать, какой глагол (GET/PUT/PATCH) использовать, как правильно/не правильно, вместо того, чтобы забить на это и сделать так, как НАМ удобно самое важное: чтобы была консистетность в рамках одного АПИ + документация к этому
Уместно будет вспомнить байку про goto
Стадии профессионального развития разработчика.
1. следует правилу от более опытных коллег "никогда не используй goto"
2. иногда, если нужно, можно использовать goto
3. наставляет молодых коллег словами "никогда не используй goto"
Конкретно по примерам. S>
S>POST orders/1/confirm
S>
Я бы сделал (в порядке убывания вероятности):
1.
PUT orders/1/status
body
{
"status": "confirmed"
}
2.
PUT orders/1/confirm
3.
GET orders/1/changeStatus?status=confirmed
S>
S>POST notifications/1/resend
S>
Я бы сделал одним из следующих вариантов (как конкретно — зависит от контекста и способов применения вызовов):
1.
POST notifications/clone
body
{
"fromId": 1
}
2.
POST notifications
body
{
тут вручную созданный клон нотификации с ID=1
}
3.
POST notifications/1/operations
body
{
"operation": "resend"
}
Опять же, самое главное: чтобы была прописана договоренность бэка и фронта, как максимально удобно и тем и другим, и второе — придерживаться консистентности в рамках одного АПИ.
В случае, когда делаем публичный АПИ, который идет на продажу, либо им будут пользоваться тысячи клиентов опен-осерса, то имеет смысл потратить больше времени на обдумывания и приведения к "более каноничному" стилю.
vsb>Не путаю. Я поэтому и написал "обычно". Да, можно спроектировать API, в котором PATCH не-идемпотентен (я привёл такой пример в своём сообщении). Хотя лично я считаю, что это ошибка и такие API проектировать не надо. PUT и PATCH нужно делать идемпотентными, а все не-идемпотентные операции переложить на POST и там можно даже не притворяться, что у нас REST, а просто писать, как удобно, всё равно там никаких гарантий. Но в идеале таких операций быть не должно.
S>>Подтвердить заказ, который только что отменили — должно вызвать ошибку.
vsb>Это можно о любой операции сказать. Идемпотентность не означает, что эта операция всегда срабатывает. PUT тоже не будет работать, если это нарушает какие-то условия уникальности, к примеру.
PUT должен отработать всегда — какая уникальность? Если сущность с таким ключом существует — то просто заменяете ее и все. Если нельзя заменить — то не обманывайте не притворяйтесь PUT-ом.
S>>Ключ идемпотентности можно применить и к POST. Вкусовщина. vsb>Можно, но принято эту семантику накладывать на PUT.
Это не мои попытки — это рекомендации многих уважаемых авторитетов. Ссылку вам дал.
Если авторитеты говорят — значит это всем должно быть понятно и проблем не вызовет. Когда городишь отсебятину — тогда да, сложно будет понять что ты там нагенерил в своей голове.
Здравствуйте, DiPaolo, Вы писали:
DP>это обычно обходится быстрее и дешевле, т.к. меньше времени уходит на буквоедство, а фичи быстрее появляются в продакшене; сам проходил через это, когда обсуждали очень долго как лучше назвать, какой глагол (GET/PUT/PATCH) использовать, как правильно/не правильно, вместо того, чтобы забить на это и сделать так, как НАМ удобно
Это да, но кроме создания проекта — вы нарабатываете хороший стиль кодирования. Т.е. привыкаете сразу либо делать правильно, либо делать не правильно. В будущем вы всегда будете делать как привыкли и тратить время одинаково либо на понятный стиль либо на непонятный никому кроме вас — просто как привыкните. До пенсии лет 25 еще есть, по этому раз 10 придется еще писать API — есть смысл наработать стиль. А может еще и на пенсии что-то придется писать. Смысл есть.
DP>Я бы сделал (в порядке убывания вероятности):
DP>1. DP>
Почему не PATCH?
DP>Опять же, самое главное: чтобы была прописана договоренность бэка и фронта, как максимально удобно и тем и другим, и второе — придерживаться консистентности в рамках одного АПИ.
Обычно фронт говорят — как сделаете так и будем юзать, решайте сами. По этому и хотелось бы по стандартам. И вообще выработать стиль на будущее.
DP>В случае, когда делаем публичный АПИ, который идет на продажу, либо им будут пользоваться тысячи клиентов опен-осерса, то имеет смысл потратить больше времени на обдумывания и приведения к "более каноничному" стилю.
Будешь меньше тратить времени, если сейчас заморочишься с этими вопросами.
Здравствуйте, Shmj, Вы писали:
S>PUT должен отработать всегда — какая уникальность? Если сущность с таким ключом существует — то просто заменяете ее и все. Если нельзя заменить — то не обманывайте не притворяйтесь PUT-ом.
Про то, что PUT должен отработать всегда — я в первый раз слышу. PUT /resource1/{resource1Id}/resource2/{resource2Id} тоже должен отработать всегда, создавая resource1 если он был удалён?
В общем не согласен. Всегда есть правила валидации и они могут изменяться во времени.
Здравствуйте, vsb, Вы писали:
vsb>В общем не согласен. Всегда есть правила валидации и они могут изменяться во времени.
Как вы считаете, почему PUT гарантированно идемпотентен а PATCH — нет?
Выше вы писали:
vsb>Это можно о любой операции сказать. Идемпотентность не означает, что эта операция всегда срабатывает. PUT тоже не будет работать, если это нарушает какие-то условия уникальности, к примеру.
Т.е. имели ли вы в виду, что первый запрос PUT — сработал, т.к. записи с таким ID не было. А второй PUT — уже не сработал, т.к. запись с таким ID уже есть в системе? Или о какой уникальности речь?
S>Т.е. привыкаете сразу либо делать правильно, либо делать не правильно.
В РЕСТе нет такого: правильно/неправильно. Есть рекомендации.
S>В будущем вы всегда будете делать как привыкли и тратить время одинаково либо на понятный стиль либо на непонятный никому кроме вас — просто как привыкните.
Ну я ж специально по этому поводу два раза подчеркнул: важно прописать стиль и придерживаться его.
S>До пенсии лет 25 еще есть, по этому раз 10 придется еще писать API — есть смысл наработать стиль. А может еще и на пенсии что-то придется писать. Смысл есть.
Ну вот все что я вам написал выше — оно уже основано на многолетнем опыте написания АПИ, и не только РЕСТа.
S>Почему не PATCH?
Меньше мороки. Фронтам скорее всего проще будет использовать.
S>Обычно фронт говорят — как сделаете так и будем юзать, решайте сами. По этому и хотелось бы по стандартам. И вообще выработать стиль на будущее.
Обычно, но не всегда. Команды бывают разные. Еще раз — РЕСТ — это НЕ стандарт.
S>Будешь меньше тратить времени, если сейчас заморочишься с этими вопросами.
Да какими вопросами? Вы из мухи слона делаете. Каким бы важным вам не казалось то, о чем вы сейчас спрашиваете, это — мелочь в масштабах продукта. И вот с опытом как раз приходит понимание, что важно и на что имеет смысл тратить больше времени, а что неважно.
Сразу хочется вспомнить "вам шашечки или ехать?". Программистам свойственен перфекционизм, а многие любят программировать ради программирования. И если их не ограничивать, они могут месяцами "вырабатывать стиль", переписывать с более лучшим дизайном, до посинения вылизывать АПИ. Продукт при этом будет стоять на месте.
Здравствуйте, Shmj, Вы писали:
vsb>>В общем не согласен. Всегда есть правила валидации и они могут изменяться во времени.
S>Как вы считаете, почему PUT гарантированно идемпотентен а PATCH — нет?
Потому, что те, кто придумывали эти ассоциации между методами и идемпотентностью, так решили. На мой взгляд это решение было неправильным и я в своём коде это ужесточаю до требований 100% идемпотентности в PATCH.
vsb>>Это можно о любой операции сказать. Идемпотентность не означает, что эта операция всегда срабатывает. PUT тоже не будет работать, если это нарушает какие-то условия уникальности, к примеру.
S>Т.е. имели ли вы в виду, что первый запрос PUT — сработал, т.к. записи с таким ID не было. А второй PUT — уже не сработал, т.к. запись с таким ID уже есть в системе? Или о какой уникальности речь?
Я имею в виду, что первый PUT может не сработать, если это нарушает какие-то ограничения по бизнес-логике, к примеру. Если первый PUT сработал, то второй обычно должен сработать. Но при этом могут быть ситуации, когда первый сработал, а второй не сработает. К примеру такая последовательность:
PUT /users/1/message/2
DELETE /users/1
PUT /users/1/message/2
В таком примере после удаления пользователя второй PUT с теми же параметрами что и первый уже не сработает, в этом нет смысла.
Может и второй PUT не сработать, если параметры отличаются. Хотя стандарты и говорят, что PUT заменяет данные, но это же не значит, что я обязан менять бизнес-логику под эти стандарты. К примеру у меня поле не должно меняться после создания. В этом случае второй PUT вернёт ошибку, если поля в базе отличаются от тех, что он пытается изменить. На самом деле я обычно так и делаю — PUT два раза подряд сработает только если параметры одинаковые. Не думаю, что это нарушает REST. А для редактирования уже PATCH.
Здравствуйте, vsb, Вы писали:
vsb>На самом деле я обычно так и делаю — PUT два раза подряд сработает только если параметры одинаковые.
Хорошо, очень интересно.
Такой вариант. PUT 2 раза подряд, параметры 100% одинаковые. Между двумя запросами никаких DELETE и пр. не было, т.е. все чисто. Но в PUT указано состояние Created. Однако когда пришел второй PUT — система в фоновом режиме начала обработку операции и изменила состояние на Processing, что значит заказ клиента начал исполняться и вернуть все в начальное состояние Created, как того требует второй PUT — никоим образом не представляется возможным. Что делать? Просто проигнорите и вернете 200, как бы подразумевая, что клиент использует PUT в качестве создания записи, а раз запись уже создана и хотя состояние изменилось — то все ОК. Или же вернете какую-то ошибку?
Здравствуйте, Shmj, Вы писали:
S>Такой вариант. PUT 2 раза подряд, параметры 100% одинаковые. Между двумя запросами никаких DELETE и пр. не было, т.е. все чисто. Но в PUT указано состояние Created. Однако когда пришел второй PUT — система в фоновом режиме начала обработку операции и изменила состояние на Processing, что значит заказ клиента начал исполняться и вернуть все в начальное состояние Created, как того требует второй PUT — никоим образом не представляется возможным. Что делать? Просто проигнорите и вернете 200, как бы подразумевая, что клиент использует PUT в качестве создания записи, а раз запись уже создана и хотя состояние изменилось — то все ОК. Или же вернете какую-то ошибку?
Я думаю, в данной ситуации надо возвращать 200. Смысл же в том, чтобы два подряд посланных PUT-а трактовались, как один. Я тут нарушения не вижу.
Кстати у гугла часто логика перехода состояний и тд выносится в Client API. То бишь в данной ситуации сервер сам не начинает никакие обработки, а только по запросу клиента, а в Client API реализована часть логики сервера (эдакая двухзвенка, если можно так выразиться). Минус в том, что без SDK толком ничего не сделать. Мне этот подход кажется сомнительным, но я особо его не продумывал. Может быть в нём и есть смысл.
Здравствуйте, vsb, Вы писали:
vsb>Я думаю, в данной ситуации надо возвращать 200. Смысл же в том, чтобы два подряд посланных PUT-а трактовались, как один. Я тут нарушения не вижу.
Однако же пользователи вашего API могут трактовать PUT не как запрос на создание сущности (вы так трактуете) — а в классическом понимании — как запрос на изменение. И могут поверить вам, что состояние было изменено в соответствие с тем, которое было прислано во втором PUT-запросе. Ведь ошибки не было, значит все ОК.
vsb>Кстати у гугла часто логика перехода состояний и тд выносится в Client API. То бишь в данной ситуации сервер сам не начинает никакие обработки, а только по запросу клиента, а в Client API реализована часть логики сервера (эдакая двухзвенка, если можно так выразиться). Минус в том, что без SDK толком ничего не сделать. Мне этот подход кажется сомнительным, но я особо его не продумывал. Может быть в нём и есть смысл.
Ну т.е. если бы клиент явно отправил запрос PATCH, в котором бы потребовал изменить состояние — то второй запрос PUT — уже должен бы привести к ошибке 409 Conflict?
Быть может правильнее для создания использовать POST, если нужно с ключом идемпотентности? Тогда все становится на свои места. И при попытке повторной отправки с тем же ключом — выдавать ошибку. А PUT использовать только для случаев, когда нет состояния и когда полная замена уместна вне зависимости от состояния.
Здравствуйте, Shmj, Вы писали:
S>Ну т.е. если бы клиент явно отправил запрос PATCH, в котором бы потребовал изменить состояние — то второй запрос PUT — уже должен бы привести к ошибке 409 Conflict?
Думаю, да. Он же должен в определённой последовательности посылать запросы. И если второй запрос уже ушёл, то первый повторять как-то странно. Если двое пытаются одно и то же делать, тут тоже какая-то фигня получается. В общем мне видится, что в такой ситуации ошибка это правильно, ибо тут или в клиенте баг или два клиента пытаются делать одно и то же, в обоих случаях продолжать это безобразие не нужно.
Здравствуйте, vsb, Вы писали:
vsb>Думаю, да. Он же должен в определённой последовательности посылать запросы. И если второй запрос уже ушёл, то первый повторять как-то странно. Если двое пытаются одно и то же делать, тут тоже какая-то фигня получается. В общем мне видится, что в такой ситуации ошибка это правильно, ибо тут или в клиенте баг или два клиента пытаются делать одно и то же, в обоих случаях продолжать это безобразие не нужно.
Быть может правильнее для создания использовать POST, если нужно с ключом идемпотентности? Тогда все становится на свои места. И при попытке повторной отправки с тем же ключом — выдавать ошибку. А PUT использовать только для случаев, когда нет состояния и когда полная замена уместна вне зависимости от состояния.
Тогда POST будет идемпотентным за счет ключа идемпотентности. PUT будет по-настоящему идемпотентным, как того требует спецификация — без всяких неожиданностей поведения. Но не всегда PUT уместен — если есть состояние — то метод PUT добавлять нельзя, не нужно притворяться. Ну и PATCH будет не идемпотентным, как то и определено спецификацией — если состояние не позволяет — будет возникать ошибка, т.е. зависит от очередности вызовов. PUT от очередности вызовов не зависит — в этом и разница.
Здравствуйте, DiPaolo, Вы писали:
DP> RESTFul API — это не стандарт, не спецификация, т.е. никто не накладывает жестких ограничений
Тем не менее это набор принципов, которые появились не на пустом месте, и которые без серьезного обоснования лучше не нарушать.
DP> лучше всего делать так, как удобно вам, т.к. в подавляющем большинстве случаев речь идет об АПИ в рамках одной компании, т.е. для связи бэка и фронта/мобилок
Нет, лучше всего делать так, как удобно потребителю этого API, даже если он внутри компании. В остальном — см. предыдущую фразу.
DP> это обычно обходится быстрее и дешевле
Херак херак и в продакшен?
DP>, т.к. меньше времени уходит на буквоедство,
Да. Либо, если статус меняется только независимо, лучше будет PUT orders/1/status
S>Но здесь минус — теряется ясность мысли.
Не вижу тут никакой потери ясности. Суть запрета глаголов не в каноничности, а в том чтобы заставить рассматривать работу через REST API исключительно как работу с ресурсами. Что, в свою очередь, позволяет этим семантическим приемом избежать значительного количества граблей при разработке сильно распределенных систем. Поэтому "ясность мысли" в REST варианте наоборот приобретается.
S>Как вы на все это смотрите?
Строго придерживайся REST API guidelines пока не будет ясного понимания зачем и почему оно так, выйдет дешевле.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Не вижу тут никакой потери ясности.
В случае с глаголом — не нужны лишние сущности в виде состояния, которое может быть только одно — confirmed.
НС>Строго придерживайся REST API guidelines пока не будет ясного понимания зачем и почему оно так, выйдет дешевле.
Это компания с капитализацией 75 млрд. и которая, по сути, на этом API и зарабатывает. Думаете не хватило денег на хороших спецов или, все-же, такой вариант удобнее?
Хорошо, давайте пример реального API, где есть операция подтверждения заказа. Любой другой компании. Давайте посмотрим как делает высшая лига.
Здравствуйте, Shmj, Вы писали:
НС>>Не вижу тут никакой потери ясности. S>В случае с глаголом — не нужны лишние сущности в виде состояния
Наоборот, в случае с состоянием не нужны лишние сущности в виде глагола.
S>, которое может быть только одно — confirmed.
Нет, еще может быть not confirmed.
S>Для примера посмотрите глобальных лидеров
Не самая удачная стратегия. Многие API у лидеров проектировались тогда, когда REST еще не существовал или был в зачаточном состоянии. Так что есть шанс поиметь каку.
Не боги горшки обжигают, даже у лидеров.
S>Хорошо, давайте пример реального API, где есть операция подтверждения заказа. Любой другой компании. Давайте посмотрим как делает высшая лига.
Здравствуйте, Ночной Смотрящий, Вы писали:
S>>, которое может быть только одно — confirmed. НС>Нет, еще может быть not confirmed.
Нет, не может — только подтвердить клиент может и ничего другого. Отменить подтверждение — нельзя.
S>>Хорошо, давайте пример реального API, где есть операция подтверждения заказа. Любой другой компании. Давайте посмотрим как делает высшая лига.
НС>Гугль для тебя достаточно авторитетен? https://developers.google.com/shopping-content/reference/rest/v2.1/orders/acknowledge
Здравствуйте, Shmj, Вы писали:
S>>>, которое может быть только одно — confirmed. НС>>Нет, еще может быть not confirmed. S>Нет, не может — только подтвердить клиент может и ничего другого. Отменить подтверждение — нельзя.
Ты просил пример, зачем ты вытаскиваешь другие кейсы? Хочешь показать что дизайн реста косячат даже в крупных корпорациях? Так никто в том и не сомневается.
Кроме того, конкретно cancel это и существительное тоже, короткая форма cancellation.
S>Как видите — часто удобно сделать глаголом, чтобы не нарушалась ясность мысли.
Ты как определяешь что "ясность мысли" нарушилась? Есть какой то объективный критерий, или это твоя вкусовщина?