🐡

第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つのキーワードで整理しておきます

  1. Permanence(持続性)
    • 一度記録された事実は、論理的には消せない(少なくとも、そう扱うべき)
  2. Shared(共有性)
    • 複数の組織・人間が、その記録を前提に動く
  3. 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}/issue
  • POST /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-1002TX-1003追加することです

RML-3 のロールバックとは、
この **「補正イベントを積み上げるプロセス」**だと考えた方が安全です

3.2 「ロールバック = 返金+訂正+説明」

ユーザー視点で見ると、RML-3 のロールバックはたいてい

  1. 返金(金銭的な補正)
  2. 訂正(システム上の状態の修正)
  3. 説明(何が起きたか・どう対処したか)

のセットになります

History World に足を踏み入れるというのは、この三つを「筋の通った形でやる責任を負う」ということでもあります


4. RML-3 が要求する設計の変化

RML-3 の世界に踏み込むと、設計の前提も少しずつ変わってきます

4.1 破壊的更新より「バージョン」を好む

  • RML-2 までは
    「状態を上書きする」設計でもなんとかなることが多い
  • RML-3 以降は
    「過去のバージョンを残したまま、新しい状態を追加する」設計が望ましい

例:

  • 請求書レコード

    • status カラムを DRAFTISSUEDCANCELLED と上書きするよりも
    • 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 /draftsPOST /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