学習・技術検証用デモ — 本システムは実際の銀行サービスではありません。

公開情報をもとに構築した教育目的のシステムです。実在する個人情報・口座情報・機密情報・認証情報等を入力しないでください。

ミニ銀行ACADEMY SYSTEM LEARNING
教材メニュー
学習進捗0 / 134
あと134項目
INTERMEDIATE · 中級COURSE 04 · SOFTWARE ENGINEERING

トランザクション境界を設計する

金融処理では『順番に処理する』だけでは足りません。どこまでを全部成功・全部失敗にし、再送と競合をどう一度の結果へ収束させるかを設計します。

04 / 11
SOFTWARE ENGINEERING · ソフトウェア工学

境界、transaction、並行制御、脅威、テスト、障害対応をFailure Firstで学ぶ。

先に学ぶ内容 — Prerequisite: 基礎 / 銀行システム

学ぶこと — LEARNING GOALS

このページで学ぶこと

  • Atomicityと業務境界
  • Idempotency keyとfingerprint
  • lock・constraint・rollbackの役割分担

体験29 · TRANSACTION DESIGN(境界を決める)

振込の依頼保存と資金移動の記帳が、別々のタイミングで確定してもよいか確かめよう

まずは予想をひと言。本文を読んだ後、教材内の参照元で実際のRPCとtestを確認します。

体験学習 · STEP 1 / 5
  1. 01
    まず予想する — PREDICT

    まずここだけ:思ったことをひと言書く

    振込のrequest保存と、実際の資金移動(journal記帳)が、別々のタイミングでcommitされてもよいと思いますか?

    正解でなくて大丈夫です。「〜だと思う」から始めてみましょう。

    ヒント1を見る

    片方だけ確定した状態を、利用者や監査は説明できるでしょうか?

    ヒント2を見る

    『同じ意味の事実』はどこまで一緒に確定させる必要があるでしょうか?

    もっと詳しく:この問いで使う専門用語
    • Atomicityは、まとまり全体が成功するか、全部元に戻る性質です。
    • Request fingerprintは、依頼内容を正規化して照合する指紋です。
    • Failure injectionは、処理途中で意図的に失敗させるtestです。
まずはひと言だけで大丈夫です。

この後の本文を最後まで読んでから、実際に操作して確かめるステップへ進みます。

日常語で理解する — PLAIN LANGUAGE

まず、ここだけつかむ

1

transaction境界は、途中で片方だけ残ると業務上壊れる変更をまとめます。

2

冪等性はkeyの存在ではなく、同一内容の再送・異内容の衝突・並行送信を一つの結果へ収束させる契約です。

3

成功testだけでなく、処理途中へ失敗を注入してrollbackを証明します。

順番に理解する — DIFFICULT PARTS, STEP BY STEP

難しいポイントを、順番にほどく

専門用語を覚えることより、「なぜ必要か」「このシステムのどこで使うか」を理解することが目標です。

01

Atomicity

やさしく言うと

まとまり全体が成功するか、全部元に戻る性質です。

なぜ必要?

片側残高だけ減るなどの半端な状態を防ぎます。

Minibankでは

振込のjournal、transfer、cache、auditをDB transactionで結びます。

02

Request fingerprint

やさしく言うと

依頼内容を正規化して照合する指紋です。

なぜ必要?

同じkeyを別金額へ再利用する誤用を検出します。

Minibankでは

送金元・先・金額などから保存済み内容と比較します。

03

Failure injection

やさしく言うと

処理途中で意図的に失敗させるtestです。

なぜ必要?

通常成功するだけではrollback境界の欠落が見えません。

Minibankでは

journal後・業務record後などで失敗させ最終recordを確認する考え方です。

発展 — ADVANCEDもっと詳しく確認する最初の学習では読まなくても問題ありません。仕組みをもっと深く理解したい場合に開いてください。

金融更新を戻す範囲と、失敗後に残す記録

金融更新の仕訳・transfers・残高cache・成功audit(TRANSFER_COMPLETEDと内側の変更trigger)は、confirm_transfer_otpの内側の例外blockでまとめてrollbackします。先に保存済みのrequestは消えません。transfer_auth.consumeによる認可済みintentの消費(consumed_at)、request=failed、TRANSFER_FAILEDは内側のrollbackの対象外で、外側のDB transactionがcommitすると残ります。

TOTPコードの正誤はDBではなくSupabase Authが判定します。DBはNext.jsのtransfer_mfa_broker経由で検証前に試行を予約し、その時点でintentのattemptsを加算します(累積5回まで、間隔30秒以上)。認証に失敗しても変わるのはintentとAuth側の状態だけで、requestはpending_otpのまま、金融データも変わりません。新しい認可がないままconfirmすると、transfer_auth.consumeが42501で拒否し、その呼出し全体がrollbackします。request=failedは金融更新の例外を捕捉した経路です。未捕捉例外・deferred constraint・commit自体の失敗なら、その呼出し全体の変更がrollbackするため、失敗記録の永続化を無条件には保証しません。

場面金融更新外側commit後の状態・event
依頼作成まだ記帳しないpending_otp、TRANSFER_OTP_REQUESTED(event名は従来のまま)。確認コードは生成しない
TOTPの誤入力・Auth検証失敗資金移動に入らない予約時点でintent.attemptsが加算済み(累積5回まで)。requestはpending_otp
新しい認可なしのconfirm(未認可・60秒超過・別session・内容変更)consumeが42501で拒否し、その呼出し全体をrollbackrequestはpending_otp、intentは未消費のまま
認可後の資金移動で捕捉した例外内側の仕訳・transfer・cache・成功auditをrollbackintent消費済み、request=failed、TRANSFER_FAILED
成功金融更新と成功auditを保存request=completed、transfer_id、intent消費済み
外側transaction/commit失敗その呼出し全体をrollbackその呼出しの失敗eventも保証しない。過去のcommit済み依頼とは別

1つのDB transactionへ入れる判断基準

問い同じtransactionが必要な例別処理にできる例
片方だけ成功してよいかjournalとjournal lines、振込と両口座残高通知emailの送信
同時点の事実かKYC状態変更とそのaudit日次集計reportの生成
外部systemを待つかDB内の会計記帳SMS・外部決済network
再試行方法を分離できるか資金移動の一意な確定失敗した通知の再送queue

安全なwrite pathの順番

  1. 01callerと対象ownershipを確認
  2. 02idempotency identityと内容を照合
  3. 03更新対象を決定順でlock
  4. 04lock後の状態・残高・上限を再確認
  5. 05業務recordとbalanced journalを作成
  6. 06派生cacheとauditを更新
  7. 07constraintを通してcommit
  8. 08応答を安全な公開形式へ変換

冪等性はkeyだけでは完成しない

状況期待する結果必要な仕組み
同じkey・同じ内容既存結果を返し、再記帳しないunique identityと保存済みresult
同じkey・違う金額衝突として拒否canonical fingerprint
同じ依頼を同時送信1件だけ確定unique constraintと競合処理
commit後に応答消失retryで最初の結果を取得resultをtransaction内に保存
最初の処理が失敗再試行可能性をerror種別で判断永続状態と一時障害の区別

失敗注入で境界を証明する

  • journal作成後に例外を起こし、業務recordも残らないこと
  • 片方の口座更新後に失敗し、両方戻ること
  • 同じkeyを並行送信し、仕訳が1件だけであること
  • deadlockまたはserialization failure時のretry方針
  • timeout後の再送で最初のresultへ収束すること

実際のMinibankでは

振込RPCは認可、idempotency、口座lock、残高検査、journal、transfer、cached balance、auditをDB処理内で結びます。service層の順番だけでなく、migrationに定義された関数とconstraintを確認することが重要です。

外部通知のようなDB外の副作用は同じtransactionへ直接入れられません。本番設計ではoutboxなど、commit済みeventを安全に外へ配送する境界が追加で必要です。

体験学習の続き · LEARNING LOOP

ここまで読めましたか?続けて確認してみましょう

Atomicity・冪等性の考え方を読んだところで、教材内の参照元で実際のRPCと回帰testを確認し、自分の言葉で説明してみます。

STEP 2〜5 / 5
  1. 02
    仕組みを確かめる — CONCEPT CHECK

    画面操作なしで、仕組みを確かめる

    この仕組みは、Mini Bankの画面には現れません。次のEvidenceとCode Trail、教材本文だけで理解を進めます。

  2. 03
    証拠を確認する — EVIDENCE

    何を見たか、1つずつ確かめる

    Evidence(判断の根拠となる記録)を確認したらチェックします。各項目の「何を見るか」を手がかりにしてください。

    画面を開けない項目は、Source Trail(教材の参照元)と教材の説明で確認できます。失敗を実際に起こす必要はありません。

  3. 04
    コードの処理経路を追う — CODE TRAIL

    コードを書く必要はありません

    ここでは「説明されている仕組みが、実際のMini Bankのどこに実装されているか」を確認します。まずは日本語の役割説明を読み、入力した依頼がどこへ渡るかをつかみましょう。コードの全文を読む必要はありません。

    まず振込を確定させるRPCを見て、次にその境界を検証する回帰testを見ます。どこまでが1つのtransactionかを確かめましょう。

    振込のwrite path。execute_transfer()がどこまでを一緒に確定させるかを探します。

    supabase/migrations/20260823170001_create_transfer_beneficiary_and_otp.sql

    再送・競合・rollbackを検証する回帰test。

    scripts/regression/suites/
    教材の参照元を確認する — Source Trail ↓
  4. 05
    自分の言葉で説明する — EXPLAIN

    LEVEL 1 — やさしい説明(まずここだけ)

    なぜ振込のjournal・transfer・cache・auditを1つのDB transactionへまとめる必要があるのでしょうか? 短い2文を目安に説明してください。

    日常語だけで完了できます。専門用語の数や正誤を自動採点するものではありません。

    ヒント1を見る

    「Atomicity」と「fingerprint」という2つの言葉を使って書いてみましょう。

    ヒント2を見る

    通知メールと資金移動を同じtransactionにできない理由を考えてみましょう。

    もっと詳しく:LEVEL 2 — 専門用語を使った説明(任意)

    Atomicity、Request fingerprint、Failure injectionの3つを使って、transaction境界の設計方法を説明してみましょう。

    挑戦する場合は、上の説明欄に追記できます。追加しなくても完了できます。

    説明の確認ポイント — Checkpoint
    • 1つのtransactionへまとめる判断基準を説明できる
    • 成功testだけではrollback境界を証明できないと説明できる
予想5文字以上・証拠2項目・説明20文字以上で完了できます。

証拠の確認:0 / 2。LEVEL 2(専門用語での説明)は任意です。

入力はこのブラウザに保存されます。Academy教材の進捗とは別の学習メモです。

理解を確かめる — CHECK YOUR UNDERSTANDING

3問で理解チェック

答えを頭の中で考えてから、各項目を開いて確認してください。

Q1通知emailも資金移動と同じDB transactionにできますか?
答え

通常できません。DB外副作用なのでoutboxなど別の信頼できる配送境界が必要です。

Q2同じkeyで別金額が来たら既存結果を返しますか?
答え

返しません。fingerprint不一致として衝突を拒否します。

Q3rollbackをどう証明しますか?
答え

途中地点へ失敗を注入し、関連する業務record・仕訳・残高・auditが半端に残らないことを確認します。

ここまで読めましたか?

完全に暗記する必要はありません。大切な考え方を説明できそうなら押してください。

教材の参照元 — SOURCE TRAIL

根拠と関連教材

CURRENT IMPLEMENTATIONは確認したrepository内の実装です。HISTORICAL IMPLEMENTATIONは導入・修正の履歴で、後続migrationによる再定義があり得ます。RELATED LESSONは教材への案内で、実装の一次Sourceではありません。ASSUMPTIONは仮定、UNKNOWNは未確認を表します。

確認基点: d02eae8914f39cbf06bfe422dc39c71886f82b3e / 2026-09-11。稼働環境への適用確認: UNKNOWN。パス実在の自動検査と、記述した技術的事実の確認は別です。

  • 現在の実装 — CURRENT IMPLEMENTATION — Primary Sourcesupabase/migrations/20260920074653_transfer_totp_intent_authorization.sqlpublic.request_transfer / public.confirm_transfer_otp / public.reissue_transfer_otp / public.prepare_transfer_mfa#128。依頼は確認コードを生成せずotp_plaintext=null。confirmはtransfer_auth.consumeで認可済みintentを消費してからexecute_transferを呼ぶ。reissueは常に42501。稼働DBへの適用はUNKNOWN。
  • 現在の実装 — CURRENT IMPLEMENTATION — Primary Sourcesupabase/migrations/20260920083426_require_fresh_totp_on_intent_session.sqlpublic.transfer_mfa_broker#129。intentのAuth session自身に、予約・検証より後のTOTP AMRを要求する最新broker定義。
  • 現在の実装 — CURRENT IMPLEMENTATION — Primary Sourcesupabase/migrations/20260920115556_scalar_journal_posting_atomic_cache.sqlpublic.execute_transfer最新定義。ledger.post_journal_entryの戻り値をscalar uuidで受ける。検証・lock・上限・冪等性は従来どおり。既存ACLは維持。
  • 現在の実装 — CURRENT IMPLEMENTATIONsrc/app/api/v1/transfers/mfa/route.tsintent準備・challenge束縛・試行予約・認可完了のserver入口。TOTPコードは受け取らない。
  • 現在の実装 — CURRENT IMPLEMENTATIONsrc/components/auth/TransferMfa.tsxブラウザがSupabase Authへchallenge/verifyを直接送る部分。成功の自己申告は認可にならない。
  • 現在の実装 — CURRENT IMPLEMENTATIONsrc/app/api/v1/transfers/route.ts認証済みの旧直接振込入口はOTP_REQUIREDを返す。
  • 現在の実装 — CURRENT IMPLEMENTATIONsrc/lib/errors/apiError.tsOTP_REQUIREDのHTTP statusは409。
  • 現在の実装 — CURRENT IMPLEMENTATIONscripts/mfa/intent-db.test.mjsintent束縛・回数/期限・単回消費・session検査のSQL受入test。Auth行はfixtureで、実Authの証明ではない。当日のPASSも意味しない。
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsrc/lib/config/demoOtpDisplay.ts#128以前のデモコード表示フラグ。v7ではDBがコードを返さず、Productionでは常にfalse。
  • 過去の実装 — HISTORICAL IMPLEMENTATIONscripts/regression/suites/transferOtp.mjs#128以前のデモOTP(誤入力・再発行・期限)の回帰test。v7の認可方式の根拠にしない。
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/20260823170001_create_transfer_beneficiary_and_otp.sqlexecute_transfer()振込write path
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/20260819155222_create_journal_balance_constraint_trigger.sqlcommit時の借貸一致
  • 現在の実装 — CURRENT IMPLEMENTATIONscripts/regression/suites/再送・競合・rollbackの検証