Hello, world!
Начнём с Action, который не делает ничего полезного — печатает строку и возвращает пустую заглушку. Его ценность в другом: он показывает минимальный набор объявлений, без которых AOA не запустит вообще ничего.
$ uv run python examples/step_01_Action_and_pipeline/01_hello_world.pyДля такого простого приветствия строк набралось немало — но ни одна из них не ритуал: каждая объявляет нечто, без чего Action в AOA не считается завершённым. Всё начинается с domain. @meta — это паспорт Action; @check_roles(GuestRole) объявляет доступ вслух, а не по умолчанию. А @summary_aspect — единственная точка выхода: пропустите любое из трёх, и машина откажется запускать Action ещё до первого вызова.
Params, Result и box
Заглушки хороши для введения, а вот настоящий Action работает с данными. Добавим ему вход и выход — и заменим print на инструмент, которым Action в AOA обращается к внешнему миру.
$ uv run python examples/step_01_Action_and_pipeline/02_params_result_and_box.pybox интереснее, чем print, — это структурированный логгер, привязанный к текущему шагу: он несёт канал, уровень, domain, имя action и aspect, поэтому легко поддаётся фильтрации, маршрутизации и экспорту. Куда уходит событие — не решение самого Action: это забота машины, подключаемая снаружи.
Несколько аспектов
Одного шага редко бывает достаточно. AOA не оставляет промежуточным данным свободы прятаться в локальных переменных или полях объекта — они проходят через пайплайн в явном state, тогда как сам Action остаётся пустым между вызовами.
$ uv run python examples/step_01_Action_and_pipeline/03_multiple_aspects.pyВозвращённый словарь полностью заменяет state — а не дополняет его. @result_string("cleaned", required=True) — это checker: проверяемый контракт на выход аспекта. Нарушьте обещание — и машина немедленно остановит аспект, не дав испорченному state пройти дальше.
Наследование
Action наследуются как обычные классы — с одним намеренным исключением: аспекты не наследуются в пайплайн. Машина строит пайплайн только из того, что объявлено в самом классе.
$ uv run python examples/step_01_Action_and_pipeline/04_inheritance.pyChildOrderAction прекрасно работает, но его пайплайн содержит только child_summary — родительский validate_aspect никогда не выполняется. ExtendedOrderAction делает всё правильно: он заново объявляет аспект и вызывает super(), чтобы опереться на логику предка.
Эксперименты
В этой главе накопилось немало правил — но у них есть общее свойство: почти все они проверяются при объявлении класса, при импорте модуля, а не при вызове Action. Код здесь — исполняемая спецификация; нарушение контракта всплывает до первого запуска, а не однажды ночью в продакшене. Попробуйте сами — найдите способ сломать SayHelloAction из первого примера и нажмите Run.
$ uv run python examples/step_01_Action_and_pipeline/01_hello_world.pyИтоги
Костяк модели уже сложился: операция — это Action с типизированной границей (Params и Result), линейным пайплайном аспектов и контрактами, которые в основном проверяются при инициализации. Поведение выражено в структуре кода, а не в документации, которая существует отдельно от него.
Контрольные вопросы
- Какой инвариант делает Action читаемым «по одному классу», и в какой момент это проверяется?
- Почему отсутствие
@check_roles— это ошибка, а не молчаливое «доступно всем»? - Пайплайн AOA линеен — без ветвлений и побочных выходов. Что даёт это ограничение и какой ценой?
- Почему аспекты не наследуются в пайплайн автоматически? Сравните с обычным наследованием методов в ООП.
- Что значит «код — исполняемая спецификация», и почему большинство контрактов проверяется при инициализации, а не во время выполнения?
