第9章: モナド/クライスリ(効果境界の設計)

副作用を domain 判断と同じ場所に置くと、 仕様とテストが急速に読めなくなります。 第9章では、pure core / impure shell を 効果境界として整理します。

本章の見せ場は、pure core / impure shell 図と core / shell 対応表です。 どの判断を純粋に保ち、どの処理を境界へ追い出すかを、その場で確認できます。

学習ゴール

  • pure core / impure shell の設計原則を説明できる
  • 失敗モデル・リトライ・冪等性・監査ログを「効果」として境界へ隔離できる
  • AIへの指示として「副作用を勝手に増やさない」「境界を守る」を明文化できる
  • 図式(Diagrams)から、効果境界のテスト戦略へ落とせる
  • 運用要件(監査・信頼性)を破壊しない実装委任ができる

圏論コア(定義・直観・ミニ例)

モナド(Monad)とクライスリ(Kleisli)は、効果(IO/DB/例外/リトライ等)を含む計算を合成可能にするための枠組みです。本章では、厳密な公理よりも設計上の直観を優先します。

  • 効果付きの型: M A(例: Result<A, E>, Task<A>, IO<A>
  • 効果付きの射(クライスリ射): A → M B
  • 合成: A → M BB → M C を合成して A → M C を作る(エラー伝播や副作用の順序を含む)

ミニ例(型シグネチャと変換の形)は次のとおりです。

  • f: A → Result<B, E>
  • g: B → Result<C, E>
  • h: A → Result<C, E>h = kleisliCompose(f, g)、圏論の記法では h = g ∘ f に相当)

h は次のように「失敗を伝播し、成功なら次の変換へ進む」形になる(flatMap/andThen/bind 等、API 名は実装により異なる)。ここで kleisliCompose(f, g) は「先に f、次に g」を意味し、圏論の記法では g ∘ f に対応する。

type Result<A, E> = { ok: true; value: A } | { ok: false; error: E };

const kleisliCompose =
  <A, B, C, E>(f: (a: A) => Result<B, E>, g: (b: B) => Result<C, E>) =>
  (a: A): Result<C, E> => {
    const rb = f(a);
    return rb.ok ? g(rb.value) : rb;
  };

// h = kleisliCompose(f, g) (= g ∘ f)

直観は次のとおりです。

「効果を型に押し上げ、合成規則を明示する」と、AIに委任しても境界が崩れにくい。逆に、効果が暗黙(グローバル状態、隠れDBアクセス、暗黙リトライ)だと、合成や検証が破綻します。

ソフトウェア設計への射影(どこに効くか)

AI委任が難しいのは、効果が絡む領域です。

  • IO/DB/外部API
  • 例外、再試行、タイムアウト
  • 冪等性、監査ログ

本章の基本方針は pure core / impure shell です。

  • pure core:
    • ドメイン判断(状態遷移、検証、計算)を純粋関数として定義する
    • 入力→出力が明確でテストしやすい
  • impure shell:
    • DB/外部API/監査/リトライなどの効果をここへ閉じ込める
    • 失敗モデル(failures)と再試行規則を Context Pack として固定する
graph LR IN["Command / Input"] --> CORE["pure core(判断/状態遷移)"] CORE --> SHELL["impure shell(DB/外部API/監査/リトライ)"] SHELL --> OUT["Result / Output"]
pure core / impure shell 図の fallback SVG。入力が pure core に入り、その結果が impure shell を経て出力になる。
図: pure core / impure shell。入力は純粋な判断ロジックに入り、その結果だけを効果境界へ渡して DB・外部 API・監査・再試行を処理します。

共通例題(注文処理)の観点は次のとおりです。

  • PlaceOrder は在庫引当、監査、状態遷移を含む。ここで効果を増やすと、冪等(D1)や監査整合(D2)が壊れやすい。

設計成果物(テンプレ:表/図式/チェックリスト)

参照先は次のとおりです。

効果境界テンプレ(最小)

constraints:
  effect_boundary:
    pure_core: [] # 純粋にできる判断(状態遷移、計算、検証)
    impure_shell: [] # 効果(DB/外部API/監査/リトライ)
    failures: [] # 失敗モデル(variant列挙)
    retry: {} # 再試行方針(条件、回数、バックオフ、タイムアウト)
    idempotency: {} # 冪等性の鍵(キー、スコープ、保持期間)
    audit: {} # 監査イベント(必須項目、改竄検知)
    diagrams: [] # 効果境界に関する不変条件(例: D1, D2)

例(方針イメージ)は次のとおりです。

pure core:
  decidePlaceOrder(order, inventory) -> Decision
impure shell:
  loadOrder(orderId) -> IO<Result<Order, NotFound>>
  reserveInventory(order) -> IO<Result<Reservation, OutOfStock>>
  appendAudit(event) -> IO<Unit>

AIエージェントへの引き渡し

効果境界は、AIが勝手に“便利化”しやすい部分です。以下を禁止事項として明確化します。

  • 副作用の無断追加(DBアクセス、外部呼び出し、非同期化、リトライ)
  • pure core への効果混入(テスト不能化)
  • 監査/冪等性の破壊

指示の書き方(抜粋)は次のとおりです。

pure core / impure shell を維持せよ。pure core にIO/DB/外部APIを追加してはいけない。
failures/retry/idempotency/audit を Context Pack の通りに実装せよ。勝手に増減してはいけない。
図式(Diagrams)を満たすテスト観点を出力し、破綻を検知できるようにせよ。

検証(テスト観点・可換性チェック)

効果境界のテスト戦略は、層ごとに分離します。

  • pure core(単体):
    • 状態遷移、計算、検証ロジック
    • 図式(Diagrams)由来の性質をプロパティとして検証しやすい
  • impure shell(統合):
    • DB/外部API/監査/再試行
    • 冪等性(D1)や監査整合(D2)を観測点(状態/イベント)で検証する

演習

  1. 共通例題の PlaceOrder を取り上げ、pure core と impure shell を分解する
  2. failures/retry/idempotency/audit をテンプレに落とす
  3. D1(冪等)/D2(監査整合)を壊さないためのテスト観点を列挙する
  4. AIに委任する場合の禁止事項(副作用の無断追加、境界破壊)を Context Pack に追記する
  5. Context Pack を更新したら検証する(編集対象に合わせてパスを置き換える)。
    • (初回のみ)python3 -m pip install -r scripts/requirements-qa.txt
    • minimal lint を実行する。
      • python3 scripts/validate-context-pack.py <your-context-pack.yaml>
      • 例: docs/examples/common-example/context-pack-v1.yaml
    • schema validation を実行する。
      • python3 scripts/validate-context-pack-schema.py <your-context-pack.yaml>
      • 例: docs/examples/common-example/context-pack-v1.yaml
    • (任意)CI相当の一括チェックとして npm run qa を実行する。
    • 検証コマンドの SSOT を確認する。

まとめ

  • 効果(IO/DB/例外/リトライ等)を型と境界に押し上げ、合成と検証を破綻させない
  • pure core / impure shell を維持すると、AI委任後も検証可能性が保たれる
  • 冪等性・監査・再試行は境界の契約として固定し、図式(Diagrams)とテスト観点へ接続する

次章への接続

  • 第10章では、ここで分離した判断と副作用を、1つの変更要求の通しケースへ戻す。