Action X-Ray

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

複雑な業務操作はふつう、メソッド呼び出しの連なりとして姿を現す。開発中ならデバッガで走らせ、実行の道筋をたどり、途中の状態を見ることができる。それが、込み入った気づきにくい不具合を見つける助けになる。だが問題によっては、本物のデータと本物の負荷のもとでしか現れない。そのときに要るのが online デバッガだ — 実際の実行を透かして見て、プロセスを止めずに原因までたどり着かせてくれるもの。Action X-Ray が与えるのは、まさにその力である。

ドキュメント ↗

本番にデバッガは差し込めない

デバッガは操作を内側から見せてくれる — けれど本番には差し込めないし、動いている実行にはつかまるところがない。

開発中なら、デバッガはどのステップでも実行を凍らせる。引数も、途中の値も、この先へ渡っていくものも見える。本番ではそうはいかない。一つのリクエストのためにプロセスを止めるわけにはいかないし、まれな入力は再現しにくい。走っている実行をそのまま眺めることもできない。呼び出しの連なりはコールスタックの中に、その途中の値はローカル変数の中に生きていて、呼び出しが返った瞬間にすべて消える。ログに残るのはあらかじめ選んでおいた値だけ。技術的なトレースは呼び出しとその所要時間を見るが、そのどれが業務上のステップで、あいだのどの値が大事なのかは知らない。原因は間接的な手がかりから推し量るしかなくなる。

実行中のデータは存在している — デバッガがそれを証明している。足りないのは別のものだ。実行そのものと各ステップに、あらかじめ分かっている境界があること。そこへ online の観測を掛けられること — プロセスを止めずに。

開発
validate
reservebreakpoint{ validated_items: 3 } · すべての値が見える
charge

どのステップでも一時停止 — state がすべて見える

本番
リクエストレスポンス / 例外

止められない — 途中が見えない

ドキュメント全文 ↗

実行の境界はアーキテクチャがすでに決めている

実行を一つのまとまりとして観測するには、それに正確な境界が要る — そして各ステップにも。

AOA では操作とは、Params → Result という外側の境界を持つ Action だ。それを走らせるのは機械 — Action を実行する AOA のエンジンであり、実行がどこで始まりどこで終わるかをあらかじめ知っている。内側では道筋が Aspect に分かれ、それぞれが自分の途中状態 state を持つ。アスペクトは直前の state のスナップショットを受け取り、自分の新しい state を返す。それは直前のものを付け足すのではなく丸ごと置き換える。だから次のステップに要るものはアスペクトが明示的に運び、ステップのあいだで何かがひとりでに漏れることはない。@result_* は境界のその場で出力を検査する。これらの境界は観測のための道具ではなく、操作そのものの造りだ — だがまさにそれが、各ステップの入口と出口と state が見える、あらかじめ分かった地点を与えてくれる。

機械はいつでも実行の枠と、契約に沿った道筋のすべてを知っている。どの Aspect に何が入り、どんな state が出たのか。「一回の実行」という観測できる成果物は、すでにアーキテクチャ自身が組み上げている — あとはそれを外へ出すだけだ。

Action の外側の境界
Params
state(前){ }
01 · 観測点validate@result_* ✓
state(後){ validated_items: 3 }
state(前){ validated_items: 3 }
02 · 観測点reserve@result_* ✓
state(後){ …, reservation_id: 'res_42' }
state(前){ …, reservation_id: 'res_42' }
03 · 観測点charge@result_* ✓
state(後){ …, payment_id: 'pay_91' }
Result
ドキュメント全文 ↗

Online debug:動いている実行が外から見える

境界が分かっているのだから、機械はそのそれぞれでイベントを発する — 外側の観測者はそれらから実行の生きた姿を組み立てる。

外側の境界では実際の ParamsResult が見え、内側のそれぞれでは入ってくる state と出ていく state、所要時間、そしてエラーが見える。機械はこれらの地点をすべて知っていて、lifecycle events を自動で発する。プラグインはそれを外から受け取り、その一回の実行の online な投影を組み立てる — 実際の入口、出口、エラー、時間を伴ったステップの木だ。開発でも、テストでも、本番でも同じように。業務メソッドの中にログ呼び出しも、手書きのタイマーも、トレース用のコードも要らない。

これは差し込まれたデバッガでもなければ、プロセスの停止でもない。安全な online の投影だ。観測者に見えるのは契約が許したものだけ。機微なフィールドは opaque のままで、プラグイン自身の障害は切り離され、操作の結果を変えない。それがまさに Action X-Ray である。

動いている実行CreateOrderAction
start · Paramsvalidate · state → state′ · 0.4 msreserve · state′ → state″ · 12 mscharge · error · 3 msfinish · Result · 15 ms
lifecycle events
plugin · 操作の外側実行の online な投影
  • start · Params
  • validate · state → state′ · 0.4 ms
  • reserve · state′ → state″ · 12 msopaque
  • charge · error · 3 ms
  • finish · Result · 15 ms

プロセスは止まらない · dev でも test でも本番でも同じ

プラグインは機械の生成時に渡す — 業務メソッドはそのまま:

service/machine.py
plugin = OpenTelemetryPlugin(    tracer_provider=tracer_provider,    logger_provider=logger_provider,    service_name="checkout-service",)# The plugin lives outside the Action; business methods stay untouched.machine = ActionProductMachine(plugins=[plugin])result = await machine.run(Context(), CreateOrderAction(), params)
ドキュメント全文 ↗

テストでも同じレントゲン

同じ online のスナップショットはテストでも手に入る。TestBench は同じ操作を同じ機械に通す。

TestBench はテスト専用の可視性を作り出さない。同じ Action を、同じ機械と同じ境界に通すだけだ。変わるのは外の世界だけ — 本番の Context、Resource、Gateway の代わりにテスト用の実装を取る。ロール、パイプライン、checker、そして Result の組み立ては本物のまま。操作は丸ごと走らせることも、境界の一つ — Aspect、summary、compensator — で止めることもできる。業務コードに専用のフックを仕込むことなく。

テストが確かめるのは、本番で手に入るのと同じ投影だ。最後の Result だけでなく、途中のどの state も。変わるのは操作を取り巻く現実であって、操作そのものでも、その観測のしかたでもない。

PRODUCTION本物の Context · Resources · Gateways
同じ Action · 同じ機械ロール · pipeline · checkers · Result の組み立てまったく同じ online の投影
TESTテスト用の Context · fixtures · mocks
Action 全体 — システムの振る舞い境界を一つ — 正確な切り分け

同じ Action を TestBench で — 丸ごと、あるいは境界を一つだけ:

tests/test_create_order.py
inventory = AsyncMock(spec=InventoryGateway)inventory.reserve.return_value = "res_42"bench = TestBench().with_user(    user_id="customer", roles=(CustomerRole,)).with_mocks({InventoryGateway: inventory})# The whole operation — same machine, same boundariesresult = await bench.run(CreateOrderAction(), params, rollup=False)# Or stop at a single boundary and inspect its statestate_after = await bench.run_aspect(    CreateOrderAction(), "reserve_aspect",    params=params, state={"validated_items": 3},)assert state_after["reservation_id"] == "res_42"
ドキュメント全文 ↗

Online debug がアーキテクチャの性質になる

AOA は Action の外側の境界と、その Aspect の内側の境界を定め、途中の state を契約に変える。実行のあいだ、その同じ境界が lifecycle events の座標系になる — かつてはデバッガの下でしか、あるいは手で計器を仕込んだあとでしか得られなかった可視性が、いまはどの実行についても online で手に入る。本番も含めて、操作の中に基盤のコードを置くことなく。

デバッグある一回の実行について、各ステップの前後の state、エラー、そしてその正確な境界が見える — デバッガを差し込むことなく。
本番同じ投影が、本物の負荷のもとで online に手に入る。Action を変えない外部の plugin を通して。
テスト確かめるのは同じ操作と同じ境界。差し替わるのは、それを取り巻く外側の現実だけ。
Action X-Ray · aoa.run