ADR-003: ドメイン駆動設計(DDD)採用¶
ステータス¶
採用
背景¶
ぷよぷよゲームの開発において、複雑なゲームロジック(連鎖計算、消去判定、重力適用、スコア計算)を適切にモデル化し、保守可能なコードベースを構築する必要がある。CLAUDE.local.mdの指針に従い、中核業務領域として位置づけられるため、ドメイン駆動設計の適用を検討する。
検討事項¶
DDD適用の判断基準¶
- 業務領域の複雑さ: ぷよぷよのゲームルールは複雑で、多数の相互作用がある
- ビジネス価値: ゲームロジックはアプリケーションの核心価値
- 変更頻度: ゲームバランス調整、新機能追加が想定される
- 専門知識: ぷよぷよ特有のドメイン知識(連鎖、全消し等)
従来の開発手法との比較¶
Anemic Domain Model (従来手法)¶
// データのみのモデル
interface Puyo {
id: string;
color: PuyoColor;
x: number;
y: number;
}
// 外部のサービスクラスでロジック処理
class PuyoService {
checkConnectedPuyos(puyos: Puyo[], targetPuyo: Puyo): Puyo[] {
// 複雑な連鎖ロジック
}
}
問題点: - ビジネスロジックが分散 - データとロジックの分離によるモデル貧血化 - ドメイン知識の表現が困難
Rich Domain Model (DDD手法)¶
// ドメイン知識を含むモデル
class Field {
private grid: (Puyo | null)[][];
findConnectedPuyos(position: Position, color: PuyoColor): PuyoGroup {
// ドメインロジックを内包
}
detectErasableGroups(): PuyoGroup[] {
// ゲーム特有のルールを表現
}
}
利点: - ドメイン知識の明確な表現 - ビジネスロジックの集約 - モデルとコードの一致
決定¶
ドメイン駆動設計(DDD)の戦術的パターンを採用する
採用する戦術的パターン¶
- エンティティ (Entities)
- Game: ゲームセッション
-
Field: ゲームフィールド
-
値オブジェクト (Value Objects)
- Puyo: 個々のぷよ
- Position: 座標
- Score: スコア
-
PuyoPair: 組ぷよ
-
集約 (Aggregates)
- Game集約: ゲーム状態の一貫性保証
-
Chain集約: 連鎖処理の整合性管理
-
ドメインサービス (Domain Services)
- ChainDetectionService: 連鎖検出ロジック
- GravityService: 重力計算ロジック
-
ScoreCalculationService: スコア計算ロジック
-
ドメインイベント (Domain Events)
- PuyoPlacedEvent: ぷよ配置イベント
- ChainOccurredEvent: 連鎖発生イベント
-
GameOverEvent: ゲーム終了イベント
-
リポジトリ (Repositories)
- GameRepository: ゲーム状態の永続化
- ScoreRepository: スコア履歴の管理
ドメインモデル設計¶
境界づけられたコンテキスト¶
┌─────────────────────────────────────┐
│ ゲームプレイコンテキスト │
├─────────────────────────────────────┤
│ ・ゲームセッション管理 │
│ ・ぷよ操作・配置 │
│ ・連鎖・消去システム │
│ ・フィールド状態管理 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ スコアリングコンテキスト │
├─────────────────────────────────────┤
│ ・スコア計算 │
│ ・ハイスコア管理 │
│ ・統計・記録 │
└─────────────────────────────────────┘
ユビキタス言語¶
日本語用語 | 英語用語 | 定義 |
---|---|---|
ぷよ | Puyo | 色付きのゲーム要素 |
組ぷよ | PuyoPair | 2個セットの操作対象ぷよ |
フィールド | Field | 6×12のゲーム盤面 |
連鎖 | Chain | 消去による連続反応 |
全消し | AllClear | 全ぷよ消去によるボーナス |
窒息 | Suffocation | 配置不可能によるゲーム終了 |
集約の設計¶
Game集約¶
class Game {
private constructor(
private readonly id: GameId,
private field: Field,
private currentPuyo: PuyoPair,
private nextPuyo: PuyoPair,
private score: Score,
private state: GameState
) {
this.ensureInvariants();
}
handleInput(command: InputCommand): DomainEvent[] {
// 不変条件を保ちながら状態変更
// ドメインイベントの発行
}
private ensureInvariants(): void {
// ドメインルールの検証
}
}
不変条件(Invariants)¶
- ゲーム中は必ず現在のぷよが存在する
- フィールドの範囲内にのみぷよを配置できる
- スコアは負の値にならない
- 連鎖数は論理的な上限を超えない
ドメインサービスの責務¶
ChainDetectionService¶
class ChainDetectionService {
detectChains(field: Field): ChainResult {
// 1. 消去対象グループの特定
// 2. 連鎖の進行計算
// 3. 最終的な連鎖結果の生成
}
private findConnectedGroup(
field: Field,
startPos: Position,
color: PuyoColor
): PuyoGroup {
// 深度優先探索による連結ぷよ検索
}
}
実装戦略¶
段階的実装アプローチ¶
- Phase 1: コアドメインモデル
- 基本エンティティ・値オブジェクトの実装
- 不変条件の定義・実装
-
基本的なドメインサービス
-
Phase 2: 集約とイベント
- 集約ルートの実装
- ドメインイベントの設計・実装
-
イベント駆動の状態変更
-
Phase 3: 高度なパターン
- 仕様パターンの導入
- ドメインイベントハンドラー
- サガパターン(複雑な処理フロー)
テスト戦略¶
ドメインモデルテスト¶
describe('Game', () => {
test('ぷよを配置した時に適切な状態変化が起こること', () => {
// Given: 初期状態のゲーム
const game = GameFactory.createNewGame();
// When: ぷよ配置コマンドを実行
const events = game.handleInput(new PlacePuyoCommand(position));
// Then: 期待される状態変化とイベント発行
expect(events).toContainEqual(
expect.objectContaining({
type: 'PuyoPlacedEvent',
position: position
})
);
});
});
結果¶
期待される効果¶
- 表現力: ビジネスルールがコードで明確に表現される
- 保守性: ドメイン知識の変更が局所化される
- テスタビリティ: ドメインロジックが独立してテストできる
- 拡張性: 新機能追加時の影響範囲が明確
メトリクス¶
- 循環的複雑度: 平均5以下を目標
- テストカバレッジ: ドメイン層95%以上
- ドメイン純粋性: 外部依存0%(ドメイン層)
リスク・制約¶
潜在的リスク¶
- 初期実装コスト: 単純なCRUDより開発時間増加
- 学習コスト: DDD概念の理解が必要
- 過度なモデリング: 不要な複雑性の導入リスク
対策¶
- 段階的導入: 最小限のDDDパターンから開始
- 継続的リファクタリング: ドメイン理解の深化に応じて改善
- 実践的アプローチ: 理論より実装価値を重視
関連ADR¶
- ADR-001: アーキテクチャ選定
- ADR-002: フロントエンド技術スタック選定
- ADR-004: テスト戦略
- ADR-005: 状態管理戦略
日付: 2025-08-12
作成者: Claude Code
レビュー者: 開発チーム・ドメインエキスパート
次回見直し: 2025-11-12(3ヶ月後)