Склад уже принял команду собрать заказ, но подтверждение обработки до брокера не дошло. Сообщение появилось снова. Это не обязательно ошибка отправителя: при надёжной доставке повтор может быть нормальным способом не потерять работу.
Transactional Inbox нужен на стороне получателя. Он помогает отличить первое выполнение действия от повторной доставки того же сообщения. Однако его надёжность зависит от того, где именно хранится отметка об обработке и какие действия выполняет потребитель.
Проверка сообщения должна жить рядом с результатом
Представим обработчик, который сначала проверяет идентификатор события, затем меняет заказ, а потом записывает «обработано». Если приложение падает после изменения заказа, но до отметки, повторная доставка выполнит действие ещё раз.
Поэтому сведения о принятом сообщении и изменение собственной базы стоит фиксировать в одной локальной транзакции. Тогда запись либо содержит и результат, и отметку об обработке, либо не содержит ни того ни другого. Уникальный ключ по идентификатору сообщения может помочь защититься и от одновременных повторов.
Идентификатор транспорта и идентификатор операции
Проблема сложнее, если одна и та же бизнес-команда повторно отправлена с новым идентификатором события. Inbox, который сравнивает только transport ID, не распознает, что на самом деле это всё та же операция.
Для критичных действий полезен отдельный бизнес-ключ: номер платёжной попытки, заказа или другой устойчивый идентификатор эффекта. В таком случае Inbox и идемпотентность бизнес-операции должны работать вместе.
А если получатель вызывает внешний сервис
Локальная транзакция базы не может автоматически включить внешний платёжный API или службу доставки. Если потребитель сохранил отметку «выполнено», но внешний вызов не произошёл, обработка будет пропущена при повторе. Если сначала произошёл внешний вызов, а потом упала база, возможен дубль.
Здесь нужны дополнительные решения: idempotency key у внешнего провайдера, отдельная очередь исходящих работ, фиксация промежуточных состояний или компенсация. Inbox не превращает распределённый мир в одну транзакцию.
Сколько хранить память о дублях
Если записи об обработанных событиях удаляются слишком рано, старое сообщение при восстановлении потока может выглядеть как новое. Если хранить их вечно, таблица может расти без необходимости. Срок хранения должен соответствовать периоду возможной повторной доставки и бизнес-риску.
Я бы проверял не только наличие таблицы Inbox, но и восстановление после сбоя между шагами, поведение двух одновременных копий сообщения и возможность безопасного повторного проигрывания истории.
Transactional Inbox — это память о том, что мы уже сделали. Чтобы она была надёжной, память и само действие должны быть согласованы, а внешние эффекты защищены отдельно.