Неверная рамка: кнопка отмены
Запрос «добавить кнопку отмены» агенту предполагает модель, которая не работает. Он исходит из единой обратимой транзакции, как текстовый редактор трактует нажатие клавиши. Запуск агента — это не транзакция. Это последовательность разнородных записей в системах с разными моделями согласованности: строка, вставленная в базу данных, сообщение, опубликованное в очередь, файл, записанный в объектное хранилище, HTTP-вызов, запускающий нижестоящий рабочий процесс, письмо, отправленное через провайдера, который уже передал байты удалённому MTA. У каждого из них своё представление о том, что значит отозвать запись, а у некоторых такого представления нет вовсе.
Кнопка отмены — это элемент интерфейса, который скрывает проблему состояния. В однопользовательском приложении с одним хранилищем проблема состояния достаточно мала, чтобы её скрыть. В агенте, охватывающем несколько систем, проблема состояния и есть проблема проектирования. Кнопка — это то, что остаётся после того, как вы её решили, а не то, что вы строите, чтобы её решить.
Эффекты не однородны
Полезно разделять эффекты по тому, чего стоит их отмена, а не по тому, как они выглядят в трассировке.
Некоторые эффекты дёшево отменить. Черновик документа, запись в staging, строка в черновой схеме, переключённый feature flag в тестовой среде. Отмена — это ещё одна запись того же рода, против того же органа власти, и стоимость ограничена.
Некоторые дорого. Отправленное письмо нельзя отправить обратно; за ним может последовать только исправление, которое само по себе является новым сообщением со своей аудиторией и своими последствиями. Платёж можно вернуть, но возврат — это новая транзакция с комиссиями, сроками и последствиями для сверки. Удалённую запись иногда можно восстановить из резервной копии, но восстановление — это частичная операция на момент времени, которая может затереть несвязанные изменения.
Некоторые невозможны. Раскрытый секрет нельзя сделать нераскрытым. Как только учётные данные, запись клиента или внутренний документ покинули границу доверия, никакая компенсирующая операция не вернёт мир в прежнее состояние. Соответствующий контроль — это предотвращение, а не отмена.
Таким образом, вопрос проектирования не в том, предлагать ли отмену. Он в том, какие инструменты можно вызывать без пути отмены и при каких предварительных условиях.
Классификация эффектов инструментов

Работоспособная таксономия имеет три класса, и каждое объявление инструмента должно нести ровно один из них.
Обратимый. Инструмент предоставляет компенсирующую операцию с теми же полномочиями, что и исходная. Компенсация симметрична: тот же субъект, та же область, те же гарантии согласованности. Вставка в базу данных с соответствующим удалением по первичному ключу обратима. Развёртывание в staging с откатом к закреплённому артефакту обратимо. Проверка состоит в том, может ли компенсирующий вызов быть выполнен той же идентичностью агента без нового одобрения и эквивалентен ли результат состоянию до вызова для целей, важных для запуска.
Компенсируемый. Отмена существует, но это новое действие со своими побочными эффектами и своим одобрением. Возврат средств компенсируем: он отменяет экономический эффект списания, но создаёт новую запись в реестре, может повлечь комиссию и может требовать человеческой авторизации выше порога. Опубликованное сообщение, которое потребители уже обработали, компенсируемо лишь в том смысле, что можно опубликовать исправление; исходное потребление отменить нельзя. Компенсируемые инструменты — это там, где живёт большая часть интересной инженерии, потому что компенсация — это полноценная операция, которая сама нуждается в предварительном условии, пути одобрения и записи.
Необратимый. Компенсирующей операции не существует. Отправка внешнего письма, раскрытие секрета, удаление записи без резервной копии, вызов действия третьей стороны, для которого третья сторона не предоставляет отмены. Они требуют предварительной авторизации, а не отката постфактум. Контроль — это шлюз перед вызовом, а не процедура после него.
Классификация — по инструменту, а не по агенту. Агент, который в основном обратим, всё равно будет содержать один необратимый инструмент, и этот один инструмент определяет потолок автономии для всего запуска, если планировщик не ограничен так, чтобы изолировать его.
Превращение обратимости в полноценное поле
Таксономия полезна только тогда, когда она машиночитаема. Каждое объявление инструмента должно нести класс обратимости и, где он существует, идентификатор его компенсирующей операции. В схеме это выглядит как небольшой набор полей рядом с именем инструмента, параметрами и областью полномочий.
{
"name": "send_customer_email",
"reversibility": "irreversible",
"compensating_operation": null,
"preconditions": ["recipient_verified", "template_approved", "human_gate_passed"]
}
{
"name": "create_invoice",
"reversibility": "compensable",
"compensating_operation": "void_invoice",
"compensation_requires_approval": true
}
{
"name": "write_staging_record",
"reversibility": "reversible",
"compensating_operation": "delete_staging_record",
"comrver-rack-network-cables-6c483a-2ef0de.jpg)
Из этого следуют два механизма. Во-первых, планировщик ограничен классом. Необратимые инструменты нельзя вызывать спекулятивно, нельзя помещать в параллельные ветви, где сбой соседней ветви оставит частичный эффект, и нельзя повторять при неоднозначном сбое без явного шага сверки. Компенсируемые инструменты можно вызывать, но их компенсация сама является вызовом инструмента, который планировщик должен запланировать, а не неявной очисткой. Обратимые инструменты можно использовать свободно в пределах их области полномочий.
Во-вторых, запись запуска хранит компенсирующую операцию рядом с эффектом. Когда инструмент с компенсирующей операцией завершается успешно, журнал запуска записывает идентификатор эффекта и идентификатор компенсации вместе, плюс аргументы, необходимые для вызова компенсации. Тогда откат становится поиском, а не реконструкцией. Оператор, реагирующий на инцидент, не должен выводить, что сделал агент, и угадывать отмену; запись называет операцию и её аргументы. Это разница между ограниченной процедурой восстановления и открытым расследованием.
## Что это даёт
Первый выигрыш — асимметричная автономия. Агенту можно предоставить высокую автономию над обратимыми поверхностями, где ошибка стоит компенсирующего вызова, и жёсткие шлюзы над необратимыми, где ошибка стоит раскрытия или отношений с клиентом. Это полезнее единого глобального уровня автономии, который либо чрезмерно ограничивает обратимую работу, либо недостаточно ограничивает необратимую. Класс обратимости — это ось, по которой должна варьироваться автономия.
Второй выигрыш — реагирование на инциденты. Когда каждый эффект записан вместе с его компенсацией, процедура восстановления — это список, а не поиск. Оператор знает, какие эффекты обратимы, какие компенсируемы и требуют одобрения, а какие необратимы и потому требуют совершенно иного ответа: уведомления, локализации и раскрытия, а не отката.
Третий выигрыш — оценка. Если объявления инструментов несут обратимость, стенд оценки может измерять, как часто запуск тянется к необратимому инструменту до того, как выполнены его предварительные условия. Это опережающий индикатор чрезмерно рьяной автономии, и его можно измерить, не дожидаясь производственного инцидента. Это также позволяет оценке сравнивать варианты планировщика на одной и той же задаче: завершает ли вариант, откладывающий необратимые вызовы до выполнения предварительных условий, задачу так же часто, и какой ценой в шагах?
## Честный компромисс
Этот дизайн стоит пропускной способности. Ограничение планировщика изолировать необратимые инструменты и требование явного планирования компенсации для компенсируемых устраняет параллелизм и спекулятивное выполнение, которые использовал бы менее ограниченный планировщик. На задачах, которые в основном обратимы, ограничение почти бесплатно. На задачах, смешивающих обратимые и необратимые эффекты, планировщик будет сериализоваться вокруг необратимых вызовов, и запуск будет медленнее и длиннее, чем неограниченный. Компромисс намеренный: альтернатива — запуск, который быстр до тех пор, пока не станет невосстановимым.
Вторая цена — обслуживание. Класс обратимости — это не свойство инструмента в абстракции; это свойство инструмента в развёрнутом виде, с его текущими полномочиями, текущим состоянием резервного копирования и текущими нижестоящими потребителями. Инструмент, который был обратим, когда его хранилище имело восстановление на момент времени, становится компенсируемым, когда это окно восстановления сокращается. Классификацию нужно пересматривать вместе с инструментом, а не задавать один раз при объявлении.
## Заключение
Обратимость — это свойство состояния каждого инструмента, а не элемент интерфейса, наложенный поверх агента. Работа состоит в том, чтобы классифицировать эффект каждого инструмента, записать компенсирующую операцию там, где она существует, ограничить планировщик по классу и предоставлять автономию асимметрично. Кнопка отмены, если она вообще появится, — это поверхность этой работы. Она не заменяет её.
