Комментарии 15
У вас реализация неправильная. Если два запроса придут практически одновременно, то каждый вызовет _idempotencyRecordProvider.Get
, ничего не получит и пойдёт обрабатывать запрос. Как минимум там надо создать пустую запись, которая показывает, что другой запрос уже начал обрабатываться, но ещё не закончил.
Тоже обратил на это внимание. По-хорошему там должна быть транзакция, в рамках которой происходит поиск по idempotencyKey
и в случае отсутствия, вставка записи с этим идентификатором (естественно, также должен быть unique constraint в БД). В этом случае гонка уже не страшна, поскольку проверка и добавления записи с idempotencyKey
атомарно.
форма на клиенте могла бы генерировать некоторый ключ на этапе заполнения и затем использовать его в запросе сохранения заказа
без модификации логики приложения
Вижу противоречие.
Сама идея генерировать токены для запросов не нова (см xsrf токены), но, кажется, в случае с дубликатами проблему нужно решать на стороне клиента (почему клиент успевает N раз нажать на кнопку прежде чем она станет disabled?).
А что если клиент хотел именно дубликаты, а вы лишили его этой возможности?
Приложение должно вежливо уточнить хочет ли он сделать дубликат (что-то в духе "Мы уже обрабатываем заказ с таким составом, добавить выбранные продукты к текущему, или оформить отдельный?").
Вот это вот "запрос клиента может не выполниться, если сервер посчитает его дубликатом, и никто не будет считать это странным поведением" — попахивает уже by design, ведь по сути вы закэшировали ответ на POST запрос к API (которое, скорее всего, REST).
почему клиент успевает N раз нажать на кнопку прежде чем она станет disabled?
Например мышка у клиента с дребезгом контакта на левой кнопке. Щелкает он один раз, а браузер получает 2-5 кликов за 1 милисекунду. Javascript просто не успевает дернуть обработчик первого клика чтобы отметить кнопку как disabled.
Это реальный кейс.
как человек с таким реальным кейсом в принципе компом пользуется? это ж не выделить объект никак - сразу только запуск, и кейсов где это доставит мягко говоря неудобства - масса.
не кажется, что это та проблема, которую стоит решать на уровне костылей
При увеличении количества клиентов где-то до 50k вероятность столкнуться с таким поведением оборудования стремится к 1. И вот у вас в базе два заказа с разницей во времени в 1 мс.А что если клиент хотел именно дубликаты, а вы лишили его этой возможности?Приложение должно вежливо уточнить хочет ли он сделать дубликат (что-то в духе "Мы уже обрабатываем заказ с таким составом, добавить выбранные продукты к текущему, или оформить отдельный?").
Для реализации этого поведения понадобится сделать в общих чертах то, что описано в статье. Ключик завести уникальный, проверять его на сервере, и т.д.
Я не говорю, что в статье всё гладко и там всё сделано идеально, но общий смысл таков, что идемпотентность приходится делать.
Смотри шире, это же не только про форму и пользователя. В довольно стандартной интеграции микросервисов через transaction outbox одна из основных вещей - воспроизводимость и идемпотентность.
If-None-Match
же.
Зачем чинить то что не ломалось?
Тут в самом начале PUT описан как "изменить", хотя он "поместить", а для "изменить" есть PATCH.
А по тексту по сути PUT /<guid> {body} превратили в POST / {guid, body} - ну такое
Да, можно и через PUT, но с его использованием возможна накладка с ID ресурса.
a) PUT/id = set (if doesn’t exist then throw), POST + idempotency key = create if doesn’t exist
b) PUT/id = if doesn’t exist then create else set. ID ресурса будет генерироваться клиентом. И тогда для изоляции клиентов понадобится уникальность id в рамках клиента (чтобы клиент вовсе не знал о других клиентах), либо требовать от клиентов глобальной уникальности используемых id (как с username на некоторых сайтах).
Весь пост ждал, когда дойдёт до описания механизма создания idempotency key _на клиенте_, но как раз этого момента совсем нет. AFAIU, для этой проблемы бы сработал любой рандом id, генерящийся при открытии формы .
Борьба с дубликатами: делаем POST идемпотентным