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

まずモデル、それからコード。

Entity と、その関係と、そのライフサイクルを、混じりけのないドメインモデルとして宣言する — ORM にも特定のデータベースにも縛られずに。モデル全体の整合性は起動時に検査される。

モデルを設計する →
ResourceEntityRelationsLifecyclethe domain, understoodthencode
Params → Result

一つのクラスに、操作のすべて。

ロール、手順、補償、エラー、キャッシュ、依存が一か所に宣言される。入口に Params、出口に Result、そのあいだに脇口は一つもない。

transportrolesIoCrollbackcontextconnectionActionParamsResultrolesstepsrollbackcontext
Online debug

Action X-Ray。

デバッガの可視性をアーキテクチャに埋め込む。操作の進み具合と各ステップの状態は、本番でも online で観測できる — プロセスを止めずに。

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 を試す ↗

ユースケース図

ロールと、そのロールが使える Action。

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

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 — 拡張

核は小さい。
まわりはそうではない。

同じ宣言されたグラフが、操作を外の世界へつなぐ。エージェントのフレームワーク、キュー、画面、observability へ。核は育たない — 育つのはそのまわりだ。

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 を出している。プロセスマイニングはそこから本物の過程と、関わった Entity を見る — 一度描かれたきりの図ではなく。

ActionlifecycleeventsX-RayOTel spansone source of events — two live projections

OTel — 同じイベントを、トレースとして。

Action X-Ray の online な投影を組み立てるのと同じプラグインが、イベントを OpenTelemetry へ渡す。一つの源から、トレースと動いている実行が同時に。

route→ capabilityVerdictallowedreasonexpiresReactmobileFletsame verdict — any UI

Intent-Based UI — 真偽ではなく、判定を。

ルート → 型のついた可能性。verdict() が拒否の理由を説明し、call() が呼び出す。一つの契約で React も mobile も Flet も。

EntityModelAgentActomdigital twinentity + behavior + agent — one declaration

デジタルツイン

Actom は Entity と振る舞いとエージェントを一つの宣言にまとめる — 状態を持ち、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、アスペクトのパイプライン、checker、connection。

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 ずつ少しずつ移していく。


07 — faq

問いと、答え。

これはデコレータを山ほど付けた Clean Architecture では?

違いは強制力にある。Clean Architecture はレビュアーが手で確かめる取り決めの集まりだ。AOA の契約 — @check_roles@depends、state の checker — は操作が走る前に機械が検査する。見ようと思い出した人がではなく。

既存の FastAPI や Django のアプリを書き直す必要は?

ない。aoa-fastapi-adapter が既存の Action をふつうのルートとして公開する。導入は Action ずつ進められる。フレームワークごと入れ替えるのではなく。

アスペクトのパイプラインは実行時にどれくらい高くつく?

どのアスペクトもふつうの async 呼び出しだ。直列化もなければ、プロセス境界もない。代価は自分で頼んだ検査の分であって、そのまわりのフレームワークの分ではない。

AOA を使うのに Maxitor は要る?

要らない。Maxitor はすでに宣言された Domain、Role、Action を読むだけの閲覧器であって、依存ではない。AOA はそれなしでも動く。

@compensate はデータベースのトランザクションと何が違う?

トランザクションが巻き戻すのは一つの保存先だけだ。@compensate が巻き戻すのは業務操作であり、それは決済ゲートウェイにもメール配信にもデータベースにも触れていたかもしれない — その三つに共通のトランザクション境界はない。

AOA が向かないのはどんなとき?

スクリプトや、その場かぎりの CLI や、操作が一つでコンプライアンス要件のないサービスなら、ふつうの関数が正しい選択だ。

⭐ AOA を気に入っていただけたら、GitHub でスターを、そして GitHub Discussions での議論への参加をお願いします。

AOA — コードはグラフである。 · aoa.run