События и очереди

2 мин чтения ·

Exactly-Once: клиенту нужен один платёж, а не обещание брокера

Ответ от платёжного сервиса потерялся. Повторить запрос или ждать? Разбираем «ровно один раз» как бизнес-требование, а не волшебную настройку.

Платёжный сервис списал деньги, но ответ потерялся в сети. Приложение видит таймаут. Повторить запрос — риск второго списания, не повторять — риск оставить заказ без подтверждения. Требование «выполнить ровно один раз» выглядит разумно до тех пор, пока не пытаешься определить, что именно считается выполнением.

Exactly-Once обычно обсуждают как гарантию транспорта. Но бизнесу нужна не уникальная доставка байтов, а отсутствие повторного результата: одна оплата, одна бронь, одна отправка товара.

Сначала определим границу гарантии

Некоторые платформы обеспечивают транзакционную обработку внутри контролируемого контура: чтение сообщений, обновление состояния, публикацию результатов. Это полезная гарантия, но она не распространяется автоматически на банк, склад или другую внешнюю систему.

Если сообщение было обработано, а подтверждение брокеру не дошло, доставку могут повторить. Поэтому нужно проектировать корректное поведение при дубликате, даже если отдельный компонент заявляет exactly-once в своей области.

Идемпотентность отвечает за бизнес-эффект

Для оплаты можно связать повторные попытки с одним устойчивым идентификатором операции. Сервис или платёжный провайдер, поддерживающий такую семантику, распознает повтор и вернёт результат первой обработки либо состояние исходной операции вместо нового списания.

Важно, чтобы проверка повторов и изменение состояния были согласованы. Сначала проверить «не было ли такого платежа», потом отдельно списать — недостаточно, если две попытки придут одновременно. Нужна транзакционная защита, уникальный ключ или другая корректная синхронизация.

Отправка письма, создание задачи на складе и изменение баланса требуют разных способов избежать повторного эффекта. Универсальной кнопки нет.

Неизвестный результат хуже явной ошибки

Таймаут не доказывает, что операция не произошла. Поэтому после неопределённого ответа полезнее запросить состояние по идентификатору операции, чем слепо повторять новую команду.

Повторную доставку сообщения можно обрабатывать через Transactional Inbox, уникальные бизнес-ключи и идемпотентные обработчики. Проблему безопасной публикации из базы решает другая сторона — Outbox. Эти паттерны дополняют друг друга.

За что действительно стоит платить

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

В документации и на архитектурных схемах слово exactly-once должно сопровождаться ответом: «ровно один раз что, где и при каких отказах?» Без этого надёжность может оказаться рекламным обещанием.

Клиенту не важно, сколько раз брокер доставил сообщение. Ему важно, чтобы за один заказ с него взяли деньги один раз.