Closed7

100日チャレンジ day39 (Copy On Write シュミレータ)

riddle_tecriddle_tec

昨日

https://zenn.dev/gin_nazo/scraps/68379fbbed9418


https://blog.framinal.life/entry/2025/04/14/154104

100日チャレンジに感化されたので、アレンジして自分でもやってみます。

やりたいこと

  • 世の中のさまざまなドメインの簡易実装をつくり、バックエンドの実装に慣れる(dbスキーマ設計や、関数の分割、使いやすいインターフェイスの切り方に慣れる
  • 設計力(これはシステムのオーバービューを先に自分で作ってaiに依頼できるようにする
  • 生成aiをつかったバイブコーティングになれる
  • 実際にやったことはzennのスクラップにまとめ、成果はzennのブログにまとめる(アプリ自体の公開は必須ではないかコードはgithubにおく)

できたもの

https://github.com/lirlia/100day_challenge_backend/tree/main/day39_interactive_cow_simulator

riddle_tecriddle_tec

承知いたしました。「Day39 - インタラクティブCoWストレージシミュレーター」のプロジェクトを開始します。

まずは、このアプリケーションの具体的な作業手順、ディレクトリレイアウト、作成するファイル一覧を以下に提案します。

プロジェクト名

day39_interactive_cow_simulator

デザインテーマ

ミニマリズムとデータビジュアライゼーションの融合

作業手順

  1. プロジェクト初期化 (Step 1)

    • template ディレクトリをコピーして day39_interactive_cow_simulator を作成。
    • package.jsonnameday39_interactive_cow_simulator に変更。
    • README.md の初期化(アプリ概要、目的、主な機能)。
    • 基本的なレイアウト (app/layout.tsx, app/page.tsx) を作成。Tailwind CSS の設定。
    • コミット: day39: step 1/8 project initialization
  2. データモデリングとDB設定 (Step 2)

    • このアプリケーションは主にフロントエンドでのシミュレーションが中心となるため、サーバーサイドのDBは最小限にします。スナップショットのメタ情報(名前、作成日時など)や、もし永続化したい設定があればそれを保存する程度とします。
    • lib/db.ts:
      • Snapshot テーブル: id, name, createdAt, diskStateJson (ディスク状態をJSONで保存)
    • db/dev.db を削除してスキーマを反映。
    • コミット: day39: step 2/8 data modeling and db setup
  3. コアロジックと状態管理の実装 (Step 3)

    • app/_lib/cow-simulator.ts:
      • 仮想ディスク、ブロック、ファイルのデータ構造定義。
      • ファイル作成、変更、削除ロジック(CoWを意識)。
      • スナップショット作成ロジック(CoW)。
      • ブロックの割り当て、共有状態管理ロジック。
    • app/(pages)/simulator/store.ts (または app/(pages)/simulator/useCowStore.ts):
      • Zustand や React Context + useReducer を用いたクライアントサイドの状態管理。
      • 仮想ディスクの状態、ファイルリスト、スナップショットリスト、現在の操作などを管理。
      • 上記 cow-simulator.ts のロジックを呼び出すアクションを定義。
    • コミット: day39: step 3/8 core logic and state management implementation
  4. APIエンドポイント実装 (Step 4)

    • (オプション)もしスナップショット情報をサーバーに保存・ロードする機能を作る場合。
    • app/api/snapshots/route.ts:
      • GET: 保存されたスナップショットの一覧を取得。
      • POST: 現在のディスク状態からスナップショットを作成しDBに保存。
    • app/api/snapshots/[id]/route.ts:
      • GET: 特定のスナップショット情報を取得。
      • DELETE: 特定のスナップショットを削除。
    • curl などで API の動作確認。
    • コミット: day39: step 4/8 api endpoint implementation (if any)
  5. UIコンポーネント実装 (Step 5)

    • app/(pages)/simulator/page.tsx: シミュレーター全体のメインページ。
    • app/(pages)/simulator/components/VirtualDiskView.tsx:
      • 仮想ディスクのブロックを視覚的に表示(SVGやdiv要素のグリッド)。
      • ブロックの色やラベルで状態(未使用、ファイルA、共有など)を表現。
      • ブロックへのマウスオーバーで詳細情報を表示するなどのインタラクション。
    • app/(pages)/simulator/components/FileControls.tsx:
      • ファイル作成、編集、削除を行うためのUI(入力フォーム、ボタン)。
    • app/(pages)/simulator/components/SnapshotControls.tsx:
      • スナップショット作成ボタン、スナップショットリスト表示。
      • スナップショット選択で過去の状態を VirtualDiskView に反映。
    • app/(pages)/simulator/components/SnapshotTreeView.tsx (または SnapshotTimelineView.tsx):
      • スナップショット間の関係性を視覚的に表示。
    • app/(pages)/simulator/components/InfoPanel.tsx:
      • 現在のストレージ使用量 (論理/物理)、CoWイベントのログなどを表示。
    • アプリのタイトルを Day39 - インタラクティブCoWストレージシミュレーター とする。
    • コミット: day39: step 5/8 ui component implementation
  6. 主要業務フロー実装 (Step 6)

    • ファイル作成 → ブロック割り当て → VirtualDiskView 更新。
    • ファイル編集 → CoW発生 → 新規ブロック割り当て、旧ブロック状態保持 → VirtualDiskView 更新。
    • スナップショット作成 → メタデータ更新、ブロック状態更新 (共有など) → VirtualDiskView および SnapshotTreeView 更新。
    • スナップショット選択 → VirtualDiskView が選択されたスナップショットの状態を反映。
    • コミット: day39: step 6/8 main business flow implementation
  7. デバッグとテスト (Step 7)

    • Playwright を用いたインタラクションのテスト:
      • ファイルを作成し、ブロックが正しく割り当てられることを確認。
      • ファイルを編集し、CoWが視覚的に正しく表現されることを確認。
      • スナップショットを作成し、共有ブロックと新規ブロックが正しく色分けされることを確認。
      • 過去のスナップショットを選択し、状態が復元されることを確認。
    • 不要なファイルやコードを削除。
    • コミット: day39: step 7/8 debugging and testing
  8. ドキュメント作成 (Step 8)

    • README.md の更新 (スクリーンショット、使い方、学んだこと)。
    • .cursor/rules/knowledge.mdc に今回のアプリ情報を追記。
    • コミット: day39: step 8/8 documentation

ディレクトリレイアウト (抜粋)

day39_interactive_cow_simulator/
├── app/
│   ├── api/
│   │   └── snapshots/
│   │       ├── [id]/
│   │       │   └── route.ts
│   │       └── route.ts
│   ├── (pages)/
│   │   └── simulator/
│   │       ├── components/
│   │       │   ├── VirtualDiskView.tsx
│   │       │   ├── FileControls.tsx
│   │       │   ├── SnapshotControls.tsx
│   │       │   ├── SnapshotTreeView.tsx
│   │       │   └── InfoPanel.tsx
│   │       ├── store.ts        // Zustand store
│   │       └── page.tsx
│   ├── _lib/
│   │   └── cow-simulator.ts
│   ├── layout.tsx
│   ├── globals.css
│   └── page.tsx
├── components/
│   └── ui/
├── lib/
│   └── db.ts
├── db/
│   └── dev.db
├── public/
├── package.json
├── README.md
└── ... (その他設定ファイル)

作成する主なファイルと行数目安

  • app/_lib/cow-simulator.ts: (コアロジック) ~200-300行
  • app/(pages)/simulator/store.ts: (状態管理) ~150-250行
  • app/(pages)/simulator/page.tsx: (メインページ) ~100行
  • app/(pages)/simulator/components/VirtualDiskView.tsx: (最重要UI) ~200-300行
  • app/(pages)/simulator/components/FileControls.tsx: ~80行
  • app/(pages)/simulator/components/SnapshotControls.tsx: ~100行
  • app/(pages)/simulator/components/SnapshotTreeView.tsx: ~100行
  • app/(pages)/simulator/components/InfoPanel.tsx: ~80行
  • lib/db.ts: ~50行
  • app/api/snapshots/route.ts: ~80行
  • app/api/snapshots/[id]/route.ts: ~60行

各ファイルは300行以内を目指し、複雑なコンポーネントは適宜分割します。
SVGやCanvasを直接使う場合、VirtualDiskView.tsx が少し大きくなる可能性がありますが、その場合は内部コンポーネントに分割することを検討します。React Flowのようなライブラリを使う場合は、その設定やノード定義で多少行数が増えるかもしれません。

riddle_tecriddle_tec

学び

  • cow & snapshot はある時点のデータを残しておき変更の差分のみを保存する
  • snapshot を取ることで元データにロックがかかるため、削除したとしても物理的には削除されない
  • snapshot を使って戻す場合、snapshot 作成後に作成されたファイルは残り続けるが参照は存在しないのでいずれ消されるっぽい
このスクラップは4ヶ月前にクローズされました