庭師のアーキテクチャ
司令官は兵士に動けと命じる。庭師は水と光を与えるだけ。あとは植物がやる。
司令官パターン
ソフトウェアアーキテクチャの大半は命令で成り立っています。
コントローラーがサービスに指示を出します。サービスがリポジトリに保存を命じます。リポジトリがデータベースに書き込みを命じます。どの層も、下の層に命令を出している。
class OrderController
{
public function create(Request $request): Response
{
$data = $this->validator->validate($request);
$order = $this->orderService->createOrder($data);
$this->paymentService->charge($order);
$this->inventoryService->reserve($order);
$this->notificationService->sendConfirmation($order);
$this->auditService->log('order_created', $order);
return new Response($order);
}
}
コントローラーは操作の順序を知っています。どのサービスが存在するかを知っています。決済の後、在庫の後、通知の後に何が起きるかを知っています。全員のことを知っている。
新しい要件が増えるたびに1行増えます。エッジケースが増えるたびに分岐が増えます。コントローラーは膨れ続け、いつか誰かが OrderControllerV2 を作ります。元のコードには誰も触れない。
庭師が知っていること
庭師は種に「花になれ」とは言いません。土と水と光を用意します。種は自分の中に必要なものをすべて持っています。条件が整えば、なるべきものになる。
種に GrowthOrchestratorService は要りません。土と水と光に出会えば——植物になります。
命令のないアーキテクチャ
// "コントローラー"の全体
$result = $becoming(new OrderInput($items, $card));
1行です。複雑さが消えたわけではありません——分散したのです。各オブジェクトが自分の運命を持っています:
#[Be([PaymentProcessed::class])]
final readonly class OrderValidated
{
public function __construct(
#[Input] array $items,
#[Input] CreditCard $card,
#[Inject] PriceCalculator $calculator
) {
$this->total = $calculator->calculate($items);
$this->card = $card;
}
}
OrderValidated が知っているのは一つだけです。自分が何であり、次に何になるか。#[Be] は外からの指示ではありません。内側からの宣言です。
存在の条件
司令官モデルでは、コントローラーが何が起きるかを決めます。庭師モデルでは、環境が何が起きうるかを決めます。
final readonly class PaymentProcessed
{
public function __construct(
#[Input] Money $total, // 自分が持っているもの(内在)
#[Input] CreditCard $card, // 自分が持っているもの
#[Inject] PaymentGateway $gw // 環境が与えるもの(超越)
) {
$this->receipt = $gw->charge($total, $card);
}
}
PaymentGateway は種にとっての水のようなものです。オブジェクトはコンストラクタでそれと出会い、変容し、ゲートウェイは消えます。残るのは PaymentProcessed とそのレシート。
内在 + 超越 → 新たな内在
種はDNAを持っています。水と光に出会います。植物が生まれます。水は消えた——植物が成ったものの中に吸収されて。
自己組織化
庭には成長を調整する中央の権威がありません。それぞれの植物が自分の条件に応答します。それでも庭全体は調和している。
OrderInput
↓ 自分の運命を宣言する
OrderValidated
↓ 自分の運命を宣言する
PaymentProcessed
↓ 自分の運命を宣言する
OrderConfirmed
各オブジェクトが #[Be] を宣言し、連鎖が自己組織化します。
新しいステップを追加するならクラスを挿入します。ステップを削除するならクラスを消します。コントローラーの更新は要りません。各ノードは自分の直後の未来しか知らない。
失敗
司令官型のシステムが失敗すると、失敗が伝播します。コントローラーがサービスAを呼び、サービスAがサービスBを呼び、サービスBが例外を投げ、それを理解しない層を遡っていく。
庭師型のシステムが失敗すると、失敗は局所的です。発芽できない種は植物にならない。存在できないオブジェクトは存在しない:
try {
$result = $becoming(new OrderInput($items, $card));
} catch (SemanticVariableException $e) {
// この注文は存在できなかった。理由は正確にわかる。
$messages = $e->getErrors()->getMessages('ja');
}
不正な状態はチェックしません。構造的に不可能だからです。
手放す
一番難しいのは心理的な部分です。プログラマーにとって制御は自然なことです。命令型のコードを書きます——これをやれ、次にこれ、次にこれ。思考が命令になっている。
// 司令官:「まず検証、次に決済、次に在庫確保、次に通知」
// 庭師:「注文が存在するにはアイテムとカードが要る」
#[Be([OrderValidated::class])]
final readonly class OrderInput
{
public function __construct(
public array $items,
public CreditCard $card
) {}
}
50行のコントローラーは、起きるべきことの記述であって保証ではありません。どのサービス呼び出しも失敗しうる。どの前提も間違いうる。
種に成長を命じることはできません。条件を整えることしかできない。
あとは植物がやる。