Fintech Engineering Handbook — お金を扱うシステムの設計パターン整理¶
作成日: 2026-07-20 出典 / きっかけ: Fintech Engineering Handbook(著: Voytek、GitHub: Krever/fintech-engineering-handbook)https://w.pitula.me/fintech-engineering-handbook/ 関連: メルペイ_決済常駐aiエージェント_設計と5層防御_整理 / オントロジーと知識グラフ_金融業界の実践例_整理
0. 要点(3行)¶
- 貫く芯は3原則「No invented data(生まない)/ No lost data(失わない)/ No trust(信じない)」。すべてのパターンはこの3つの言い換えである。
- 効きどころは絶対値でなく「対比」で掴む。台帳=残高でなく動きの和/金額=数値でなく整数(minor-unit)か BigDecimal/Webhook=事実でなくヒント/外部=真実でなく検証対象。
- 暗号資産交換所・トレジャリー・台帳の実務を、そのまま設計原則に落とした「教科書版」。自分の実務の暗黙知を言語化・チェックリスト化するのに最適。
1. 3原則 — すべてはこの言い換え¶
| 原則 | 意味 | 担保する仕組み | 破れたときの怖さ |
|---|---|---|---|
| No invented data | お金は無から生まれない。重複・恣意的な残高更新を防ぐ | 冪等性・重複排除・リコンサイル | 検知漏れの重複1件で「お金を鋳造」してしまい下流に不可逆な誤りが伝播 |
| No lost data | お金に起きたことは全て永続化・追跡する | フル精度・at-least-once・イベントソーシング・監査証跡・不変性 | 失われた取引は復元不能。監査官が履歴を再構成できない |
| No trust | 外部・内部・世界のいずれも信じず能動的に検証する | 署名検証・複数独立ソースの突合・壊れた前提で盛大に落ちる・境界でのスキーマ検証 | 外部は失敗し嘘をつき順序も狂う。楽観的前提は静かに破綻する |
芯の一文(本書の言葉): "No invented data. No lost data. No trust."
2. 金額の表現 — 数値ではなく「型」で持つ¶
2-1. 精度(Precision)¶
float は原則使わない(IEEE-754 は予測不能・不可逆な精度損失を蓄積する。0.1 + 0.2 ≠ 0.3)。選択肢は4つで排他ではなく組み合わせる。
| 表現 | 使いどころ | 対比で言うと |
|---|---|---|
| float / double | ざっくり見積もりのみ。お金にはほぼ不可 | 速いが壊れる |
| 任意精度(BigDecimal 等) | 中間計算の連鎖(FX 換算・手数料・価格計算)。丸め位置を自分で制御 | 遅いが正確・制御可能 |
| minor-unit 整数(推奨・法定通貨) | 保存。最小単位の整数で持つ(€12.34 → 1234)。ISO 4217 が通貨ごとの精度を定義(JPY=0桁・KWD=3桁・2桁と決め打ちしない) |
中央銀行の精度に一致・誤差なし・高速 |
| 有理数(分数) | どこでも精度損失を許さないとき。最も数学的に正しいが最遅・変換で精度を失う | 理想だが実務は稀 |
- 保存と計算は別決定: 整数で保存し、連鎖計算だけ BigDecimal。
- クリプトは精度が資産ごと: ERC-20 の
decimals(多くは18)/BTC=satoshi(8桁)/ETH=wei(18桁)。64bit は溢れるので任意幅整数が要る。 - シリアライズの罠: 素の JSON 数値は多くのパーサで double になる。文字列
"12.34"か minor-unit 整数で送り、number で送らない(境界で浮動小数点誤差を再注入しないため)。
2-2. 丸め(Rounding)¶
丸めは不可避だが明示的に行う。4原則: 1. 丸めは業務判断: 保守的(切り捨て=無い金を使わない)/banker's rounding(half-even=統計的偏りを減らす)。端数を誰が取るかは税務・法務上の意味を持つ。 2. できるだけ丸めない: 境界(永続化前・表示前・システム境界越え)でのみ丸める。精度を長く保つほど文脈に応じた正しい丸めの選択肢が残る。 3. 丸めは和を壊す: €10 を3分割して各 €3.33 にすると合計 €9.99 で €0.01 残る。残差を捨てず、専用の丸め口座(rounding tolerance account)で受ける。 4. 丸め規則を台帳スキーマ・監査証跡に記録する(事後検証のため)。
2-3. 通貨(Currency)¶
金額と通貨は不可分。
- Money newtype に金額+通貨コードを同梱(EUR と USD をうっかり足す事故を型で防ぐ)。
- 異通貨の算術を禁止。変換は厳密に制御されたレートで明示的にのみ。
- 通貨集合を制御(任意のコードを受けない・境界で検証)。
- ISO 4217 は法定通貨のみを一意識別。クリプトは (network, contract address) のタプルで識別(Ethereum の USDC ≠ Polygon の USDC)。
- ペッグ ≠ 原資産: WBTC ≠ native BTC、USDC ≠ 実 USD。台帳では別資産として扱い、変換は明示レートで(誤った等価・意図しないクロス担保を防ぐ)。
2-4. FX レート¶
レートは方向性を持ち時間に縛られる。 - 方向性: EUR/USD ≠ USD/EUR(単純な逆数でない)。取引所では bid/ask にスプレッドがあり買いと売りで価格が違う。 - 時刻が効く: current-time レート(今の評価額)と value-date レート(基準日の税額・評価損益)は別物。どちらを使ったか記録する。 - 2種類のレート: - 取引レート(transactional): 実際に換算が起きたレート。直接保存せず、元金額と結果金額から導出(100 EUR → 110 USD なら 1.10)。 - 参照レート(reference): 評価・等価用(ECB・Bloomberg mid・CME CF)。誰も実取引しない価格。 - canonical なレートは存在しない: 会場・算出法で変わる。レートの出典と時刻を保存データに含める(監査・リコンサイル・事後再評価のため)。
3. お金の記録 — 台帳(The Ledger)¶
3-1. 複式簿記¶
すべての動きを (credit口座, debit口座, 金額) で記録。お金は口座間を移動するだけで、生成も消滅もしない。
- 残高は保存せず導出(口座への全movementの和)。台帳が真実で残高は射影(projection)。任意時点の残高を再構成できる。
- 口座は型を持つ(Asset は debit で増/Liability・Equity は credit で増)。会計等式 Assets = Liabilities + Equity + Revenue − Expenses が常に成立。
- 1トランザクション=多movement: カード購入なら「利用者を debit /加盟店を credit /手数料を expense へ debit /手数料収益を revenue へ credit」。同一タイムスタンプ・参照IDを共有。
- posted は不変(規約): 訂正は元を消さず補償エントリを追加。元と訂正の両方が履歴に残る。
3-2. 3つの時刻(value / booking / settlement)¶
| 時刻 | 意味 | 使いどころ |
|---|---|---|
| value time | 実際に起きた瞬間(当事者が合意した日) | 報告・税・利息計算 |
| booking time | システムが記録した瞬間(ほぼ必ず value と乖離) | 追跡・内部監査・どの会計期間に載るか |
| settlement time | 資金が実際に動いた瞬間。T+X で表す(カードは T+3 が多い・内部振替は即時) | 実際の資金移動の把握 |
なぜ3つ全部取るか: created_at 1本に潰すと後から復元できない情報が消える。特に value と booking が別の会計期間にまたがるとき(12/31 value・1/2 booking は別会計年度)に効く。
3-3. 監査と監査証跡¶
監査=外部の規制当局の精査。監査証跡=現状に至った完全・不変の記録。全変更に what / when / who / why を記録する。 - 監査が検証すること: 資金分別(利用者資金と会社資金を混ぜない)・収益の網羅性・残高の正しさ(会社の保有 ≥ 利用者への負債か)・アクセス統制。 - 対象は資金移動だけでなく手動介入・設定変更(手数料表・レート出典・上限)・権限変更も。決定表/ルールエンジン(DMN・Drools)ならどのルールがどの入力で発火したかまで残す。
3-4. イベントソーシング・不変性・訂正¶
- イベントソーシング: 状態+別ログでなくイベントだけ保存し状態を導出。複式台帳そのものが好例。ただしどこでも使う必要はない(台帳以外の周辺ドメインは通常の状態モデル+変更ログで十分なことも)。射影(projection)は作り込みが要る/スキーマ進化に備える(イベントは何年も生きる・upcasting・フィールドは削らず追加のみ)。
- 不変性の担保3段: ① by construction(append-only・UPDATE/DELETE 権限剥奪=最強)② runtime checks ③ post-factum(ハッシュチェーンで改竄検知)。
- 実務の妥協: 「石に刻む」のは外部に報告した後だけ。報告前(当期内)なら早期発見時に in-place 修正も可。だから報告スケジュールを厳密に管理する。
- 訂正: 元を編集しない。Reversal(全額を打ち消す)か Correction(差額を計上)を追加し、双方向にリンク。既に締めた期間には backdate できない(発見日の期に載る)。
4. お金のフローを動かす(Executing Flows)¶
4-1. 不変条件(Invariants)¶
「常に成り立つべき性質」(会計等式・残高≥0・日次出金上限)。3段で担保: by construction(不正な状態を表現不能に)/runtime(アサーション・property-based)/post-factum(夜間ジョブ・リコンサイルで出荷済みバグを捕捉)。3つは補完関係で併用する。
4-2. 資金予約(Funds Reservation)¶
同じ金を2つの並行取引が裏付ける競合を防ぐ。total balance ≠ available balance(available = total − 予約)。
[available をチェック+予約] --(単一の linearizable ステップ)--> [外部処理(compliance/provider)] --> [実額で settle・残りを release]
└ 失敗したら release(資金は available へ戻る)
4-3. オーバードラフト¶
- 意図的(与信商品・別の overdraft 負債口座でモデル化)と非意図的(settlement が見積りより大・reversal が資金流出後に着弾)。
- 決定的な区別: 「Forbidden ≠ Unrepresentable」。
balance ≥ 0を unsigned や CHECK 制約で表現しない。表現できない系は負残高を強いられると crash / 0にclamp(=金を鋳造)/ 破滅的に失敗する。→ 残高≥0 は不変条件として runtime で強制し、違反は post-factum で検知。発生時は明示的に計上・回収(次の入金と相殺/返済請求/損失口座へ write-off)。
4-4. 冪等性(Idempotency)¶
分散系は exactly-once を保証できない→同じメッセージ2回で処理1回にする。 - 明示的 idempotency key を使う(payload から導出=amount で重複判定は脆い)。 - エラーの再生方法を決める: エラーを冪等な結果として保存し replay する方が単純(クライアントは新 key で再試行できる)。 - 順序外の再試行にも冪等(例: 既に release 済みでも hold を冪等に)。 - 時間窓の罠: 「24h だけ重複排除」は実装が楽だが正しさを犠牲にする(本当に必要な時だけ)。 - 再試行をテストで焼く(全呼び出しを自動再送する middleware を統合テストに仕込む)。
4-5. 完全な再開可能性(Full Resumability)¶
フローは複数ステップ・時間をまたぎ、任意の2ステップ間で落ちうる。半端な状態を必ず回復可能に。 - 進捗を永続化(メモリでなく)。明示的な状態機械にし、各ステップ完了を次の開始前にコミット。 - 止まったフローを誰かが再開(scheduler/worker/poller。オーケストレータの crash で永久放置しない)。 - 各ステップは再実行安全=冪等。 - roll forward か compensate: 外部効果は取り消せない(呼んだら un-call 不可・DB rollback は効かない)。前進リトライか補償アクション(saga)。 - 道具: Temporal / Camunda / AWS Step Functions 等の durable execution。
5. 外部の世界(The External World)¶
5-1. API を叩く¶
相手のコード・品質・稼働は制御不能。誤動作する前提で防御的に。 - スキーマを信じない(欠落・型変化・null)。必要な部分だけ境界で検証し、想定外は盛大に落とす(不要な部分まで検証すると相手の契約違反で無用な障害を招く)。 - 稚拙な実装を前提に(URL にトークン・精度損失・HTTP 200 でエラーボディ・独自日付)。「苛立たず仕事だと思え」。 - 全呼び出しは失敗する(リトライ・タイムアウト必須)。circuit breaker は多くの場合任意(自分のレイテンシ・有限資源を守る時に)。 - クォータを napkin math で先読み(週末障害を防ぐ)。 - 全 request/response を構造化保存(Redshift 等)=監査証跡・係争時の証拠・バグ後の再処理素材。 - プロバイダ冗長(重要部は複数ソースで突合。高価だが必要なら)。sandbox を信じない(本番と乖離。canary で本番テスト)。
5-2. Webhook¶
Webhook は未認証・順序なし・欠落しうる・重複しうる「ヒント」。事実として扱わない。 - 順序を仮定しない(届いた内容で状態を上書きせず、API に現状を問い合わせて突合)。 - 内容を使わずトリガーとしてのみ使うのが良い作法(ただし API が eventually consistent で webhook に遅れることがある→リトライ準備)。 - 配信を仮定しない(欠落する→独立プロセス=リコンサイルで補完)。単一配信を仮定しない(重複する→冪等に処理)。 - 速く ACK・非同期処理(生イベントを durable に保存したら即 2xx。同期で遅いと発行側が timeout→再送で負荷倍増)。 - 生ペイロードを verbatim 保存(監査証跡・再処理用)。 - 署名検証は「受信した生バイト」に対して行う(再シリアライズするとバイトが変わり署名が壊れる。HMAC が普通)。
5-3. 確実な通知(Outbox / CDC)¶
状態変更の外部公開(Kafka・webhook)は不確実。「publish 成功したが応答なし→rollback」「publish 失敗→rollback せず」の不整合。 - Outbox パターン: 「発行意図」を状態変更と同一トランザクションで専用ストアに書き、別プロセスが成功まで再送(atomicity と delivery を分離)。 - CDC(Debezium・AWS DMS): WAL/レプリカログから確定変更を検知しイベント化(漏れない・ただし内部スキーマが consumer に漏れる)。 - ほかに listen-to-yourself・イベントソーシング。 - 全配信は at-least-once(relay が publish 後・記録前に crash→再送)。consumer は冪等で安定 event id で重複排除。
5-4. リコンサイル(Reconciliation)¶
2つの独立系のドリフト(欠落 webhook・台帳にあるが provider に無い・同一取引で額が違う)を整合させる安全網。 - cadence(時次/日次/月次/年次)・ドリフトの性質(欠落は易・「額が違う」は難/T+3 なら3日は未整合=誤アラートにしない)。 - 突合アルゴリズム: 外部 provider ID を自系に保存すると素直に突合できる(無ければ額+時刻のヒューリスティック)。 - one-to-many(1 settlement が多取引をカバー)。 - 単純な上書きで整合させない(各差分を理解し補償レコード・再処理で first-class に修正)。 - 芯: 「単一ソースを正しいと信じず、独立ソース間で検証する」/「落ちた事実(欠落 webhook・未 settle 送金)を消える前に捕まえる安全網」。
6. 統制とアクセス(Controls & Access)¶
6-1. 職務分掌 & 4-eyes¶
一部の操作は信頼できる1人でも不十分。segregation of duties(誰も全工程を所有しない)+four-eyes / maker-checker(効力発生前に2人目の承認)。 - 対象: 大口/手動出金・手動台帳訂正・トレジャリー/コールドウォレット移動・手数料/上限変更/コードmerge・本番デプロイ・インフラ変更。 - 承認自体が証跡(誰が申請・誰が承認・両者が別人であること。でなければ統制は証明不能)。 - break-glass(緊急override)を明示的・厳重監査で用意(硬直した統制は裏道を招く)。
6-2. アクセス制御¶
- 最小権限(RBAC で役割ベース=レビュー可能に)。
- 認可変更は証跡が要る(付与/剥奪は資金移動と同じ sensitive event。what/who/why を記録)。
- 定期的なアクセスレビュー(recertification)=アクセスへの post-factum チェックでドリフトを捕捉。
6-3. 変更証跡(SDLC)¶
規制環境ではコードが本番に至る過程も監査対象。 - ソース管理が記録(commit 履歴=著者・レビュー・チケットで what/who/why)。署名コミット・保護ブランチ・shared history を force-push しない。 - レビュー/パイプラインを必須化(任意でなく。「規律は監査で通らない」)。 - デプロイを追跡可能に(どの版が誰の手でいつ出たか)。芯: 「システムが delivery 統制を強制する。人の記憶に頼らない」。
7. テスト — 期待出力を列挙できない世界の戦い方¶
操作空間が巨大で面白い失敗は組み合わせに潜む。複数技法を併用。
| 技法 | 何を担保 | 勘所 |
|---|---|---|
| property-based | 不変条件・金額計算(「任意の postings 列で帳簿が balance する」) | フレームワークが人手で書かない意地悪ケースを生成 |
| ステップ間の不変条件チェック | 操作列の各ステップ後に検証 | 手動不可なのでアサーション自動注入のハーネスが要る |
| 生成的な冪等テスト | 全操作を自動で2回叩き2回目の影響ゼロを検証 | 冪等性を「性質」として扱う |
| crash & resume 注入 | 長フローの再開可能性 | 各ステップで失敗を注入 |
| round-trip | 精度損失・シリアライズバグ | encode→decode / convert→convert back で元に戻るか(許容誤差内か) |
| golden | 難解な計算(手数料内訳・明細) | レビュー済み結果を pin し差分で異常検知 |
| 後方互換 | イベント/レコードは何年も生きる | 旧フォーマットの実ペイロード corpus を保持し現行コードで読めるか(スキーマ変更が履歴を静かに壊すのを防ぐ) |
| 本番テスト | sandbox は本番と乖離 | canary・小 blast radius・synthetic transaction(小額を継続的に流すヘルスチェック)。ただし実際に金が動くので同じ台帳/リコンサイル/監査を通し、明確にタグし、通常の訂正/reversal で後始末(裏道で帳簿を迂回しない) |
8. E2E 例: クリプト出金(本書 Appendix B の型)¶
利用者が 0.5 ETH を外部アドレスへ出金=不可逆な外部効果でお金が出る。全原則が1フローに凝縮する。
[1] 到着時に idempotency key ── 再送されても出金は1回
│
[2] 不可逆の前に資金予約 ── 0.5 ETH + 推定network fee を available に対して予約
│ (残高チェック+予約は単一 linearizable step)
[3] compliance ゲート(数日) ── 制裁/AML/宛先スクリーニング。外部呼(遅い・失敗・嘘)
│ 落ちても永続状態から独立driverが再開・予約は維持
│ 日次出金上限は認可と atomic に強制・全判断を証跡へ
[4] on-chain ブロードキャスト ── 冪等必須(再開時は「再ブロードキャスト」でなく chain 照会)
│ 実 fee は事前不明→見積り予約・実額 settle・残差 release
[5] finality 待ち→台帳へ posting ── 1確認では不十分(reorg)。十分な確認後に複式記帳:
│ 利用者を debit・External On-Chain を credit
│ network fee は expense・service fee は revenue
[6] 夜間リコンサイル vs chain ── 未確認ブロードキャスト・fee 差異を安全網で捕捉
(ほかに Appendix B には カード入金〔webhook を信じず clearing 口座経由・T+X の one-to-many settlement・chargeback は補償エントリ〕と アプリ内換算+cashback〔cashback を「無から生む」誘惑に抗い promo/expense 口座から複式で funding〕の2例がある。)
9. まとめ — いつ効くか / 実務対応表¶
「状況 → この本のどの型を出すか」の早見表:
| 状況 | 出す型 |
|---|---|
| 金額を DB/JSON でどう持つか | minor-unit 整数で保存・BigDecimal で計算・number でシリアライズしない |
| 分割・按分で端数が出る | 明示的丸め+丸め口座で残差を受ける(捨てない) |
| クリプト資産の識別 | (network, contract) タプル・ペッグ≠原資産 |
| 残高を持つべきか | 持たない・movementの和で導出。「残高≥0」は制約でなく不変条件 |
| 並行出金の二重支払い | available への予約+単一 linearizable step・必ず解決 |
| 外部連携が落ちる/嘘をつく | スキーマを境界検証・全 req/res 保存・複数ソース突合 |
| Webhook が来た | 事実でなくトリガー・生バイトで署名検証・API で現状照会・冪等 |
| 状態変更を確実に外へ | Outbox / CDC・consumer は event id で重複排除 |
| ズレの検知 | リコンサイル(外部 ID 保存・T+X を織り込む・上書きしない) |
| 破壊的/大口操作 | 4-eyes+承認自体を証跡化・break-glass を明示 |
| 長い money flow の実装 | durable execution(Temporal 等)+各ステップ冪等+saga 補償 |
| テスト設計 | property-based+crash 注入+round-trip+後方互換+synthetic の本番テスト |
一言で: この本は「自分が交換所・トレジャリーで暗黙にやっている防御」を、No invented / No lost / No trust の3語に圧縮した設計チェックリスト。新規設計・レビュー・後輩への言語化のたびに、この対応表を引けばよい。
参考リンク¶
- Fintech Engineering Handbook(本体): https://w.pitula.me/fintech-engineering-handbook/
- GitHub(一次資料・Issue): https://github.com/Krever/fintech-engineering-handbook
- 著者の consulting: https://w.pitula.me/#consulting
- Martin Kleppmann, "Accounting for Computer Scientists"(本書 Appendix A 推薦): https://martin.kleppmann.com/2011/03/07/accounting-for-computer-scientists.html
- Modern Treasury, "How to Scale a Ledger"(同推薦): https://www.moderntreasury.com/journal
- "Designing Data-Intensive Applications"(同推薦・システム基礎)
作成: 2026-07-20 / 最終更新: 2026-07-20