第4章 RML-3 — History World の設計原則:消せない歴史と前向きな修正
RML-3 — History World の設計原則:消せない歴史と前向きな修正
『The Worlds of Distributed Systems』第4章
「それ、DBを書き戻したら“なかったこと”にできますか?」
「……社会の側には、もう残ってしまってるかもしれませんよ」
第2章では RML-1(Closed World)、
第3章では RML-2(Dialog World)を見てきました
- RML-1:部屋の中だけで完結する世界
- RML-2:サービス同士・人間との「対話」で帳尻を合わせる世界
第4章では、いよいよ三つ目の世界、
RML-3 — History World(歴史の世界)
を扱います
ここは、
一度起きたことを「なかったことにする」のではなく、
「起きたことの上に修正を積み上げる」しかない世界
です
1. History World とは何か — 「消せない歴史」のある世界
RML-3 を一言で表すと
「複数の主体が共有する“歴史”があり、それに責任を負う世界」
です
1.1 典型的な RML-3 の領域
- 金融
- 銀行振込、クレジットカード決済、証券取引
- 税務・会計
- 請求書、領収書、会計仕訳
- 医療・公共
- 診療記録、処方履歴、公共インフラのログ
- B2B・契約
- 企業間の受発注、契約の履行履歴
共通するのは
- 「あとから第三者が見て意味を持つ」記録であること
- 複数の組織・人間が、その記録を前提に行動すること
です
1.2 3つのキーワード
History World の性質を、3つのキーワードで整理しておきます
-
Permanence(持続性)
- 一度記録された事実は、論理的には消せない(少なくとも、そう扱うべき)
-
Shared(共有性)
- 複数の組織・人間が、その記録を前提に動く
-
Accountability(説明責任)
- 「なぜこうなったのか」を後から説明する必要がある
ここまで来ると、単なる「システムの都合」では済まなくなります
2. RML-2 と RML-3 の境界 — どこから歴史になるのか
実務上いちばん難しいのは、
「どこから先が RML-3 なのか?」
という境界を引くことです
2.1 境界を越えたと判断する典型パターン
ざっくり、こんな条件が揃ってきたら RML-3 を疑います
-
お金 / 物 / 権利が動く
- 決済が確定する
- 在庫が確定する
- サービス利用権が発生する
-
第三者に対する約束が発生する
- 法的効力のある書類が出る(請求書、契約書)
- 利用規約に基づいた義務が生じる
-
外の世界のアクションが誘発される
- 顧客が支払いをする、税務署に申告する、患者が薬を飲む など
逆に言うと、
- 社内メモレベル
- 仮ステータス
- 「いつでも再計算できる一時結果」
に留まるものは、まだ RML-2 側に置けるかもしれません
2.2 分割のコツ:History Hand-off Point を決める
設計としては、
「ここから先の操作は、History World に手渡す」
という History Hand-off Point を決めておくと分かりやすいです
例:
- 決済:オーソリ(RML-2) → キャプチャ(RML-3)
- 請求書:ドラフト(RML-2) → 発行済(RML-3)
- 医療:メモ(RML-2) → 正式なカルテ記録(RML-3)
UI や API の設計でも、
- 「確定」「発行」「送信」ボタン
POST /invoices/{id}/issuePOST /payments/{id}/capture
といった境界になる操作を、ちゃんと名前付きで切り出しておくと、
チーム内の世界観も揃えやすくなります
3. RML-3 における「ロールバック」の正体
RML-3 の世界には、RML-1/2 のような意味での「ロールバック」は基本的に存在しません
過去を消すのではなく、過去の上に修正を積み上げる
だけです
3.1 Effect Ledger という見方
抽象化の話としてだけ出しておくと、History World はしばしば Effect Ledger(効果台帳) として扱われます
- すべての外部効果(決済、請求、通知など)を append-only なログに記録する
- ログはハッシュなどでチェーンされ、改ざんしにくい形にする
- 後から Reconciler(人間+システム)が、ログを見ながら修正イベントを追加する
非常にざっくり書くと、こんな感じです
[TX-1001] 2025-04-01 10:00 "決済 5000円"
[TX-1002] 2025-04-02 09:30 "決済 5000円 の返金"
[TX-1003] 2025-04-02 10:00 "お詫びクーポン 1000円 発行"
ここでやっているのは、
-
TX-1001を DB から消すことではなく -
TX-1002やTX-1003を 追加することです
RML-3 のロールバックとは、
この **「補正イベントを積み上げるプロセス」**だと考えた方が安全です
3.2 「ロールバック = 返金+訂正+説明」
ユーザー視点で見ると、RML-3 のロールバックはたいてい
- 返金(金銭的な補正)
- 訂正(システム上の状態の修正)
- 説明(何が起きたか・どう対処したか)
のセットになります
History World に足を踏み入れるというのは、この三つを「筋の通った形でやる責任を負う」ということでもあります
4. RML-3 が要求する設計の変化
RML-3 の世界に踏み込むと、設計の前提も少しずつ変わってきます
4.1 破壊的更新より「バージョン」を好む
- RML-2 までは
「状態を上書きする」設計でもなんとかなることが多い - RML-3 以降は
「過去のバージョンを残したまま、新しい状態を追加する」設計が望ましい
例:
-
請求書レコード
-
statusカラムをDRAFT→ISSUED→CANCELLEDと上書きするよりも -
invoice_versionsテーブルに履歴を持つ/イベントストアに発行・取消を積む
-
-
ユーザー残高
- 残高カラムを直接更新するよりも
- トランザクションログを合計する形で算出する
4.2 「誰が」「いつ」「何を」決めたかを残す
History World では、
- この操作は 誰の意思決定か(システム / オペレーター / 顧客)
- どの時点の状態を見て、その決定をしたのか
- 何に基づいてしたのか(規約 / ポリシー / 手順書)
といったメタ情報も重要になります
実装レベルでは、
- 操作ユーザーID
- トレースID / リクエストID
- 使用したルールのバージョン
- 変更理由の簡単なコメント
などを、RML-3 のイベントと一緒に保存しておくと、後ろの章で出てくる「事件簿」やインシデント対応とつながります
5. ありがちなアンチパターン
RML-3 の世界観が曖昧なときに起きがちな事故を、いくつか挙げておきます
5.1 History World を RML-2 のノリで「巻き戻す」
- すでに請求書を発行し、お客様にも送付してしまった
- そこでバグに気づき、「DB から請求書レコードを消した」
結果:
- ユーザーのメールボックスには「存在しない請求書」が残る
- 会計システムや別サービスが「見えないはずの請求」を参照しているかもしれない
- 後で監査やトラブルシュートが極めて難しくなる
RML 的には
- History World に刻まれた事実を、Dialog World のロールバックで上書きしようとした状態です
5.2 「History Hand-off Point」がふにゃふにゃ
- どの時点からが「正式な履歴」なのかが、チーム内でバラバラ
- ある人はドラフト段階を「もう履歴」と見なしている
- 別の人は正式発行後だけを履歴と見なしている
結果:
- どこまでが「消していい世界」かの認識がずれる
- RML-1/2 の便利さを活かしきれず、全部が重くなる
- 逆に、本当に RML-3 であるべきところが軽く扱われがち
対策の方向性は
- 第8章で出てくるような、「この領域はデフォルトで RML-3 とする」合意を作ることになります
5.3 ログを「History」として扱っていない
- 監査ログやアクセスログを、単なる技術ログだと思っている
- ログをしれっと削除・上書きしてしまう
- あとから「このお金、誰がいつ動かしたんですか?」と聞かれて詰む
ポイント
- ログの中にも、RML-1/2/3 が混在している
- RML-3 扱いすべきログ(監査・取引・アクセスの一部)を識別する必要がある
6. History World を設計に織り込むための視点
ここまでを踏まえて、RML-3 を現場の設計に織り込むときの視点を整理しておきます
6.1 「これは RML-3 ですよね?」と言えるようにする
まずは、会話レベルで
- 「この機能は、どの時点から RML-3 に入りますか?」
- 「このイベントは、History World に残す前提ですか?」
- 「このログは、技術ログですか? それとも監査ログですか?」
といった質問が自然に出てくるようになるのが第一歩です
6.2 UI / API / ドキュメントに境界を埋め込む
- UI:
「保存(まだ戻せる)」と「確定(History World に載る)」を、文言と見た目で分ける - API:
POST /draftsとPOST /drafts/{id}/issueなど、RML-2 → RML-3 のハンドオフを分ける - ドキュメント:
「RML-3 に入る操作」と「RML-2 に留まる操作」を一覧で整理しておく
6.3 「修正イベント」を最初から1つの機能として設計する
RML-3 に入るなら、
- 返金
- 訂正
- 無効化
- 追記コメント
などの修正操作を、最初から1つの「正規の機能」として設計します
「バグったら SQL で直せばいいや」
という裏口に頼らないようにする、ということでもあります
7. RML-3 観点のミニチェックリスト
最後に、第4章時点でのミニチェックリストを
7.1 History Hand-off
- プロダクトの中に、明示的な「History Hand-off Point」が定義されているか?
- そのポイントは UI / API / ドキュメントで分かるようになっているか?
7.2 データ・ログ設計
- 歴史として残すべき情報は、破壊的更新ではなくバージョン or イベントで管理しているか?
- 監査・取引ログのうち「RML-3 扱いすべきもの」は識別されているか?
- 「誰が・いつ・何を決めたか」を残す仕組みはあるか?
7.3 ロールバック観
- RML-3 に入ったあとにやるべきことを、「返金+訂正+説明」として捉えているか?
- History World の事実を、RML-2 レベルのロールバックで「なかったこと」にしていないか?
8. まとめ — History World は「やらかしたとき」に本性を見せる
RML-3 の世界観は、普段はあまり意識されないかもしれません
- 決済も請求も、何事もなく動いているときは
- History World を意識しなくても、システムは動きます
でも、本当に差が出るのは、
「やらかしてしまったあと」
です
- 二重決済をしてしまった
- 間違った請求書を発行してしまった
- 間違った情報をユーザーやパートナーに送ってしまった
そのときに、
- 適切に返金・訂正・説明ができるか
- 誰が何を決めたのかを、後から辿れるか
- 「なかったことにする」のではなく、「どうリカバリしたか」を歴史として残せるか
が、プロダクトと組織の信頼を決めます
RML-3 は、
分散システムの中で **「社会とちゃんと付き合うための世界」**でもあります
次の Part II では、一段論点を RML-2 に戻しつつ、
- 例外設計(StructuredError / ActionHint)
- Idempotency / Saga / 結果整合性
- API / GraphQL / gRPC における world 情報の扱い
といった Dialog World の具体パターンを掘っていきます
History World でちゃんと振る舞うためには、
その手前の RML-2 をどれだけ丁寧に設計するかが鍵になるからです
Discussion