Я не считаю интеграцию готовой, пока повторное сообщение может изменить результат

Интеграция часто выглядит готовой в тот момент, когда система А отправила сообщение, а система Б его приняла. Но именно после этого начинается неприятная часть. Сеть может оборваться, ответ может прийти слишком поздно, отправитель может не успеть сохранить подтверждение и послать то же сообщение ещё раз.
Я не считаю интеграцию готовой, пока повторное сообщение может изменить результат. Один и тот же заказ не должен дважды уйти в отгрузку, платёж — появиться в учёте два раза, а заявка — создать две одинаковые задачи только потому, что между системами на секунду пропала связь.
Повтор — обычная жизнь, а не редкая авария
Представим простой путь: интернет-магазин передаёт заказ в учётную систему. Он отправил данные, но не получил ответ. Что должен сделать магазин? Если он молча забудет операцию, заказ может потеряться. Если немедленно отправит её снова, получатель может увидеть два заказа.
Правильный ответ не в том, чтобы надеяться на идеальную сеть. Надо заранее решить: две одинаковые доставки одного события означают одну операцию. В разработке это называют идемпотентностью, но смысл бытовой: повторное нажатие кнопки не должно второй раз списать деньги.
Это касается не только заказов. Повторяются сообщения о платеже, выдаче доступа, изменении цены, передаче лида, закрытии заявки. Чем ближе событие к деньгам, обязательствам перед клиентом или правам сотрудника, тем дороже ошибка.
У события должен быть собственный номер
Первое, что я ищу в такой интеграции, — устойчивый идентификатор события. Не время отправки и не случайный номер попытки, а ключ, который остаётся тем же, пока мы говорим об одной бизнес-операции.
Например, заказу можно присвоить внешний идентификатор ещё в источнике. Получатель сохраняет его вместе с результатом обработки. Если сообщение пришло повторно, система не создаёт новый заказ. Она находит старый и возвращает уже известный результат: принято, ожидает проверки, отклонено с причиной.
Здесь важна мелочь: ключ должен описывать именно событие, а не человека или карточку вообще. У одного клиента может быть много заказов, а одна карточка заказа может пройти несколько разных событий: создание, отмену, возврат. Смешать их — всё равно что выдавать всем посылкам один трек-номер.
Не путайте «доставили» и «сделали»
Ещё одна частая ловушка — считать технический ответ успехом бизнеса. Сервер ответил 200, но запись могла не сохраниться, ожидать проверки или уйти в очередь. Поэтому полезно разделять три состояния: сообщение получено, действие принято к обработке, бизнес-результат подтверждён.
Это помогает и человеку. Вместо загадочного «интеграция работает» он видит: заказ принят, ждёт проверки лимита; повторная отправка безопасна; окончательный статус появится после ответа учётной системы. Такой маршрут честнее, чем зелёная галочка, за которой ничего нельзя проверить.
На стыке систем всегда нужен ответственный за результат, а не только за свой кусок кода. Я подробно разбирал эту роль в материале о владельце ошибки на стыке систем. Без неё каждая команда честно доказывает, что отправила или приняла запрос, а клиент всё равно ждёт.
Где хранить след обработки
Получателю нужен журнал обработанных событий. В нём достаточно хранить ключ события, время первого получения, статус, ссылку на созданную операцию и понятную ошибку, если она была. Это не отчёт ради отчёта. Он отвечает на вопрос поддержки: «Мы получили это дважды или это две разные заявки?»
Журнал особенно важен при повторной обработке после сбоя. Не стоит заставлять оператора угадывать, можно ли нажать «повторить». Система должна либо повторить безопасный технический шаг, либо прямо сказать, что операция уже завершена и показать результат.
История события и текущая карточка — разные вещи. В карточке заказа удобнее видеть текущее состояние. В истории — как оно появилось и какая попытка была первой. Когда это смешано, расследование превращается в чтение обрывков логов. Об этом различии есть отдельный разбор истории события и текущего состояния.
Проверка не должна ограничиваться счастливым сценарием
Перед запуском я бы попросил команду пройти не только обычный обмен, но и четыре неудобных случая:
- ответ потерялся после успешного создания операции;
- одно сообщение пришло дважды с интервалом в минуту;
- получатель временно недоступен и отправитель делает повтор;
- часть операции выполнилась, а следующая часть — нет.
Для каждого случая нужен понятный ответ: что увидит сотрудник, что можно повторить, кто получит сигнал и останется ли одна честная бизнес-операция. Если ответ звучит как «потом посмотрим в логах», интеграция ещё не готова к рабочему дню.
Наконец, стоит договориться о границе повтора. Иногда событие безопасно принять много раз, но следующая команда уже требует отдельного подтверждения. Например, можно повторно запросить статус доставки, но нельзя автоматически повторить списание. Такая граница должна быть частью бизнес-правила и видна тому, кто поддерживает процесс.
Надёжность начинается с уважения к реальности
Повторная доставка не означает плохую систему. Это нормальный способ пережить сеть, перезапуск и задержку. Плохим становится решение, которое не умеет отличить повтор от новой просьбы.
Когда у события есть устойчивый ключ, у получателя — память о результате, а у команды — видимый статус, сбой связи перестаёт превращаться в двойную работу. Для меня это и есть признак взрослой интеграции: она не требует, чтобы мир вокруг всегда отвечал с первого раза.
Обсудим?