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
Action trace

Action X-Ray。

查看一次操作中每个 Aspect 的输入、输出与耗时——无论开发、测试还是生产,都无需在业务逻辑中加入可观测性代码。

打开 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

替换世界,而非代码。

真实的流水线原封不动——只是运行在一个被替换的世界里:用户、mock、上下文都可以换掉。这里测试通过的,就是最终会在生产环境中运行的那套代码。

Test worldmocked gatewayProd worldreal gatewayActionResultsame pipeline — different world

02 — Maxitor

上面的图是真实的。
这就是绘制它的工具。

每一个 Domain、角色和依赖,在被导入的那一刻就会成为一个节点——同一套运行中系统的四种视图,全部都是未经编辑的原始导出。

完整图

每一个 Domain、角色、依赖——尽在一张图中。

试用 Maxitor ↗

03 — 能力

不只是一条流水线。

同一张被声明出来的图,同时为缓存、业务事件、可观测性与 agent 框架提供支撑——不需要一行胶水代码。

Actionmachinerolescacheeventsrollback

执行它们的那台机器。

每个 Action 都由同一台机器执行:角色、缓存、事件与回滚都是它做的。

cache_key?cached valueaspectHITMISS

缓存,声明式的。

cache_key 命中时直接短路到结果;未命中时运行该 aspect,并由 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

Saga 会自行回滚。

失败时已完成的步骤按相反顺序撤销,回滚声明在它撤销的那一步旁。

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 接入 agent 图中——契约不变,回滚机制不变。

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。流程挖掘能从中看到真实的流程与涉及的实体,而非一次性画好的图表。

ActionlifecycleeventsX-RayOTel spansone source of events — two live projections

OTel:同样的事件,化作轨迹。

驱动 Action X-Ray 在线投影的同一个插件,也会把事件发送给 OpenTelemetry:同一个数据源,轨迹与实时运行,一次到位。

route→ capabilityVerdictallowedreasonexpiresReactmobileFletsame verdict — any UI

基于意图的 UI:不是布尔值,而是判定。

路由 → 类型化的能力:verdict() 解释拒绝原因,call() 执行调用。一份契约:React、移动端、Flet。

EntityModelAgentActomdigital twinentity + behavior + agent — one declaration

数字孪生

Actom 在一个声明中统一了实体、行为与智能体,即一个带状态、可通过 Action X-Ray 观测的行动者。文件由此成为系统的图态数字孪生体。


05 — 概念

五个主题。
每个都会成为独立页面。

五个独立视角,聚焦同一个系统:从声明语法,到 AOA 在其他框架中的定位。

01

强制语法

语法把每条规则都变成声明(@check_roles@depends、校验手段、命名规则);导入时这些声明汇聚成一张图,于是代码即是规约,图即其机器可读形式:启动时校验、在 Maxitor 中绘制、由 Action X-Ray 追踪,而非游离于代码之外的文档。

02

AOA 宪法

八个原语和一组强制声明,不是代码风格,而是机器在每次导入时校验的法则。谁都无法悄悄违反它:不一致会直接阻止启动,而不是等到生产环境才暴露出来。

03

业务逻辑与基础设施的分离

Action 了解 Domain 与规则;如何访问 Postgres、Kafka 或第三方 API,则由 Gateway 或 Resource 接口背后的适配器负责。业务代码不直接导入驱动,边界由声明划定,而非开发者之间的约定。

04

意图导向编程的原则

代码声明的是意图,即应该发生什么、在什么条件下发生,而不是对具体库的一连串调用。实现通过端口(@depends@connection)注入,只要意图不变,实现就可以更换。

05

与其他框架的对比

FastAPI 和 Django 提供路由和 ORM,但不会在导入时校验角色、依赖与状态迁移。LangGraph 确实显式构建图,但服务的是 LLM 步骤编排,而非业务操作。AOA 同时更接近二者:一张可执行的应用架构图,而不仅仅是传输层或模型流水线。


06 — 实用食谱

一个任务。
一份食谱。

面向任务,而非面向教程——你已经知道自己要构建什么,这些食谱告诉你该怎么做。

how-to/authoring-action

Action 的骨架:Params、aspect 流水线、checker、connection。

how-to/choosing-primitive

一份决策指南,帮你找到真正契合你所构建之物的原语。

how-to/authoring-resource

把一个数据库、队列或 SDK 封装为一个受管理、可注入的依赖。

how-to/authoring-adapter

在不改动现有 Action 的前提下,以新协议对外暴露它们。

how-to/authoring-cache-adapter

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 checker——是由机器在操作运行之前检查的,而不是靠谁碰巧记得去看一眼。

我必须重写现有的 FastAPI 或 Django 应用吗?

不需要。aoa-fastapi-adapter 会把现有的 Action 以普通路由的形式暴露出来。你可以按 Action 逐个迁移,而不是按框架整体迁移。

aspect 流水线在运行时的开销是多少?

每个 aspect 都是一次普通的异步调用——没有序列化,也没有进程边界。开销来自你自己要求的那些检查,而不是框架本身。

使用 AOA 一定需要 Maxitor 吗?

不需要。Maxitor 读取的正是你已经声明好的 Domain、Role 与 Action——它是一个查看器,而不是一个依赖项。没有它,AOA 照样运行。

@compensate 和数据库事务有什么区别?

事务回滚的是单一数据存储。@compensate 回滚的是一整个业务操作,它可能触及了支付网关、邮件服务和数据库——而这些系统之间并不共享同一个事务边界。

什么时候 AOA 不是合适的选择?

对于一个脚本、一次性的 CLI 工具,或者只有单一操作且没有合规要求的服务来说,普通函数才是正确的选择。

⭐ 如果你喜欢 AOA,欢迎在 GitHub 上点亮 star,并在 GitHub Discussions 中加入讨论。

AOA — 代码即图。 · aoa.run