Claude-skill-registry fsd-es-lite
FSD + ES-lite アーキテクチャに基づく機能開発スキル。新しいfeature/entity/widgetを追加する時、CQRS + Event Sourcingパターンで実装する時、scaffoldスクリプトを使う時に使用。AI-nativeアプリケーション開発向け。
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/fsd-es-lite" ~/.claude/skills/majiayu000-claude-skill-registry-fsd-es-lite && rm -rf "$T"
manifest:
skills/data/fsd-es-lite/SKILL.mdsource content
FSD + ES-lite 開発スキル
Feature-Sliced Design (FSD) と Event Sourcing Lite (ES-lite) を組み合わせた開発プロセス。 PoC・小規模開発向けに最適化された軽量アーキテクチャ。
設計原則
1. 関数ベース実装(クラス禁止)
クラスを使わず、関数とvelonaのDIで実装する。
理由:
- クラスはインスタンス内での状態管理が複雑になりがち
- 関数は純粋で、テストしやすく、合成しやすい
- velonaで依存注入すれば、テスト時のモック差し替えも容易
// ✅ 関数ベース + velona DI import { depend } from "velona"; export const executeCommand = depend( { eventStore, projector, clock: () => new Date().toISOString(), idGen: () => crypto.randomUUID() }, async (deps, command: Command, correlationId: string) => { // 実装 } ); // テスト時 executeCommand.inject({ eventStore: mockStore })(command, correlationId); // ❌ クラスベース(使わない) class CommandHandler { constructor(private eventStore: IEventStore) {} async execute(command: Command) { /* ... */ } }
2. ES-lite統一
全featureでEvent Sourcing Lite(軽量版ES)に統一。
特徴:
- 全状態変更をイベントとして記録
- イベントリプレイで状態再構築
- AIエージェント + ユーザー操作を同じイベント系列で追跡
- CRUDとの混在を避け、学習コストを削減
3. 厳格なFSD構造
src/ shared/ # 共有インフラ・ユーティリティ entities/ # ドメインモデル・スキーマ・表示UI(オプション) features/ # ユースケース・API・状態管理 widgets/ # 複合UIブロック pages/ # ルートコンポーネント app/ # アプリ初期化
4. decide/apply パターン(純粋関数)
// decide: 状態 + コマンド → イベント(純粋関数) function decide( state: Aggregate | null, command: Command, meta: EventMeta ): Result<Event[], DecisionError> // apply: 状態 + イベント → 新状態(純粋関数) function apply( state: Aggregate | null, event: Event ): Aggregate
Scaffoldスクリプト
新しい機能を追加する際は、scaffoldスクリプトを使用して最小限のボイラープレートを生成する。 生成後、ドメインに合わせてカスタマイズする。
Feature全体を生成
npx tsx .claude/skills/fsd-es-lite/scripts/scaffold-feature.ts <feature-name>
生成されるファイル(最小構成):
- Zodドメインスキーマentities/<name>/model/schema.ts
- イベント型定義entities/<name>/model/events.ts
- 決定ロジックfeatures/<name>/model/decide.ts
- 適用ロジックfeatures/<name>/model/apply.ts
- コマンド実行features/<name>/model/commands.ts
- クエリfeatures/<name>/model/queries.ts
- Projection更新features/<name>/model/projector.ts
- Hono APIルートfeatures/<name>/api/routes.ts
- RPC型定義features/<name>/api/contracts.ts
- バリデーションスキーマfeatures/<name>/api/schemas.ts
UIやhooksは必要に応じて追加する(scaffoldでは生成しない)
Entityのみを生成
npx tsx .claude/skills/fsd-es-lite/scripts/scaffold-entity.ts <entity-name>
生成されるファイル:
- Zodドメインスキーマentities/<name>/model/schema.ts
- イベント型定義entities/<name>/model/events.ts
- エクスポートentities/<name>/model/index.ts
- UIコンポーネント(実装必須)entities/<name>/ui/<Name>.tsx
- UIエクスポートentities/<name>/ui/index.ts
生成後、TODOコメントを参照してドメインに合わせて実装する
Widgetを生成
npx tsx .claude/skills/fsd-es-lite/scripts/scaffold-widget.ts <widget-name>
生成されるファイル:
- メインコンポーネント(実装必須)widgets/<name>/ui/<Name>.tsx
- UIエクスポートwidgets/<name>/ui/index.ts
- モデルエクスポートwidgets/<name>/model/index.ts
- パブリックAPIwidgets/<name>/index.ts
生成後、TODOコメントを参照してentities/featuresを組み合わせて実装する
開発フロー
新機能追加時
-
scaffoldで雛形生成
npx tsx .claude/skills/fsd-es-lite/scripts/scaffold-feature.ts order -
ドメインスキーマ定義 (
)entities/order/model/schema.ts- Zodでドメイン型を定義
- ステータス enum、集約、投影を定義
- ドメインに必要なフィールドを追加
-
イベント定義 (
)entities/order/model/events.ts- ドメインイベントを定義(Created, Updated, etc.)
- イベントファクトリ関数を追加
-
decide/apply実装 (
)features/order/model/
: ビジネスルールを純粋関数で実装decide.ts
: イベント適用を純粋関数で実装apply.ts- in-source testingでテスト追加
-
commands.ts実装
- velonaでDI
- EventStore.load → decide → EventStore.append → Projector.save
-
APIルート定義 (
)features/order/api/routes.ts- Honoでエンドポイント定義
- zValidatorでバリデーション
-
必要に応じてUI/hooksを追加
- TanStack Query hooksfeatures/order/hooks/
- UIコンポーネントfeatures/order/ui/
- 表示専用コンポーネントentities/order/ui/
参照ドキュメント
詳細な実装パターンは以下を参照:
- ARCHITECTURE.md - アーキテクチャ詳細
- PATTERNS.md - コードパターン集
重要な設計判断
Result型と例外の使い分け
| 種類 | エラー例 | 対応 |
|---|---|---|
| Result型 | バリデーションエラー、バージョン競合 | 復帰可能 |
| 例外 | DBアクセスエラー | 復帰不能 |
楽観的ロック
// コマンドにexpectedVersionを含める type UpdateCommand = { type: "Update"; aggregateId: string; expectedVersion: number; // ... }; // appendでバージョンチェック await eventStore.append(aggregateId, events, expectedVersion); // → ConflictErrorならリトライを促す
日時型の統一
全ての日時はISO 8601文字列(
string)で統一。
// ✅ ISO 8601文字列 createdAt: z.string().datetime() // ❌ Dateオブジェクト(使わない) createdAt: z.date()
Projector失敗時の方針
イベントは保存済みなので、投影失敗はログのみ。後からリプレイで再構築可能。
try { await deps.projector.save(next); } catch (error) { console.warn("Projection save failed:", error); }
AI操作追跡
CommandLogでAI操作を追跡:
interface CommandLog { actor: { type: 'user' | 'ai' | 'system'; id?: string }; aiInfo?: { modelId: string; runId: string; promptHash?: string; }; // ... }