Action-Oriented Architecture

Код
— это граф.

AOA — Python-фреймворк, где бизнес-операция — стержень всей архитектуры. Операция сама объявляет, кто имеет право её выполнять, от чего она зависит и что произойдёт при сбое; из каталога операций собирается граф, который проверяет сам себя и не даёт инфраструктуре смешаться с бизнес-логикой. Нарушение ловится на старте, а не в проде через три недели.

pip install aoa-action-machine
как начать
python 3.12+лицензия Apache 2.0CI пройдентесты 2 555aoa-action-machine 1.0.1a2
orders/actions.py
@meta(domain=OrderDomain)@check_roles(ManagerRole)@depends(PaymentGateway)@compensate(refund_payment)class ChargeOrderAction(BaseAction[Params, Result]):    @result_state(OrderStatus.CONFIRMED)    async def charge_aspect(self, params, state, box):        await self.gateway.charge(params.amount)        return {"status": OrderStatus.CONFIRMED}
обходит фреймворквживую, при импорте
DomainRoleActionGateway

01 — Обзор

Архитектура живёт в коде.
Не в головах и не в документах.

Системы гибнут не от багов — а от того, что их со временем перестают понимать, когда команда меняется, а сложность растёт. Нас учат писать код, применять паттерны, следовать стилям — но не учат, как управлять сложностью и сохранять систему читаемой через год, два, пять. Каждая минута, потраченная на разбор «что здесь происходит», — это минута, украденная у развития.

В AOA принят другой подход: он переносит знания об архитектуре и системе из головы и устаревающих документов — в код, как машиночитаемое намерение: исполняемое, проверяемое, наблюдаемое.

ПЯТЬ УРОВНЕЙ ДЕТАЛИЗАЦИИ

Как пройти путь понимания системы от общей картины до полного погружения в код

Каждый уровень — опора: он даёт законченный ответ на свой вопрос, что такое система, и не требует заранее знать, что находится глубже.

Пройти пять уровней ↗
01

Domain

Где живёт ответственность?

02

Action

Что умеет система?

03

Contract

Что операция требует и обещает?

04

Pipeline

Как развивается сценарий?

05

Code

Где находится конкретное поведение?

● выводится из графа
Design-first

Спроектируйте модель, затем код.

Сущности, их связи и жизненный цикл объявляются как чистая доменная модель — не привязанная к ORM или конкретной базе. Целостность всей модели проверяется на старте.

Спроектировать модель →
ResourceEntityRelationsLifecyclethe domain, understoodthencode
Params → Result

Один класс — вся операция.

Роли, шаги, компенсации, ошибки, кеш и зависимости объявлены в одном месте: Params на входе, Result на выходе и ни одной боковой двери между ними.

transportrolesIoCrollbackcontextconnectionActionParamsResultrolesstepsrollbackcontext
Online debug

Action X-Ray.

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

Открыть Action X-Ray ↗
ONLINE DEBUG · RUN #A84FLIVE · PRODUCTIONACTION · ONE EXECUTIONParamsvalidatereservechargeResultSTATE IN{ validated_items: 3 }STATE OUTreservation_id · 18.6 msinput · output · error · duration at every boundarydevelopment · tests · production — the same projection
@depends / @connection / @context_requires

Порты гексагональной архитектуры, не зависимости.

@depends / @connection / @context_requires объявляют порты Action — адаптеры подключаются снаружи. box.resolve(T) отдаёт только объявленное.

PaymentGatewayEmailServiceInventoryServiceenv.* · runtime.*request.* · user.*@depends@connection@depends@context_requiresActionbox.resolve(T) — только объявленное
TestBench

Подменяйте мир, а не код.

Настоящий пайплайн выполняется без изменений — но на подменённом мире: пользователе, моках, контексте. Что здесь прошло, то и работает в продакшене.

Test worldmocked gatewayProd worldreal gatewayActionResultsame pipeline — different world

02 — Maxitor

Граф выше — настоящий.
Вот инструмент, который его рисует.

Каждый domain, роль и зависимость становятся узлом в момент импорта — четыре разных представления одной и той же работающей системы, и все они — неотредактированные экспорты.

Полный граф

Каждый domain, роль, зависимость — один граф.

Попробовать Maxitor ↗

FSM жизненного цикла

Все достижимые переходы статусов.

Попробовать Maxitor ↗

03 — Возможности

Больше, чем пайплайн.

Один и тот же объявленный граф питает кэширование, бизнес-события, observability и агентные фреймворки — без единой строчки клеевого кода.

Actionmachinerolescacheeventsrollback

Машина, которая их исполняет.

Каждый Action запускает одна машина: роли, кеш, события и откаты — её работа.

cache_key?cached valueaspectHITMISS

Кэш, объявленный.

cache_key сразу ведёт к результату; при промахе запускается аспект, а on_cache_write сохраняет значение.

ActionOrderCompletedMaxitorOCELDash

События — это факты, а не строчки лога.

box.info(Channel.business, …) порождает типизированный факт — его читают и Maxitor, и OCEL, и дашборды.

machineloggerotelocelone way — side-effects only

Observer не может вмешаться.

Уберите все observer — поведение не изменится. Телеметрия — это гарантия, а не надежда.

aspectraiseson_errorResultone handler — end of pipeline

Одно место, куда стекаются ошибки.

Единственный обработчик в конце пайплайна решает, что операция вернёт после сбоя.

reservereleasechargerefundpersistreverse order — automatic

Саги откатываются сами.

Сбой разворачивает завершённые шаги в обратном порядке; откат объявлен рядом со шагом.

check_rolesроль · RBACgrant/guardлёгкий ABACaccess_decide()глубокий ABACmachine.check() → AccessVerdict«могу ли я» — без вызовакаждый уровень может отказать раньше следующего

Сначала RBAC. Потом — ABAC, всё глубже.

check_roles — чистый RBAC: есть ли роль. grant.when / guard= — лёгкий ABAC по вызову. access_decide() — сложный ABAC: объект и факт.

OrderDomainActionRoleManagerRoleissubclass = authorityaccess matrixdomain × role — вместе матрица доступа

Domain и Role — карта системы.

Domain — точка сборки операции: граф, матрица доступа, визуализация. Role несёт полномочия через наследование. Вместе они и есть эта матрица.


04 — Расширение

Ядро маленькое.
Экосистема — нет.

Тот же объявленный граф подключает операцию к внешнему миру: агентным фреймворкам, очередям, интерфейсам, наблюдаемости. Ядро не растёт — растёт то, что вокруг него.

startplanActionrespondagent loop

Типизированный узел в LangGraph.

LangGraphController встраивает Action в агентный граф — те же контракты, тот же откат.

KafkaplannedFastAPIHTTPMCPAI toolsActionthe adapters translate — the Action doesn't change

Один Action. Любой фронт.

HTTP-маршрут и вызов AI-инструмента — это одна и та же операция с одними и теми же гарантиями.

many runsOCELobject-centric logOrderthe real process, discovered from what actually ran

OCEL/OCPM — настоящий процесс, а не диаграмма.

Каждый запуск уже даёт lifecycle events. Process mining видит по ним настоящий процесс и вовлечённые сущности — а не однажды нарисованную диаграмму.

ActionlifecycleeventsX-RayOTel spansone source of events — two live projections

OTel — те же события, трейсом.

Тот же плагин, что строит online-проекцию Action X-Ray, отдаёт события в OpenTelemetry: один источник — трейсы и живой запуск разом.

route→ capabilityVerdictallowedreasonexpiresReactmobileFletsame verdict — any UI

Intent-Based UI — не булево, а вердикт.

Маршрут → типизированная возможность: verdict() объясняет отказ, call() вызывает. Один контракт — React, mobile, Flet.

EntityModelAgentActomdigital twinentity + behavior + agent — one declaration

Digital twin

Actom объединяет сущность, поведение и агента в одной декларации — актор с состоянием, наблюдаемый через Action X-Ray. Файлы становятся графом-двойником системы.


05 — Концепции

Пять тем.
Каждая станет отдельной страницей.

Пять самостоятельных углов на одну систему — от грамматики деклараций до места AOA среди других фреймворков.

01

Принудительная грамматика

Грамматика превращает каждое правило в объявление (@check_roles, @depends, средства проверки, именования); при импорте эти объявления объединяются в граф — таким образом, код является спецификацией, а граф — это его машиночитаемая форма: проверяется при запуске, рисуется в Maxitor, отслеживается Action X-Ray — а не документация, которая плавает где-то рядом с кодом.

02

Конституция AOA

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

03

Разделение бизнес-логики и инфраструктуры

Action знает домен и правила; как достучаться до Postgres, Kafka или стороннего API, знает адаптер за интерфейсом Gateway или Resource. Бизнес-код не импортирует драйверы напрямую — граница проходит по декларации, а не по соглашению между разработчиками.

04

Принципы интенционально-ориентированного программирования

Код объявляет намерение — что должно произойти и при каких условиях, — а не последовательность вызовов конкретных библиотек. Реализация подставляется через порты (@depends, @connection) и может смениться, пока намерение остаётся прежним.

05

Сравнение с другими фреймворками

FastAPI и Django дают маршрутизацию и ORM, но не проверяют роли, зависимости и переходы состояний при импорте. LangGraph явно строит граф, но для оркестрации шагов LLM, а не бизнес-операций. AOA ближе к обоим сразу — исполняемый граф прикладной архитектуры, а не только транспорт или пайплайн модели.


06 — Практические рецепты

Одна задача.
Один рецепт.

Ориентированы на задачу, а не на обучение — вы уже знаете, что строите. Здесь сказано, как.

how-to/authoring-action

Скелет Action: Params, пайплайн аспектов, чекеры, подключения.

how-to/choosing-primitive

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

how-to/authoring-resource

Оберните базу данных, очередь или SDK в управляемую, инжектируемую зависимость.

how-to/authoring-adapter

Откройте доступ к существующим Action по новому протоколу, не трогая их.

how-to/authoring-cache-adapter

Подключите другой backend кэша к cache_key / on_cache_write.

how-to/authoring-plugin

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

how-to/authoring-auth-coordinator

Подключите собственную схему аутентификации к @check_roles.

how-to/authoring-logger

Направьте box.info / box.warning туда, где на самом деле живут ваши логи.

how-to/authoring-intent

Объявите новую грамматику участия, к которой смогут подключаться другие примитивы.

how-to/extending-context

Добавьте собственный срез к тому, что Action может видеть о вызове.

how-to/migrating-legacy

Постепенно переносите существующую кодовую базу, Action за Action.


07 — faq

Вопросы — ответы.

Разве это не просто Clean Architecture с кучей декораторов?

Разница — в контроле. Clean Architecture — это набор соглашений, которые ревьюер проверяет вручную. Контракты AOA — @check_roles, @depends, чекеры state — проверяются машиной до запуска операции, а не тем, кто вспомнил посмотреть.

Нужно ли переписывать существующее приложение на FastAPI или Django?

Нет. aoa-fastapi-adapter открывает существующие Action как обычные маршруты. Вы внедряете AOA Action за Action, а не фреймворк за фреймворком.

Сколько стоит пайплайн аспектов во время выполнения?

Каждый аспект — это обычный async-вызов: без сериализации, без границы процесса. Цена — это проверки, которые вы сами запросили, а не фреймворк вокруг них.

Нужен ли Maxitor, чтобы использовать AOA?

Нет. Maxitor читает те же Domain, Role и Action, которые вы уже объявили, — это просмотрщик, а не зависимость. AOA работает и без него.

Чем @compensate отличается от транзакции базы данных?

Транзакция откатывает одно хранилище данных. @compensate откатывает бизнес-операцию, которая могла затронуть платёжный шлюз, почтовый сервис и базу данных — а у них нет общей границы транзакции.

Когда AOA — не тот инструмент?

Для скрипта, разового CLI-инструмента или сервиса с одной операцией и без требований к комплаенсу — правильным выбором будут обычные функции.

⭐ Если вам нравится AOA, поставьте звезду на GitHub и присоединяйтесь к обсуждению в GitHub Discussions.

AOA — Код — это граф. · aoa.run