目次を表示する

コードの考古学 ── 設計思想に宿る哲学の源流

第3話 ヴェネツィアの帳簿 ── 1494年の発明がイベントストリームを生んだ

場面

1494年、ヴェネツィア。

フランチェスコ会の修道士ルカ・パチョーリは、数学の大著を書き上げた。『算術、幾何、比及び比例全書(Summa de arithmetica, geometria, proportioni et proportionalità)』——その名の通り、中世ヨーロッパの数学知識のほぼすべてを一冊に詰め込んだ百科全書だ。レオナルド・ダ・ヴィンチとも親交があった彼は、この本の中に、当時ヴェネツィアの商人たちが百年以上使い続けてきたある実践を、初めて体系的に文字に起こした。

「勘定と記録の詳論(Particularis de Computis et Scripturis)」と題されたその章で、パチョーリは複式簿記の原理を記述した。

核心にある原則は、当時の商人たちには自明すぎてほとんど語られなかったことだ——残高を記録するな。取引を記録せよ。

銀行家は「今現在の残高」を台帳に書かない。すべての入金を、すべての出金を、それぞれ二行で記録する。借方と貸方。すべての取引には、必ず対応する反対側の記録が存在しなければならない。残高とは、そうした取引記録をすべて積み上げた結果として計算されるものだ——直接書き込まれるものではない。

なぜか。ヴェネツィアの商人たちは経験から知っていた。残高だけ書いておくと、後で何も説明できない。「なぜ先月より100ドゥカートが減ったのか」を証明できない。しかし取引をすべて記録しておけば、任意の時点の残高が計算できる。過去の任意の瞬間の財務状態を再現できる。不正も、ミスも、追跡できる。

パチョーリの記述を読んだヨーロッパの商人たちは、この体系を急速に採用した。五百年後、この原則はデジタルの世界で再発見されることになる。


答え

パチョーリが言語化し、ヴェネツィアの商人たちが実践で発見した洞察は一行で言える。

取引のログが真実だ。残高は、その便利な導出に過ぎない。

すべての取引を保持しているなら、過去の任意の時点の残高を計算できる。しかし現在の残高だけを保持しているなら、歴史は永遠に失われる。

だから監査が成立する。「Tという時点での残高は何だったか」という問いに答えられるのは、残高をT時点で記録したからではない——T時点までのすべての取引を再生したからだ。

真実は状態ではなく、状態を生んだ出来事の連鎖の中にある。


CS への翻訳

マーティン・ファウラーが「イベントソーシング(Event Sourcing)」というパターンを明文化したのは2005年のことだ。グレッグ・ヤングが2010年頃にCQRS(Command Query Responsibility Segregation)と組み合わせて形式化し、広く普及した。

しかし、その原則自体はパチョーリが文字に起こした五百年前からずっと同じだ。

現在の状態を保存するな。状態を生んだイベントの連鎖を保存せよ。

graph TB
    subgraph CRUD(状態を直接更新する)
        A["候補者\nstage: 書類選考"] -->|"UPDATE SET stage='一次面接'"| B["候補者\nstage: 一次面接"]
        B -->|"UPDATE SET stage='最終面接'"| C["候補者\nstage: 最終面接"]
        C -->|"UPDATE SET stage='内定'"| D["候補者\nstage: 内定"]
        D -. "なぜ二次面接をスキップ\nしたのか?\n→ 永遠にわからない" .-> X["❌"]
    end

    subgraph Event Sourcing(イベントを積み上げる)
        E1["ApplicationReceived\n2026-01-10"] --> E2["FirstInterviewPassed\n2026-01-20"]
        E2 --> E3["SecondInterviewSkipped\nreason=recruiter_discretion\n2026-01-21"]
        E3 --> E4["FinalInterviewScheduled\n2026-01-25"]
        E4 --> E5["OfferExtended\n2026-02-01"]
        E5 -. "任意の時点のstateを\n再現できる\n理由も全部残る" .-> Y["✅"]
    end

データベースの**WAL(Write-Ahead Log)**も同じ原理だ。PostgreSQLもMySQLも、データを直接ディスクに書き込む前に、「何をしようとしているか」をログに書く。クラッシュが起きても、ログを再生すれば状態を復元できる。ログが真実で、データベースのページファイルはそのキャッシュに過ぎない——これは、帳簿の取引記録が真実で、残高欄はその計算結果に過ぎないというパチョーリの原則と構造的に同一だ。

Kafkaのようなイベントストリームプラットフォームが「ログコンパクション」を持ちながらも、デフォルトではすべてのイベントを保持するのも同じ理由だ。ログそのものが権威の源泉だからだ。

ここでひとつ、後の章(第8話)への伏線を張っておく。複式簿記の取引記録は、書かれた瞬間から変更されない。「先月の取引を書き換える」のではなく「訂正の取引を新しく追記する」。イベントソーシングの「イベントは不変」も同じ規律だ。これは単に偶然の似姿ではない——「過去の事実を書き換えれば、過去の事実は失われる」という、ヘラクレイトス以来の流転の哲学にまでさかのぼる構造を持っている。第8話でこの不変性の系譜をさらに辿る。


設計への示唆

採用管理システムを設計するとき、この区別が鮮明に現れる。

❌ 状態を記録するシステム(CRUD思考)

-- 候補者テーブル:現在の状態だけを記録する
CREATE TABLE candidates (
    id UUID PRIMARY KEY,
    name VARCHAR(255),
    current_stage VARCHAR(50),  -- 'screening', 'first_interview', 'final', 'offer'
    updated_at TIMESTAMP
);

-- 書類選考から最終面接に上げる(一次・二次をスキップ)
UPDATE candidates
SET current_stage = 'final_interview',
    updated_at = NOW()
WHERE id = 'cand-001';

このシステムに「なぜこの候補者は一次面接・二次面接をスキップして最終面接に進んだのか」と聞いても、答えられない。updated_at は時刻を教えてくれるが、理由は永遠に失われた。監査もできない。ログも残らない。「状態」だけが残る。

✅ 事実を記録するシステム(イベントソーシング思考)

// イベントを積み上げる
type CandidateEvent =
  | { type: 'ApplicationReceived'; candidateId: string; appliedAt: Date }
  | { type: 'ScreeningPassed'; candidateId: string; reviewerId: string; passedAt: Date }
  | { type: 'InterviewSkipped'; candidateId: string; stage: string; reason: string; skippedAt: Date; authorizedBy: string }
  | { type: 'InterviewScheduled'; candidateId: string; stage: string; scheduledAt: Date }
  | { type: 'OfferExtended'; candidateId: string; offeredAt: Date; salary: number };

// イベントを保存する
async function skipToFinalInterview(
  candidateId: string,
  reason: string,
  authorizedBy: string
): Promise<void> {
  await eventStore.append({
    type: 'InterviewSkipped',
    candidateId,
    stage: 'second_interview',
    reason,   // 理由が永遠に残る
    skippedAt: new Date(),
    authorizedBy  // 誰が承認したかも残る
  });
  await eventStore.append({
    type: 'InterviewScheduled',
    candidateId,
    stage: 'final_interview',
    scheduledAt: new Date()
  });
}

// 任意の時点のstateを再現する
function reconstructState(events: CandidateEvent[]): CandidateState {
  return events.reduce(applyEvent, initialState);
}

「なぜ最終面接に上がったのか」——すべてのイベントが残っているから、即座に答えられる。採用担当者が裁量でスキップを承認したこと、その理由、その時刻、全部がそこにある。

パチョーリの洞察を一言で言えば:説明できるシステムは、報告だけできるシステムより根本的に信頼できる。

残高を見せてくれるシステムではなく、残高がどうやって生まれたかを見せてくれるシステム——それがヴェネツィアの商人が五百年前に選んだ設計だ。


問い:あなたのシステムは「事実」を記録しているか、「結論」を記録しているか?今の設計で「なぜそうなったか」を説明できるか?