Action X-Ray

Встраивает видимость отладчика прямо в архитектуру: ход операции и её промежуточное состояние на каждом шаге видно online — даже в production, без остановки процесса.

Обычно сложная бизнес-операция представляет собой цепочку вызовов методов. Во время разработки её можно запустить в debugger, пройти весь путь исполнения и увидеть промежуточное состояние. Это помогает находить сложные, неочевидные ошибки. Но некоторые проблемы проявляются только на реальных данных и под реальной нагрузкой. В такой момент нужен online debugger, который позволит просветить реальное исполнение операции и найти причину без остановки процесса. Action X-Ray даёт ровно такую возможность.

Документация ↗

В production отладчик не поставишь

Отладчик показывает операцию изнутри — но в production его не поставить, а живой запуск не за что ухватить.

Во время разработки отладчик замораживает исполнение на любом шаге: видно аргументы, промежуточные значения и то, что уйдёт дальше. В production так нельзя — процесс не остановить ради одного запроса, а редкий вход трудно воспроизвести. Да и на лету запуск не наблюдать: цепочка вызовов живёт в call stack, её промежуточные значения — в локальных переменных, и как только вызов вернул управление, всё это исчезает. Логи сохраняют лишь заранее выбранные значения; технический trace видит вызовы и их длительность, но не знает, какие из них — бизнес-шаги и какие промежуточные значения между ними важны. Причину приходится угадывать по косвенным признакам.

Данные во время исполнения есть — отладчик это доказывает. Не хватает другого: заранее известной границы у запуска и у каждого его шага, к которой можно прицепить online-наблюдение — не останавливая процесс.

Разработка
validate
reservebreakpoint{ validated_items: 3 } · видны все значения
charge

пауза на любом шаге — виден весь state

Продакшн
запросответ / исключение

остановить нельзя — середина не видна

Полная документация ↗

Границы запуска уже заданы архитектурой

Чтобы наблюдать запуск как целое, у него должна быть точная граница — и у каждого его шага тоже.

В AOA операция — это Action с внешней границей Params → Result. Прогоняет её машина — движок AOA, который исполняет Action и заранее знает, где запуск начинается и заканчивается. Внутри путь разбит на Aspects, и у каждого — своё промежуточное состояние, state. Аспект получает snapshot предыдущего state и возвращает свой новый — тот полностью заменяет предыдущий, а не дополняет его. Поэтому всё нужное следующим шагам аспект переносит явно, и между шагами ничего не протекает само; @result_* проверяет выход сразу на границе. Эти границы — не инструмент наблюдения, а само устройство операции; но именно они дают заранее известные точки, где у каждого шага виден вход, выход и state.

Машина в любой момент знает рамку запуска и весь его контрактный путь: что вошло в каждый Aspect и какой state вышел. Наблюдаемый артефакт «один запуск» уже собран самой архитектурой — осталось выпустить его наружу.

внешняя граница Action
Params
state до{ }
01 · точка наблюденияvalidate@result_* ✓
state после{ validated_items: 3 }
state до{ validated_items: 3 }
02 · точка наблюденияreserve@result_* ✓
state после{ …, reservation_id: 'res_42' }
state до{ …, reservation_id: 'res_42' }
03 · точка наблюденияcharge@result_* ✓
state после{ …, payment_id: 'pay_91' }
Result
Полная документация ↗

Online debug: живой запуск виден снаружи

Раз границы известны, машина на каждой из них выпускает событие — а внешний наблюдатель собирает из них живую картину запуска.

На внешней границе видны фактические Params и Result, на каждой внутренней — входящий и исходящий state, длительность и ошибка. Машина знает все эти точки и автоматически выпускает lifecycle events; плагин получает их снаружи и строит online-проекцию конкретного запуска — дерево шагов с реальными входами, выходами, ошибками и временем. Одинаково в development, в тестах и в production — без вызовов логирования, ручных таймеров и tracing-кода внутри бизнес-методов.

Это не подключённый отладчик и не остановка процесса, а его безопасная online-проекция. Наблюдатель видит только разрешённое контрактом: чувствительные поля остаются opaque, а сбой самого плагина изолирован и не меняет результат операции. Ровно это и есть Action X-Ray.

живой запускCreateOrderAction
start · Paramsvalidate · state → state′ · 0.4 мсreserve · state′ → state″ · 12 мсcharge · error · 3 мсfinish · Result · 15 мс
lifecycle events
plugin · снаружи операцииonline-проекция запуска
  • start · Params
  • validate · state → state′ · 0.4 мс
  • reserve · state′ → state″ · 12 мсopaque
  • charge · error · 3 мс
  • finish · Result · 15 мс

процесс не останавливается · одинаково в dev, test и production

Плагин отдают машине при создании — бизнес-методы остаются нетронутыми:

service/machine.py
plugin = OpenTelemetryPlugin(    tracer_provider=tracer_provider,    logger_provider=logger_provider,    service_name="checkout-service",)# The plugin lives outside the Action; business methods stay untouched.machine = ActionProductMachine(plugins=[plugin])result = await machine.run(Context(), CreateOrderAction(), params)
Полная документация ↗

В тесте — тот же рентген

Тот же online-снимок доступен и в тесте: TestBench запускает ту же операцию через ту же машину.

TestBench не создаёт особую тестовую видимость — он прогоняет тот же Action через ту же машину и те же границы. Меняется только внешний мир: вместо production-контекста, ресурсов и gateway берутся их тестовые реализации, а роли, pipeline, checkers и сборка Result остаются настоящими. Операцию можно прогнать целиком или остановиться на одной границе — Aspect, summary или compensator — без специальных крючков в бизнес-коде.

Тест проверяет ту же проекцию, что доступна в production: не только финальный Result, но и любой промежуточный state. Меняется реальность вокруг операции, а не сама операция и не способ её наблюдать.

PRODUCTIONреальные Context · Resources · Gateways
тот же Action · та же машинароли · pipeline · checkers · сборка Resultодна и та же online-проекция
TESTтестовые Context · fixtures · mocks
весь Action — поведение системыодна граница — точная локализация

Тот же Action через TestBench — целиком или на одной границе:

tests/test_create_order.py
inventory = AsyncMock(spec=InventoryGateway)inventory.reserve.return_value = "res_42"bench = TestBench().with_user(    user_id="customer", roles=(CustomerRole,)).with_mocks({InventoryGateway: inventory})# The whole operation — same machine, same boundariesresult = await bench.run(CreateOrderAction(), params, rollup=False)# Or stop at a single boundary and inspect its statestate_after = await bench.run_aspect(    CreateOrderAction(), "reserve_aspect",    params=params, state={"validated_items": 3},)assert state_after["reservation_id"] == "res_42"
Полная документация ↗

Online debug становится свойством архитектуры

AOA задаёт внешнюю границу Action, внутренние границы его Aspects и превращает промежуточный state в контракт. Во время запуска эти же границы становятся системой координат для lifecycle events — и видимость, которая раньше возникала только под отладчиком или после ручной инструментализации, теперь доступна online для каждого исполнения, включая production, без инфраструктурного кода внутри операции.

ОтладкаДля конкретного запуска видны state до и после каждого шага, ошибка и её точная граница — без подключения отладчика.
ProductionТа же проекция доступна online на реальной нагрузке через внешние plugins, которые не меняют Action.
ТестированиеПроверяется та же операция и те же границы; подменяется только внешняя реальность вокруг неё.
Action X-Ray · aoa.run