Надёжная автономность
Единица однократного исполнения — бизнес-эффект
Однократность — не свойство запуска агента или сообщения. Это управляемое утверждение об одном одобренном бизнес-изменении.

Автономные системы повторяют попытки. Они продолжают работу после тайм-аута, передают её другому воркеру после сбоя и делегируют задачу от одного агента другому. Для распределённых систем это нормальное поведение. Опасность возникает, когда повторная попытка начинает выглядеть как новое решение.
Представим одобренное поручение вернуть деньги клиенту, создать заказ поставщику или изменить право доступа. Среда исполнения может несколько раз пытаться выполнить это поручение. Но это не несколько бизнес-действий, а несколько возможных исполнений одного бизнес-намерения. И наоборот: два запроса с совершенно одинаковой нагрузкой могут быть двумя законными разными намерениями — два одинаковых заказа вполне могут быть действительно нужны.
Именно это различие определяет, что должна дедуплицировать автономная организация. Единицей однократного исполнения должен быть управляемый бизнес-эффект: одно одобренное, внешне наблюдаемое изменение. Не запуск агента, не экземпляр workflow, не трасса, не сообщение и не вызов инструмента.
Три идентификатора — три области действия
Среда исполнения должна различать идентификаторы с разным смыслом. ID запуска обозначает одну историю исполнения: какой воркер или агент пытался выполнить работу и через какие промежуточные шаги прошёл? ID события или сообщения обозначает одну транспортную запись: какая запись доставлена и является ли доставка повторной? ID эффекта обозначает одно предполагаемое изменение в реальном мире: какой одобренный бизнес-результат система пытается создать?
Это не терминологическая аккуратность, а практическая необходимость. CloudEvents требует уникальности комбинации source и id для отдельного события и допускает сохранение того же ID при повторной передаче дубликата. Это помогает потребителю распознать повторную транспортную запись. Но не определяет идентичность бизнес-эффекта, который может включать несколько событий, агентов, очередей и внешних вызовов.
По той же причине ID запуска не может быть идентификатором идемпотентности. Возобновлённый workflow или задача, переданная другому исполнителю, обычно получают новую историю исполнения. Если для каждой истории создаётся новый внешний ключ идемпотентности, среда сознательно отключает собственную защиту от дубликатов. Та же проблема у trace ID: это корреляция для наблюдаемости, а не утверждение, что две операции выражают одно одобренное намерение.
Выпускайте ID эффекта в момент одобрения
ID эффекта следует выпускать в момент, когда организация одобряет бизнес-действие, до начала исполнения. Одобрение может возникать из решения политики, делегированных полномочий или человеческого контроля — механизм различается. Архитектурное требование одно: идентичность должна быть стабильной. Любая повторная попытка, возобновление или передача работы, направленные на тот же одобренный результат, обязаны нести тот же ID эффекта.
Среда должна связать этот ID с отпечатком предполагаемого изменения. Отпечаток может включать цель, значимую нагрузку, тип операции и другие поля, определяющие эффект. Но он не заменяет ID. Один только хеш полезной нагрузки не отличит намеренный платёж от позднего, но такого же намеренного платежа. ID эффекта несёт идентичность одобренного намерения; отпечаток защищает его от повторного использования с изменёнными параметрами.
ID эффекта означает: «это то же одобренное действие». Отпечаток нагрузки означает: «и это всё ещё действие, которое было одобрено».
Такой подход соответствует устройству идемпотентных API. AWS описывает предоставляемые вызывающей стороной идентификаторы запроса как способ распознавать повторные выражения одного намерения и рекомендует отклонять использование одного идентификатора с изменёнными параметрами. Stripe также принимает ключи для операций изменения от клиента, отклоняет изменение параметров при том же ключе и устанавливает конечный срок хранения записей дедупликации.
Сделайте журнал эффектов контуром контроля
Журнал эффектов — это запись среды исполнения о данном контракте. Как минимум он должен хранить ID эффекта, целевую систему и операцию, отпечаток изменения, ссылку на авторизацию, статус, результат или ссылку на объект в целевой системе, а также период распознавания дубликатов. В нём также нужно фиксировать применённый для конкретного назначения механизм: ключ идемпотентности, транзакционную операцию или путь сверки.
Журнал превращает подавление дубликатов из скрытой возможности SDK в управляемую операционную функцию. Операторы смогут спросить: возврат был предпринят один раз или четыре; относятся ли четыре попытки к одному одобренному эффекту; какая авторизация его разрешила; когда истёк срок дедупликации у назначения. Аудит сможет отличить повторную попытку эффекта от второго бизнес-решения. Политика сможет определять, какие эффекты требуют долговременного хранения, а какие можно безопасно удалить после ограниченного срока.
Регистрацию идентичности эффекта и применение изменения необходимо проектировать особенно тщательно. AWS отмечает, что запись токена идемпотентности и связанного с ним изменения должны быть атомарны; иначе система может записать токен без изменения или выполнить изменение без записи токена. Та же гонка существует в автономной среде. Одновременные попытки требуют атомарной регистрации, явной обработки конфликтов или сериализации. Сам по себе ID эффекта не мешает двум воркерам одновременно потянуться к одному действию.
Адаптеры переносят контракт к каждому назначению
Управляемый эффект существует внутри организации; у каждого назначения — собственные гарантии. Адаптер должен преобразовывать стабильный ID эффекта в ключ идемпотентности или транзакционный механизм конкретного назначения и сохранять в журнале правила области действия и хранения этого назначения. Платёжный API может хранить ключи ограниченный задокументированный срок. База данных может поддерживать транзакцию. Обработчик сообщений может дедуплицировать только в пределах собственного хранилища. Это разные контракты, а не взаимозаменяемые ярлыки.
Kafka ясно показывает эту границу. Его семантика exactly-once позволяет атомарно фиксировать смещения и результаты, когда и то и другое остаётся в транзакционном домене Kafka. Как только эффект выходит во внешнюю систему, однократная доставка обычно требует сотрудничества со стороны назначения. Автономная организация не должна превращать сильную локальную гарантию в универсальное заявление о каждом SaaS API, банке, устройстве или почтовом ящике, с которым она взаимодействует.
Тайм-аут — это неопределённость, а не разрешение
Самый трудный случай — назначение, которое не сотрудничает. Агент отправляет изменяющий запрос, теряет ответ и получает тайм-аут. Операция могла завершиться успешно, не завершиться или выполниться лишь частично. Слепое повторение превращает неопределённость в риск дубликата.
Правильный статус здесь — неоднозначный результат. Среда должна запретить автоматическое повторное выполнение эффекта, а затем установить состояние через запрос к назначению, поздний callback или сверку с записями назначения. Сверка не заменяет идемпотентность. Это необходимый резервный механизм, когда среда не может получить достоверный ответ в момент исполнения.
Exactly-once — это ограниченное обещание управления
Фразу «ровно один раз» часто используют как общий лозунг надёжности. Для автономных организаций это должно быть точное, ограниченное обещание: в пределах заданного журнала эффектов, для заданного отпечатка изменения, в течение заданного срока хранения и с учётом возможностей каждого назначения организация распознаёт повторные попытки создать один одобренный бизнес-эффект.
Такое обещание полезнее невозможной сквозной гарантии. Оно даёт логике повторов стабильный объект для сохранения. Оно даёт авторизации конкретное действие для контроля. Оно даёт операторам доказательства для разбора спора, возникшего после тайм-аута. И оно предотвращает главную ошибку учёта автономной работы: принять несколько попыток за несколько решений.

