Hello, world!
まずは何の役にも立たない Action から始めよう。文字列を印字して、空の返り値を返すだけだ。値打ちは別のところにある — これがなければ AOA が何一つ起動しない、最小限の宣言の組を見せてくれる。
これほど簡単な挨拶にしては行数が多い — だが儀式は一行もない。どれも、それなしでは AOA の Action が完成したと見なされないものを宣言している。すべては domain から始まる。@meta は Action の身分証。@check_roles(GuestRole) はアクセス権を暗黙にせず声に出して宣言する。そして @summary_aspect は唯一の出口だ。三つのどれか一つでも欠ければ、最初の呼び出しを待たずに機械が起動を拒む。
Params、Result、そして box
空の返り値は導入にはよいが、本物の Action はデータを扱う。入口と出口を与え、print を、AOA の Action が外の世界に触れるための道具に置き換えよう。
$ 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 は普通のクラスと同じように継承できる — ただし意図された例外が一つある。アスペクトはパイプラインに継承されない。機械はそのクラス自身に宣言されたものだけからパイプラインを組み立てる。
ChildOrderAction はきちんと動くが、そのパイプラインに入っているのは child_summary だけだ — 親の validate_aspect は決して実行されない。ExtendedOrderAction は正しくやっている。アスペクトを改めて宣言し、super() を呼んで親の処理に乗っている。
実験
この章では規則がずいぶん積み上がった — が、それらには共通の性質がある。ほとんどが クラス宣言の時点で、モジュールの読み込み時に検査される。Action の呼び出し時ではない。ここでのコードは実行可能な仕様であり、契約違反は最初の実行より前に表に出る。ある夜の本番で、ではなく。自分で試してみてほしい — 最初の例の SayHelloAction を壊す方法を見つけて Run を押そう。
まとめ
モデルの骨格はもう組み上がった。操作とは、型のついた境界(Params と Result)と、アスペクトの一本のパイプラインと、その多くが初期化時に検査される契約とを備えた Action である。振る舞いはコードの構造そのもので言い表されていて、コードとは別に存在する文書の中にはない。
確認問題
- Action を「クラス一つで」読めるものにしている不変条件は何か。そしてそれはいつ検査されるか。
@check_rolesがないことが、黙って「全員に公開」ではなく誤りとして扱われるのはなぜか。- AOA のパイプラインは一本道で、分岐も脇道の出口もない。この制約は何をもたらし、その代償は何か。
- アスペクトがパイプラインへ自動で継承されないのはなぜか。オブジェクト指向における通常のメソッド継承と比べてみよう。
- 「コードは実行可能な仕様である」とはどういう意味か。そして契約の大半が実行時ではなく初期化時に検査されるのはなぜか。
