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

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