メインコンテンツまでスキップ

第28章:Projectionって何?(読みやすい形を別で作る)🔎✨


1) Projectionは「表示しやすい形の別データ」だよ😊🧺

イベントソーシングでは、真実(ソース・オブ・トゥルース)は イベントの列(出来事の履歴) です📜✨ でも画面や検索で「毎回イベント全部を読んで計算」するのは、だんだんキツくなります😵‍💫

そこで登場するのが Projection(投影 / 読みモデル / Read Model) 🎀 イベントを材料にして、画面・検索・一覧にちょうどいい形のデータ を別で作っておく考え方です🍱🔎

ざっくり一言: 書く側は“正しさ重視” / 読む側は“見やすさ・速さ重視” 💪✨

この「読みと書きを分けようね」という発想は、CQRS(Command と Query を分ける)としてもよく説明されます📮👀 (martinfowler.com)


2) まず超イメージ図(頭の中にこれを置く)🧠✨

読み書き分離

  • Command(書き込み):ルール(不変条件)を守って、イベントを追加する📮✅
  • Event(出来事):追加された事実のログ(変更履歴)📜🧷
  • Projection(読みモデル):イベントを受け取って、表示用に整えたデータを更新する🔎🧱

イベント→読みモデル更新のイメージは「イベントを受け取ったProjectionが、読みモデルDB(何でもOK)を普通にCRUDする」みたいに説明されます📦🛠️ (Kurrent - event-native data platform)


3) なんでProjectionが必要になるの?(嬉しさ)🎁😊

① 画面がラクになる🖥️✨

画面って「一覧」「検索」「集計」みたいな 読みやすさ優先 が多いよね📋🔎 Projectionで 表示に必要な形に“先に整形” しておくと、UI側がめっちゃ単純になります😺

② 読み取りが速くなる⚡

読みと書きを分けると、読み側は読み側で最適化できる🌟 「読む回数が多い」「一覧が重い」みたいなときに効くよ〜📈 (martinfowler.com)

③ 追加の画面・集計が増えても“作り足せる”🔁✨

イベントは「履歴」なので、あとから「新しい読みモデル」を作りたくなったら イベントを最初から流して(リプレイして)作り直せる のが強いです🔁🧹 そもそもイベントソーシングは、変更をイベント列として保存し、必要ならそこから状態を再構成できる、という考え方です📜🧠 (martinfowler.com)


4) ここでありがちな勘違い(先に潰す)🧯😆

❌ 勘違いA:Projection = “DBのVIEW” でしょ?

違うよ〜🙅‍♀️ Projectionは イベントを材料にして作る「表示用データ」そのもの(またはそれを作る処理)だよ🔎🧱

❌ 勘違いB:読みモデルにも“ビジネスルール”を入れる?

基本は入れないよ〜🙅‍♀️ ルール(不変条件)で守るのは 書き込み側(Command側) がメイン✅ 読み側は 表示に必要な整形・集計 に寄せるのがコツ🍱✨

❌ 勘違いC:読みモデルは1個だけ作るもの?

むしろ逆!😳 画面ごとに作ってOK(むしろその方がラク)📋📄📊 「1画面 = 1Projection」くらいの気持ちで始めると迷子になりにくいよ🧭✨


5) Projection設計のいちばん簡単な手順(迷ったらコレ)🧩😊

Step 1:まず“画面で欲しいもの”を箇条書き📝🖥️

例(カート画面だと…)🛒✨

  • 商品一覧(名前・単価・数量)📦
  • 合計金額💰
  • 合計点数🔢
  • 最終更新日時🕒

Step 2:それを“イベントから作れる?”でチェック✅🔍

  • 合計点数 → ItemAdded / ItemRemoved で増減できる🔢
  • 合計金額 → 単価×数量の積み上げ、またはイベントに単価を入れておく💰
  • 商品名 → どこから来る?(商品マスタ参照?イベントにスナップを持つ?)🤔

ここで「イベントのpayloadに何を入れるべきか」が見えてくるよ🍱✨

Step 3:読みモデルの形(DTO)を決める📦🧾

読みモデルは “問い合わせに最適な形” が正義🏆 正規化とか気にしすぎなくてOK(表示が楽なら勝ち)😺✨

Step 4:更新ルールは“単純”にする🧼

読みモデル更新は、だいたい次のどれかでOK👇

  • 加算 / 減算(カウント、合計など)➕➖
  • 置き換え(最新の名前、ステータスなど)🪄
  • 配列に追加 / 削除(一覧用)🧺

6) “畳み込み(reduce)”で理解するProjection(ミニコード)🧠🔁

イベントが並んでいて、それを左から順に適用して「表示用の状態」を作る… これは reduce(畳み込み) のノリです✨

// 例:超ミニ「カート概要」Projection(教材用にかなり簡略)
type CartEvent =
| { type: "ItemAdded"; itemId: string; price: number; quantity: number; occurredAt: string }
| { type: "ItemRemoved"; itemId: string; price: number; quantity: number; occurredAt: string };

type CartSummary = {
itemCount: number;
totalPrice: number;
updatedAt: string | null;
};

const emptySummary = (): CartSummary => ({
itemCount: 0,
totalPrice: 0,
updatedAt: null,
});

const applyToSummary = (s: CartSummary, e: CartEvent): CartSummary => {
switch (e.type) {
case "ItemAdded":
return {
itemCount: s.itemCount + e.quantity,
totalPrice: s.totalPrice + e.price * e.quantity,
updatedAt: e.occurredAt,
};
case "ItemRemoved":
return {
itemCount: s.itemCount - e.quantity,
totalPrice: s.totalPrice - e.price * e.quantity,
updatedAt: e.occurredAt,
};
}
};

export const projectCartSummary = (events: CartEvent[]): CartSummary =>
events.reduce(applyToSummary, emptySummary());

ポイントはこれだけ👇😊

  • Projectionは「イベント列 → 表示用の形」へ変換する処理🔁
  • ルールの厳密さより「表示が楽」「速い」を優先してOK🔎⚡
  • 壊れたら作り直せる発想(あとで第32章でやるよ)🧹🔁

7) ミニ演習(この章のゴール)🧪🎀

演習A:画面に必要な項目を列挙しよう📝✨

あなたの題材(カート / ToDo / 家計簿など)で、次を3つの画面に対して書いてね👇

  1. 一覧画面📋
  2. 詳細画面🔎
  3. ちょい集計画面📊

それぞれ「欲しい表示項目」を10個以内でOK😺✨

演習B:その項目、イベントだけで作れる?チェック✅🔍

各項目に「材料」を書き添えてね👇

  • 例)合計金額 → ItemAdded/Removed の price と quantity から作る💰
  • 例)最終更新 → eventの occurredAt を最後に見たらOK🕒

8) AI活用(Projectionで迷わないための“型”)🤖✨

① 画面要件 → Projection案を出してもらう🧾🔎

プロンプト例(コピペOK)👇

  • 「この画面に必要な表示項目を10個以内で」
  • 「それぞれを“イベントから作れるか”も一言で」
  • 「Projectionを1つにまとめず、画面ごとに分けて」

② “イベントpayloadに入れるべき情報”の漏れチェック🕵️‍♀️🍱

プロンプト例👇

  • 「この表示項目をProjectionで作るために、イベント側に必要なデータは?」
  • 「イベントに入れすぎ注意:本当に必要なものだけに絞って」

③ reduce(apply)関数の叩き台を作らせる✍️🧠

プロンプト例👇

  • 「CartSummaryの型はこれ。イベント型はこれ。apply関数とproject関数を書いて」
  • 「境界条件(0未満になる、など)の注意点もコメントで」

9) 今日のチェックポイント(3つだけ覚えて帰ろう)🏁😊

  • Projectionは “表示しやすい形を別で作る” こと🔎✨
  • 書く側=正しさ / 読む側=見やすさ・速さ に寄せる🍱⚡ (martinfowler.com)
  • イベント列を reduceで畳み込む イメージを持つと一気にわかる🔁🧠

おまけ:2026/02/01時点の関連“最新メモ”🧷🗞️

  • TypeScriptのnpm最新安定版は 5.9.3(npmのLatest表示)です📌 (npm)
  • TypeScriptチームは、ネイティブ移植(Project Corsa)に向けた更新を継続して公開中で、6.0は“橋渡し”になる予定と説明されています(Microsoft公式ブログ)🧠⚡ (Microsoft for Developers)
  • ネイティブ版のプレビューは npm や VS Code 向けにも案内されています🔧✨ (Microsoft for Developers)
  • VS Codeは 2026年1月のリリース(1.108)と、Insiders(1.109)の更新が公開されています🛠️✨ (code.visualstudio.com)