Идемпотентность POST - хорошая ли практика?
От: Shmj Ниоткуда  
Дата: 20.09.22 04:26
Оценка:
См. статью от великого Яндекса: https://habr.com/ru/company/yandex/blog/442762/

Понятно что под стажером описан опыт целой команды (никто бы не доверил стажеру проектировать архитектуру), но чтобы набить себе цену и победить в эволюционной борьбе, чтобы никто не посмел потом сказать какие же вы идиоты — придумали концепцию стажера. Ну да ладно, отвлеклись. Суть вот в чем:

Вот решение "стажера" по идемпотентности:

POST /v1/orders

{
  "from": "Москва, ул. Садовническая набережная 82с2",
  "to": "Аэропорт Внуково",
  "idempotency_key": "786706b8-ed80-443a-80f6-ea1fa8cc1b51"
}


Т.е. этот "стажер" (как позже признались в комментах — это опыт выведен из нескольких проектов) — сделал запрос POST идемпотентным. Но как мы знаем — POST не идемпотентен, т.к. еще не знает ключа ресурса — ключ только генерится.

Не логичнее ли вместо idempotency_key использовать ID и сделать этот ID типа UUID v4? Ведь по сути именно это хотел изобразить стажер:


PUT /v1/orders/786706b8-ed80-443a-80f6-ea1fa8cc1b51

{
  "id": "786706b8-ed80-443a-80f6-ea1fa8cc1b51",
  "from": "Москва, ул. Садовническая набережная 82с2",
  "to": "Аэропорт Внуково"
}


Какие минусы в этом?

Минус очевидный — это диктует тип ключей — только UUID. А если хочется чтобы ключи генерила СУБД и там спец. система для распределенной генерации ключей? Т.е. такой PUT как бы обязывает применять новую схему создания ключей.

И если уж так подойти — то запросы POST вообще лишены смысла — ведь они не идемпотентны, а это плохо всегда, т.к. если можно сделать идемпотентным (а это можно и нужно всегда — интернет по определению не надежен) — то нужно делать.
Отредактировано 20.09.2022 4:28 Shmj . Предыдущая версия . Еще …
Отредактировано 20.09.2022 4:27 Shmj . Предыдущая версия .
Re: Идемпотентность POST - хорошая ли практика?
От: vsb Казахстан  
Дата: 20.09.22 05:19
Оценка: 1 (1)
Здравствуйте, Shmj, Вы писали:

S>Т.е. этот "стажер" (как позже признались в комментах — это опыт выведен из нескольких проектов) — сделал запрос POST идемпотентным. Но как мы знаем — POST не идемпотентен, т.к. еще не знает ключа ресурса — ключ только генерится.


POST не обязан быть идемпотентным, но никто не мешает сделать его идемпотентным.

S>Не логичнее ли вместо idempotency_key использовать ID и сделать этот ID типа UUID v4? Ведь по сути именно это хотел изобразить стажер:



S>
S>PUT /v1/orders/786706b8-ed80-443a-80f6-ea1fa8cc1b51

S>{
S>  "id": "786706b8-ed80-443a-80f6-ea1fa8cc1b51",
S>  "from": "Москва, ул. Садовническая набережная 82с2",
S>  "to": "Аэропорт Внуково"
S>}
S>


S>Какие минусы в этом?


Я бы так и сделал.

S>Минус очевидный — это диктует тип ключей — только UUID. А если хочется чтобы ключи генерила СУБД и там спец. система для распределенной генерации ключей? Т.е. такой PUT как бы обязывает применять новую схему создания ключей.


Ну вот я недавно думал над этим и решил, что ключи должны быть только UUID. Минусов у UUID почти нет, а плюсов много.

S>И если уж так подойти — то запросы POST вообще лишены смысла — ведь они не идемпотентны, а это плохо всегда, т.к. если можно сделать идемпотентным (а это можно и нужно всегда — интернет по определению не надежен) — то нужно делать.


Ну в идеале — согласен. На практике не всегда получается это выполнять. Ограничения архитектуры или старого кода могут играть роль. К примеру в браузере нельзя послать PUT на другой домен без разрешений CORS, а POST с определёнными ограничениям — можно. Ну это просто пример.
Re: Идемпотентность POST - хорошая ли практика?
От: DiPaolo Россия  
Дата: 20.09.22 05:21
Оценка: +5
Кажется у вас небольшая каша в голове по этому вопросу. Вы ж совсем недавно задавали подобный вопрос про идемпотентность и защиту от дублирования http://rsdn.org/forum/dotnet/8348759.1
Автор: Shmj
Дата: 31.08.22
.

ID и idempotency_key — это совершенно разные вещи, и служат для разных целей. idempotency_key позволяет исключить повторное создание нового айтема с новым ID, а ID айтема служит для идентификации айтема при работе с ним.



PS лучше было бы такие темы писать в другой раздел. Тут ничего нет имеющего непосредственно отношение к точке нету.
Патриот здравого смысла
Re[2]: Идемпотентность POST - хорошая ли практика?
От: Shmj Ниоткуда  
Дата: 20.09.22 05:39
Оценка:
Здравствуйте, DiPaolo, Вы писали:

DP>ID и idempotency_key — это совершенно разные вещи, и служат для разных целей. idempotency_key позволяет исключить повторное создание нового айтема с новым ID, а ID айтема служит для идентификации айтема при работе с ним.


Однако же если в качестве ID применить UUID и генерить на клиенте — то проблема решается сама собой. Вот чел. так и делает: https://rsdn.org/forum/dotnet/8364034.1
Автор: vsb
Дата: 20.09.22


DP>PS лучше было бы такие темы писать в другой раздел. Тут ничего нет имеющего непосредственно отношение к точке нету.


По сути все сидят на этом форуме — 90% людей.
Re: Идемпотентность POST - хорошая ли практика?
От: scf  
Дата: 20.09.22 05:53
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Вот решение "стажера" по идемпотентности:


Не ту проблему решали: если нужно, чтобы в базе не было одинаковых заказов, достаточно на стороне сервера обеспечить, чтобы в базе не было одинаковых заказов.
Re[2]: Идемпотентность POST - хорошая ли практика?
От: Shmj Ниоткуда  
Дата: 20.09.22 06:01
Оценка: +2
Здравствуйте, scf, Вы писали:

S>>Вот решение "стажера" по идемпотентности:


scf>Не ту проблему решали: если нужно, чтобы в базе не было одинаковых заказов, достаточно на стороне сервера обеспечить, чтобы в базе не было одинаковых заказов.


Прочитайте статью. Как раз в том и фишка — одинаковые заказы могут быть. Как так? А вот так — приехала компания из 7 чел., в 1 машину влазит 4 чел. Сделали сначала 1 заказ а потом сразу другой — так корректно. Главное отличать когда это по воле клиента а когда по сетевой ошибке.
Re[3]: Идемпотентность POST - хорошая ли практика?
От: karbofos42 Россия  
Дата: 20.09.22 06:18
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Прочитайте статью. Как раз в том и фишка — одинаковые заказы могут быть. Как так? А вот так — приехала компания из 7 чел., в 1 машину влазит 4 чел. Сделали сначала 1 заказ а потом сразу другой — так корректно. Главное отличать когда это по воле клиента а когда по сетевой ошибке.


Я прочитал в самом начале статьи следующее:

Когда надо было сделать API для отдачи активных заказов, Вася задумался: а может ли понадобиться заказывать одновременно несколько машин такси? Менеджеры ответили, что нет, такая возможность не нужна.

Re: Идемпотентность POST - хорошая ли практика?
От: karbofos42 Россия  
Дата: 20.09.22 06:25
Оценка:
Здравствуйте, Shmj, Вы писали:

S>И если уж так подойти — то запросы POST вообще лишены смысла — ведь они не идемпотентны, а это плохо всегда, т.к. если можно сделать идемпотентным (а это можно и нужно всегда — интернет по определению не надежен) — то нужно делать.


Допустим, у нас есть запрос PUT/PATCH для изменения пароля пользователя.
В системе настроено правило: при неправильно введённом текущем пароле 3 раза — учётка блокируется.
У клиента очень плохой интернет, он решает поменять пароль.
В итоге запрос даже не задваивается, а улетает 4 копии.
Первая отрабатывает и меняет пароль. Остальные приходят с уже неправильным старым паролем.
Итого система видит, что какой-то негодяй пытается подобрать пароль и блокирует учётку.
Этот запрос не POST, но является ли он идемпотентным?
Re[4]: Идемпотентность POST - хорошая ли практика?
От: Shmj Ниоткуда  
Дата: 20.09.22 06:31
Оценка:
Здравствуйте, karbofos42, Вы писали:

K>Я прочитал в самом начале статьи следующее:

K>

K>Когда надо было сделать API для отдачи активных заказов, Вася задумался: а может ли понадобиться заказывать одновременно несколько машин такси? Менеджеры ответили, что нет, такая возможность не нужна.

K>

Так читайте дальше:

Вася удивлен: как же так, я же спрашивал, и вы говорили мне, что это не понадобится?!


Мы не боги — никто из нас не обладает полнотой картины реальности, хотя часто думаем что обладаем и что-то там можем прогнозировать. Просто принять как факт — никто не знает что понадобится а что нет — делать максимально гибко.
Re[5]: Идемпотентность POST - хорошая ли практика?
От: karbofos42 Россия  
Дата: 20.09.22 06:41
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Так читайте дальше:


Действительно. Пропустил, когда эту занимательную выдуманную историю по диагонали пробегал.
Re[6]: Идемпотентность POST - хорошая ли практика?
От: Shmj Ниоткуда  
Дата: 20.09.22 07:06
Оценка:
Здравствуйте, karbofos42, Вы писали:

S>>Так читайте дальше:

K>Действительно. Пропустил, когда эту занимательную выдуманную историю по диагонали пробегал.

Оно можно сказать — аналитики плохие, не предугадали, не виноватая я и т.д. Но суть то не в том кто виноват — важно сделать максимально гибко. И хорошие принципы это позволяют.
Re[7]: Идемпотентность POST - хорошая ли практика?
От: karbofos42 Россия  
Дата: 20.09.22 07:59
Оценка: 5 (1) +1
Здравствуйте, Shmj, Вы писали:

S>Оно можно сказать — аналитики плохие, не предугадали, не виноватая я и т.д. Но суть то не в том кто виноват — важно сделать максимально гибко. И хорошие принципы это позволяют.


Сделали гибко, в итоге теперь у обычных клиентов не проходят лишние случайные заказы. Они счастливы.
Зато мамкин кулхацкер посмотрел на отправляемые запросы, сгенерировал своих дубликатов с уникальными UUID и заказал на адрес тысячу машин за наличку.
Яндекс будет рад такой ситуации или не нужно клиенту таки слепо доверять и на сервере как-то нужно следить за входными данными?
Re[2]: Идемпотентность POST - хорошая ли практика?
От: fmiracle  
Дата: 20.09.22 08:18
Оценка: +2
Здравствуйте, karbofos42, Вы писали:

K>Допустим, у нас есть запрос PUT/PATCH для изменения пароля пользователя.

K>В системе настроено правило: при неправильно введённом текущем пароле 3 раза — учётка блокируется.
K>У клиента очень плохой интернет, он решает поменять пароль.
K>В итоге запрос даже не задваивается, а улетает 4 копии.
K>Первая отрабатывает и меняет пароль. Остальные приходят с уже неправильным старым паролем.
K>Итого система видит, что какой-то негодяй пытается подобрать пароль и блокирует учётку.
K>Этот запрос не POST, но является ли он идемпотентным?

В данном случае, его вполне можно сделать идемпотентным добавлением ключа идемпотентности и данный сценарий от этого только улучшится:

1. первый запрос сменит пароль, сохранит результат (ок или ошибка) в специальную таблицу с этим полученным ключом (это может быть постоянная таблица типа истории изменений пароля или временная с периодической очисткой — только для обеспечения идемпотености запросов)
2. каждый следующий запрос посмотрит наличие ключа в этой таблице и сразу вернет "ок" (ну или ошибка, если все же первый запрос был уже неудачный), даже не пытаясь собственно менять пароль.
3. Поскольку попытки смены пароля для этих последующих запросов реально не проводилось, то и учетку не блокируем.
Re[3]: Идемпотентность POST - хорошая ли практика?
От: karbofos42 Россия  
Дата: 20.09.22 09:04
Оценка:
Здравствуйте, fmiracle, Вы писали:

F>В данном случае, его вполне можно сделать идемпотентным добавлением ключа идемпотентности и данный сценарий от этого только улучшится:


И в итоге чуть ли не каждый запрос на редактирование нужно делать идемпотентным, чтобы у пользователя не возникало фантомных ошибок и запрос действительно одно и то же возвращал.
Почему-то во многих статьях пишут именно про POST, подразумевая то, что вот этот запрос создаст новую запись и с ним очень легко захламить сервер мусором.
Плевать, что повторный PUT или DELETE скорее всего вернёт уже не статус 200, а какую-нибудь ошибку.
Я вот как-то разницу между запросами не особо вижу в плане "идемпотентности".
GET разве что особняком стоит, т.к. он на чтение работает, а у остальных примерно одинаковые проблемы.
Re[8]: Идемпотентность POST - хорошая ли практика?
От: Shmj Ниоткуда  
Дата: 20.09.22 09:08
Оценка:
Здравствуйте, karbofos42, Вы писали:

K>Сделали гибко, в итоге теперь у обычных клиентов не проходят лишние случайные заказы. Они счастливы.

K>Зато мамкин кулхацкер посмотрел на отправляемые запросы, сгенерировал своих дубликатов с уникальными UUID и заказал на адрес тысячу машин за наличку.
K>Яндекс будет рад такой ситуации или не нужно клиенту таки слепо доверять и на сервере как-то нужно следить за входными данными?

Это уже другой вопрос — вопрос безопасности. Даже если ты установишь лимит — 1 клиент 1 заказ — это не спасет от подобной ситуации, т.к. мамкин кулхацкер с легкостью создаст несколько учеток даже с разных IP-адресов и точно так вызовет 1000 машин, можно даже на разные адреса.
Re[2]: Идемпотентность POST - хорошая ли практика?
От: Shmj Ниоткуда  
Дата: 20.09.22 09:10
Оценка:
Здравствуйте, karbofos42, Вы писали:

K>Допустим, у нас есть запрос PUT/PATCH для изменения пароля пользователя.

K>В системе настроено правило: при неправильно введённом текущем пароле 3 раза — учётка блокируется.
K>У клиента очень плохой интернет, он решает поменять пароль.
K>В итоге запрос даже не задваивается, а улетает 4 копии.
K>Первая отрабатывает и меняет пароль. Остальные приходят с уже неправильным старым паролем.
K>Итого система видит, что какой-то негодяй пытается подобрать пароль и блокирует учётку.
K>Этот запрос не POST, но является ли он идемпотентным?

Выше вам хорошо ответили. Без ключа идемпотентности — да, запись была бы заблокирована. С ключом идемпотентности, а он один и тот же для всех 4 запросов — первый раз отработали, на все остальные сказали что все ОК.
Re: Идемпотентность POST - хорошая ли практика?
От: Ночной Смотрящий Россия  
Дата: 20.09.22 09:20
Оценка: 83 (2)
Здравствуйте, Shmj, Вы писали:

S>Но как мы знаем — POST не идемпотентен


Не совсем так. У него нет требования быть идемпотентным. Но обратного требования тоже нет.
Но с точки зрения понятности API, конечно, неидемпотентный метод лучше сделать PUT.
Еще один не самый лучший момент — явное вытаскивание этого idempotency_key в бизнес-слой. Разумнее было бы передавать в каком нибудь хидере типа X-Request-ID, так как это не бизнес-сущность, а специфика транспорта.

S>Не логичнее ли вместо idempotency_key использовать ID и сделать этот ID типа UUID v4?


Логичнее.

S>И если уж так подойти — то запросы POST вообще лишены смысла — ведь они не идемпотентны, а это плохо всегда, т.к. если можно сделать идемпотентным


А если нельзя?

S> (а это можно и нужно всегда — интернет по определению не надежен)


Иногда важнее получить fail сразу же и ретраи не делать.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[4]: Идемпотентность POST - хорошая ли практика?
От: Shmj Ниоткуда  
Дата: 20.09.22 09:21
Оценка: 5 (1)
Здравствуйте, karbofos42, Вы писали:

K>Почему-то во многих статьях пишут именно про POST, подразумевая то, что вот этот запрос создаст новую запись и с ним очень легко захламить сервер мусором.

K>Плевать, что повторный PUT или DELETE скорее всего вернёт уже не статус 200, а какую-нибудь ошибку.

DELETE должен вернуть 200, даже если запись была удалена ранеее. Именно по этому считаем идемпотентным.

Аналогично PUT — он полностью заменяет запись и будет всегда возвращать одно и тоже, не зависимо от очередности вызовов.

Только PATCH еще может быть не идемпотентным.

K>Я вот как-то разницу между запросами не особо вижу в плане "идемпотентности".


Вот табличка, уже приводил:

  Скрытый текст


K>GET разве что особняком стоит, т.к. он на чтение работает, а у остальных примерно одинаковые проблемы.


Не только, см. табличку.
Re[3]: Идемпотентность POST - хорошая ли практика?
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 20.09.22 09:22
Оценка: +2
Здравствуйте, Shmj, Вы писали:

DP>>PS лучше было бы такие темы писать в другой раздел. Тут ничего нет имеющего непосредственно отношение к точке нету.

S>По сути все сидят на этом форуме — 90% людей.

Это неприемлемая мотивация. Не надо постить в неподходящий форум потому что тут больше людей.
AVK Blog
Re[2]: Идемпотентность POST - хорошая ли практика?
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 20.09.22 09:23
Оценка: +1
Здравствуйте, Ночной Смотрящий, Вы писали:

НС>Не совсем так. У него нет требования быть идемпотентным. Но обратного требования тоже нет.

НС>Но с точки зрения понятности API, конечно, неидемпотентный метод лучше сделать PUT.
НС>Еще один не самый лучший момент — явное вытаскивание этого idempotency_key в бизнес-слой. Разумнее было бы передавать в каком нибудь хидере типа X-Request-ID, так как это не бизнес-сущность, а специфика транспорта.

А что это принципиально меняет? Все равно нам нужно по этому x-request-id понять, что делать с повторным запросом. И это уже не транспорт, а бизнес-логика.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.