🚀 cc-sdd 超詳細完全ガイド

📋

仕様駆動開発ツール

初学者向け・超詳細・実践的マスター講座

1 / 100
2025年1月
KEELシステム部 開発サービス1課 内藤翔太

📚 今日学ぶこと(超詳細版)

2 / 100

🤔 cc-sddとは?(超詳細解説)

📖 正式名称と定義

cc-sdd = Code Completion for Spec-Driven Development

仕様駆動開発を支援する国産AI開発ツール

🏢 開発元と背景

3 / 100

⚙️ cc-sddの技術的特徴

🧠 AI技術の活用

Claude 3.5 Sonnet

最新のAI技術を活用したコード生成

自然言語処理

日本語での仕様記述を理解

コード解析

既存コードの構造を自動解析

推論エンジン

仕様から実装を自動推論

🔧 対応技術スタック

対応言語・フレームワーク: - JavaScript/TypeScript (Node.js, React, Vue.js) - Python (Django, Flask, FastAPI) - Java (Spring Boot, Maven) - C# (.NET Core, ASP.NET) - Go (Gin, Echo) - Ruby (Rails, Sinatra) - PHP (Laravel, Symfony)
4 / 100

💡 仕様駆動開発(SDD)とは?(詳細解説)

📖 基本概念

仕様駆動開発は、コードを書く前に仕様を明確に定義し、その仕様に基づいて開発を進める手法です。

📊 従来開発 vs 仕様駆動開発の比較

❌ 従来の開発

1. 要件収集(曖昧)

2. いきなりコーディング

3. テスト(後回し)

4. バグ修正(繰り返し)

✅ 仕様駆動開発

1. 要件収集(詳細)

2. 仕様定義(明確化)

3. 設計(構造化)

4. 実装(仕様準拠)

5. 検証(仕様確認)

5 / 100

📚 仕様駆動開発の歴史と背景

🕰️ 開発手法の進化

1970年代

ウォーターフォール

1990年代

アジャイル開発

2000年代

TDD/BDD

2020年代

AI支援SDD

🎯 日本の開発現場の課題

6 / 100

🌟 cc-sddのメリット(超詳細分析)

🎯 品質向上のメカニズム

1 仕様の明確化: 曖昧な要件を具体的な仕様に変換
2 自動検証: 仕様と実装の整合性を自動チェック
3 バグの早期発見: 仕様段階で問題を発見
4 テストケース自動生成: 仕様からテストケースを自動作成

📊 定量的な効果

バグ削減

約60%のバグ削減

開発時間短縮

約40%の時間短縮

ドキュメント精度

約80%の精度向上

保守性向上

約50%の保守コスト削減

7 / 100

🛠️ 環境構築の準備

📋 必要な環境

⚠️ 前提条件

  • Node.js: バージョン18以上が必要
  • npm: 最新版を推奨
  • Git: バージョン管理用
  • インターネット接続: AI API接続用

🔍 環境確認コマンド

# Node.jsのバージョン確認 node --version # 期待値: v18.x.x 以上 # npmのバージョン確認 npm --version # 期待値: 9.x.x 以上 # Gitのバージョン確認 git --version # 期待値: 2.x.x 以上

📱 対応IDE環境

8 / 100

⚙️ インストール手順(超詳細)

📦 ステップ1: 基本インストール

1 ターミナルを開く
2 プロジェクトディレクトリに移動
3 インストールコマンド実行
# 基本的なインストール(Claude Code用) npx cc-sdd@latest --lang ja # 実行例 $ npx cc-sdd@latest --lang ja ✓ ダウンロード中... ✓ 依存関係をインストール中... ✓ 設定ファイルを作成中... ✓ インストール完了! cc-sddが正常にインストールされました。 次のステップ: /kiro:steering を実行してください。

✅ インストール成功の確認

インストールが成功すると、以下のメッセージが表示されます。

9 / 100

🔧 環境別インストール手順

🎯 Cursor IDE用インストール

# Cursor IDE用インストール npx cc-sdd@latest --cursor --lang ja # 実行結果 $ npx cc-sdd@latest --cursor --lang ja ✓ Cursor IDE用設定を適用中... ✓ カスタムコマンドを追加中... ✓ 設定完了! Cursor IDEでcc-sddが利用可能になりました。 以下のコマンドが使用できます: - /kiro:steering - /kiro:spec-init - /kiro:spec-design - /kiro:spec-tasks - /kiro:spec-impl

🤖 Gemini CLI用インストール

# Gemini CLI用インストール npx cc-sdd@latest --gemini-cli --lang ja # 実行結果 $ npx cc-sdd@latest --gemini-cli --lang ja ✓ Gemini CLI用設定を適用中... ✓ APIキー設定を確認中... ✓ 設定完了! Gemini CLIでcc-sddが利用可能になりました。

⚠️ 注意事項

複数のIDE環境を使用する場合は、それぞれ個別にインストールが必要です。

10 / 100

🚀 初期セットアップ(詳細手順)

📁 ステップ1: プロジェクトナレッジの作成

1 IDEでプロジェクトを開く
2 チャット欄にコマンド入力
3 AIがプロジェクト解析を実行
4 生成されたファイルを確認
# コマンド実行 /kiro:steering # 実行結果例 🔍 プロジェクトを解析中... 📁 ファイル構造を確認中... 🔧 技術スタックを特定中... 📝 ドキュメントを生成中... ✓ 以下のファイルが生成されました: - kiro/product.md (プロダクト概要) - kiro/structure.md (プロジェクト構造) - kiro/tech.md (技術スタック) ✓ プロジェクトナレッジの作成が完了しました。
11 / 100

📄 生成されるファイルの詳細

📁 ファイル構造

プロジェクトルート/ ├── kiro/ │ ├── product.md # プロダクト概要 │ ├── structure.md # プロジェクト構造 │ └── tech.md # 技術スタック ├── .kiro/ # 設定ディレクトリ │ └── config.json # 設定ファイル └── その他のプロジェクトファイル

📝 product.md の内容例

# プロダクト概要 ## プロジェクト名 タスク管理アプリケーション ## 概要 チームでのタスク管理を効率化するWebアプリケーション ## 主要機能 - ユーザー管理 - タスク作成・編集 - 進捗管理 - チーム連携 ## 対象ユーザー - 開発チーム - プロジェクトマネージャー - 営業チーム

🔧 tech.md の内容例

# 技術スタック ## フロントエンド - React 18.x - TypeScript 5.x - Material-UI 5.x - Vite 4.x ## バックエンド - Node.js 18.x - Express.js 4.x - TypeScript 5.x ## データベース - PostgreSQL 15.x - Prisma ORM ## インフラ - Docker - AWS (予定)
12 / 100

🔄 cc-sdd開発フロー(超詳細解説)

📊 全体フロー図

1. 要件定義

/kiro:spec-init

仕様の明確化

2. 設計

/kiro:spec-design

システム設計

3. タスク分解

/kiro:spec-tasks

実装タスク作成

4. 実装

/kiro:spec-impl

AI支援実装

5. 検証

/kiro:spec-validate

品質確認

🎯 各フェーズの詳細

13 / 100

📝 要件定義フェーズ(詳細手順)

🚀 ステップ1: 要件定義の開始

1 コマンド実行: /kiro:spec-init <プロジェクト説明>
2 AI分析: プロジェクトの概要を理解
3 ファイル生成: spec.json と requirements.md を作成
4 要件記述: requirements.md を編集
# 要件定義の開始例 /kiro:spec-init "チーム向けタスク管理アプリケーション" # 実行結果 📋 プロジェクト: チーム向けタスク管理アプリケーション 🔍 既存ファイルを分析中... 📝 要件定義テンプレートを作成中... ✓ 以下のファイルが生成されました: - .kiro/specs/task-app/spec.json - .kiro/specs/task-app/requirements.md 📝 requirements.md を編集して要件を詳細化してください。
14 / 100

📋 requirements.md の記述例

📝 機能要件の詳細記述

# タスク管理アプリケーション 要件定義書 ## 1. 機能要件 ### 1.1 ユーザー管理機能 #### 1.1.1 ユーザー登録 - **概要:** 新規ユーザーがアカウントを作成できる - **入力項目:** - メールアドレス(必須、形式チェック) - パスワード(必須、8文字以上) - ユーザー名(必須、2-20文字) - 所属チーム(選択式) - **出力:** 登録完了メール送信、ダッシュボードへ遷移 - **例外処理:** 重複メールアドレス、無効な形式 #### 1.1.2 ログイン機能 - **概要:** 登録済みユーザーがログインできる - **入力項目:** メールアドレス、パスワード - **認証方式:** JWT トークン - **セッション管理:** 30日間有効 - **セキュリティ:** ログイン試行回数制限(5回/時間) ### 1.2 タスク管理機能 #### 1.2.1 タスク作成 - **概要:** 新しいタスクを作成できる - **入力項目:** - タイトル(必須、1-100文字) - 説明(任意、最大1000文字) - 期限(日時選択) - 優先度(高/中/低) - 担当者(チームメンバーから選択) - ラベル(複数選択可) - **出力:** タスクID生成、通知送信

📊 非機能要件の記述例

## 2. 非機能要件 ### 2.1 パフォーマンス要件 - **レスポンス時間:** ページ表示3秒以内 - **同時接続数:** 1000ユーザー - **データベース:** クエリ実行1秒以内 - **ファイルアップロード:** 10MB以内 ### 2.2 セキュリティ要件 - **認証:** JWT トークン方式 - **暗号化:** HTTPS通信必須 - **データ保護:** 個人情報の暗号化保存 - **アクセス制御:** ロールベースアクセス制御 ### 2.3 可用性要件 - **稼働率:** 99.9%以上 - **メンテナンス:** 月1回、深夜帯 - **バックアップ:** 日次自動バックアップ - **災害復旧:** RTO 4時間以内
15 / 100

🎨 設計フェーズ(詳細手順)

🏗️ ステップ2: 設計の開始

1 コマンド実行: /kiro:spec-design <プロジェクト名> -y
2 AI分析: 要件から設計を自動生成
3 設計確認: 生成された設計をレビュー
4 設計修正: 必要に応じて修正
# 設計フェーズの開始 /kiro:spec-design task-app -y # 実行結果 🎨 プロジェクト: task-app の設計を開始します 📋 要件定義書を読み込み中... 🏗️ システム設計を生成中... 📊 データベース設計を作成中... 🔌 API設計を生成中... 🎨 UI/UX設計を作成中... ✓ design.md が生成されました 📝 生成された設計を確認してください

✅ 設計生成のポイント

AIが要件定義書を基に、包括的な設計ドキュメントを自動生成します。生成後は必ず内容を確認し、必要に応じて修正を行ってください。

16 / 100

📐 design.md の内容例

🏗️ システムアーキテクチャ設計

# タスク管理アプリケーション 設計書 ## 1. システムアーキテクチャ ### 1.1 全体構成 ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ フロントエンド │ │ バックエンド │ │ データベース │ │ (React) │◄──►│ (Express.js) │◄──►│ (PostgreSQL) │ │ │ │ │ │ │ │ - UI Components │ │ - API Routes │ │ - User Data │ │ - State Mgmt │ │ - Business Logic│ │ - Task Data │ │ - Auth Service │ │ - Data Access │ │ - Team Data │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` ### 1.2 技術スタック - **フロントエンド:** React 18, TypeScript, Material-UI, Vite - **バックエンド:** Node.js, Express.js, TypeScript - **データベース:** PostgreSQL 15, Prisma ORM - **認証:** JWT, bcrypt - **デプロイ:** Docker, AWS ECS

🗄️ データベース設計

## 2. データベース設計 ### 2.1 エンティティ関係図 ``` User (ユーザー) ├── id: UUID (PK) ├── email: VARCHAR(255) (UNIQUE) ├── password_hash: VARCHAR(255) ├── username: VARCHAR(50) ├── team_id: UUID (FK) └── created_at: TIMESTAMP Team (チーム) ├── id: UUID (PK) ├── name: VARCHAR(100) ├── description: TEXT └── created_at: TIMESTAMP Task (タスク) ├── id: UUID (PK) ├── title: VARCHAR(100) ├── description: TEXT ├── due_date: TIMESTAMP ├── priority: ENUM('high', 'medium', 'low') ├── status: ENUM('todo', 'in_progress', 'done') ├── assignee_id: UUID (FK) ├── creator_id: UUID (FK) └── created_at: TIMESTAMP ```
17 / 100

🔌 API設計の詳細

📡 RESTful API設計

## 3. API設計 ### 3.1 認証API #### POST /api/auth/register - **概要:** ユーザー登録 - **リクエスト:** ```json { "email": "user@example.com", "password": "password123", "username": "testuser", "team_id": "uuid" } ``` - **レスポンス:** ```json { "success": true, "data": { "user": { "id": "uuid", "email": "user@example.com", "username": "testuser" }, "token": "jwt_token" } } ``` #### POST /api/auth/login - **概要:** ユーザーログイン - **リクエスト:** ```json { "email": "user@example.com", "password": "password123" } ```

📋 タスク管理API

### 3.2 タスク管理API #### GET /api/tasks - **概要:** タスク一覧取得 - **クエリパラメータ:** - status: todo|in_progress|done - assignee_id: UUID - priority: high|medium|low - **レスポンス:** ```json { "success": true, "data": [ { "id": "uuid", "title": "タスクタイトル", "description": "タスクの説明", "due_date": "2025-01-31T23:59:59Z", "priority": "high", "status": "todo", "assignee": { "id": "uuid", "username": "担当者名" } } ], "pagination": { "page": 1, "limit": 20, "total": 100 } } ```
18 / 100

📋 タスク分解フェーズ(詳細手順)

🔨 ステップ3: タスク生成

1 コマンド実行: /kiro:spec-tasks <プロジェクト名> -y
2 AI分析: 設計から実装タスクを生成
3 タスク確認: 生成されたタスクをレビュー
4 タスク調整: 必要に応じて優先度や順序を調整
# タスク分解の開始 /kiro:spec-tasks task-app -y # 実行結果 📋 プロジェクト: task-app のタスク分解を開始します 🎨 設計書を読み込み中... 🔨 実装タスクを生成中... 📊 依存関係を分析中... ⏰ 推定工数を計算中... ✓ tasks.md が生成されました 📝 生成されたタスクを確認してください

✅ タスク分解のポイント

AIが設計書を基に、実装可能な単位までタスクを細分化します。各タスクには推定工数と依存関係も含まれます。

19 / 100

📝 tasks.md の内容例

🔨 実装タスクリスト

# タスク管理アプリケーション 実装タスク ## 1. 環境構築・基盤準備 ### 1.1 プロジェクト初期化 - **概要:** プロジェクトの基本構造を作成 - **工数:** 2時間 - **依存関係:** なし - **成果物:** package.json, ディレクトリ構造 - **詳細:** - Node.js プロジェクトの初期化 - TypeScript設定 - ESLint/Prettier設定 - Git設定 ### 1.2 データベース設定 - **概要:** PostgreSQLデータベースの設定 - **工数:** 3時間 - **依存関係:** 1.1 - **成果物:** データベーススキーマ, Prisma設定 - **詳細:** - PostgreSQLインストール・設定 - Prisma ORM設定 - マイグレーションファイル作成 - シードデータ作成 ## 2. バックエンド実装 ### 2.1 認証システム - **概要:** JWT認証システムの実装 - **工数:** 6時間 - **依存関係:** 1.2 - **成果物:** 認証API, ミドルウェア - **詳細:** - ユーザー登録API実装 - ログインAPI実装 - JWT生成・検証 - パスワードハッシュ化

📊 タスクの優先度と依存関係

## 3. タスク実行順序 ### フェーズ1: 基盤構築(1週間) 1. プロジェクト初期化 (1.1) 2. データベース設定 (1.2) 3. 基本API構造 (2.1) ### フェーズ2: コア機能実装(2週間) 4. 認証システム (2.1) 5. ユーザー管理 (2.2) 6. タスクCRUD (2.3) ### フェーズ3: フロントエンド実装(2週間) 7. 基本UI実装 (3.1) 8. 認証画面 (3.2) 9. タスク管理画面 (3.3) ### フェーズ4: テスト・デプロイ(1週間) 10. 単体テスト (4.1) 11. 統合テスト (4.2) 12. デプロイ (4.3)
20 / 100

💻 実装フェーズ(詳細解説)

🚀 ステップ4: 実装の開始

1 コマンド実行: /kiro:spec-impl <プロジェクト名>
2 AI分析: タスクから実装コードを生成
3 コード確認: 生成されたコードをレビュー
4 テスト実行: 動作確認とテスト

🎯 実装モードの種類

全タスク実行

/kiro:spec-impl task-app

特定タスク実行

/kiro:spec-impl task-app 1.1

複数タスク実行

/kiro:spec-impl task-app 1,2,3

段階的実行

タスクを順番に実行

21 / 100

⚡ 実装コマンド詳細

🔨 基本的な実装コマンド

# 全タスクの実装を開始 /kiro:spec-impl task-app # 実行結果例 🚀 プロジェクト: task-app の実装を開始します 📋 タスクリストを読み込み中... 🔍 依存関係を分析中... ⚡ 実装を開始します... 📝 タスク 1.1: プロジェクト初期化 ├── package.json を作成中... ├── TypeScript設定を作成中... ├── ESLint設定を作成中... └── ディレクトリ構造を作成中... ✅ タスク 1.1 が完了しました

📊 実装進捗の確認

実装進捗レポート: ┌─────────────────────────────────────┐ │ タスクID │ タスク名 │ 状態 │ ├─────────────────────────────────────┤ │ 1.1 │ プロジェクト初期化 │ ✅完了 │ │ 1.2 │ データベース設定 │ 🔄実行中 │ │ 2.1 │ 認証システム │ ⏳待機 │ │ 2.2 │ ユーザー管理 │ ⏳待機 │ └─────────────────────────────────────┘ 進捗率: 25% (1/4 タスク完了)
22 / 100

🤖 AI支援コード生成の詳細

🧠 AIが生成するコードの特徴

仕様準拠

要件定義書に基づいた実装

ベストプラクティス

業界標準のコーディング規約

エラーハンドリング

適切な例外処理を含む

ドキュメント

コメントとJSDoc付き

📝 生成されるコード例

// ユーザー登録API - AI生成コード import { Request, Response } from 'express'; import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); /** * ユーザー登録API * @param req - リクエストオブジェクト * @param res - レスポンスオブジェクト */ export const registerUser = async (req: Request, res: Response) => { try { const { email, password, username, team_id } = req.body; // バリデーション if (!email || !password || !username) { return res.status(400).json({ success: false, message: '必須項目が不足しています' }); } // パスワードハッシュ化 const hashedPassword = await bcrypt.hash(password, 10); // ユーザー作成 const user = await prisma.user.create({ data: { email, password: hashedPassword, username, team_id }, select: { id: true, email: true, username: true, created_at: true } }); // JWT生成 const token = jwt.sign( { userId: user.id }, process.env.JWT_SECRET!, { expiresIn: '30d' } ); res.status(201).json({ success: true, data: { user, token } }); } catch (error) { console.error('ユーザー登録エラー:', error); res.status(500).json({ success: false, message: 'サーバーエラーが発生しました' }); } };
23 / 100

🔍 実装中の確認ポイント

✅ コード品質チェック

1 仕様準拠: 要件定義書との整合性確認
2 コード品質: 可読性・保守性の確認
3 セキュリティ: 脆弱性の確認
4 パフォーマンス: 効率性の確認

⚠️ よくある問題と対策

🚨 実装時の注意点

  • 環境変数: 本番環境の設定を確認
  • データベース: マイグレーションの実行
  • 依存関係: パッケージのバージョン確認
  • エラーハンドリング: 適切な例外処理
  • ログ出力: デバッグ情報の確認

🔄 実装サイクル

実装

AI生成コード

確認

コードレビュー

テスト

動作確認

修正

必要に応じて調整

24 / 100

🗄️ データベース実装詳細

📊 Prismaスキーマの生成

// schema.prisma - AI生成スキーマ generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) email String @unique password String username String team_id String? created_at DateTime @default(now()) updated_at DateTime @updatedAt // リレーション team Team? @relation(fields: [team_id], references: [id]) created_tasks Task[] @relation("TaskCreator") assigned_tasks Task[] @relation("TaskAssignee") @@map("users") } model Team { id String @id @default(cuid()) name String description String? created_at DateTime @default(now()) // リレーション users User[] tasks Task[] @@map("teams") } model Task { id String @id @default(cuid()) title String description String? due_date DateTime? priority Priority @default(MEDIUM) status Status @default(TODO) assignee_id String? creator_id String team_id String? created_at DateTime @default(now()) updated_at DateTime @updatedAt // リレーション assignee User? @relation("TaskAssignee", fields: [assignee_id], references: [id]) creator User @relation("TaskCreator", fields: [creator_id], references: [id]) team Team? @relation(fields: [team_id], references: [id]) @@map("tasks") } enum Priority { HIGH MEDIUM LOW } enum Status { TODO IN_PROGRESS DONE }
25 / 100

🔌 API実装の詳細

🚀 Express.jsルーターの実装

// routes/auth.ts - 認証APIルート import express from 'express'; import { registerUser, loginUser, verifyToken } from '../controllers/authController'; import { validateRegister, validateLogin } from '../middleware/validation'; const router = express.Router(); /** * ユーザー登録 * POST /api/auth/register */ router.post('/register', validateRegister, registerUser); /** * ユーザーログイン * POST /api/auth/login */ router.post('/login', validateLogin, loginUser); /** * トークン検証 * GET /api/auth/verify */ router.get('/verify', verifyToken, (req, res) => { res.json({ success: true, data: { user: req.user } }); }); export default router;

🛡️ ミドルウェアの実装

// middleware/auth.ts - JWT認証ミドルウェア import jwt from 'jsonwebtoken'; import { Request, Response, NextFunction } from 'express'; interface AuthRequest extends Request { user?: any; } export const authenticateToken = ( req: AuthRequest, res: Response, next: NextFunction ) => { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.status(401).json({ success: false, message: 'アクセストークンが必要です' }); } jwt.verify(token, process.env.JWT_SECRET!, (err, user) => { if (err) { return res.status(403).json({ success: false, message: '無効なトークンです' }); } req.user = user; next(); }); };
26 / 100

🎨 フロントエンド実装詳細

⚛️ Reactコンポーネントの実装

// components/TaskList.tsx - タスク一覧コンポーネント import React, { useState, useEffect } from 'react'; import { List, ListItem, ListItemText, ListItemSecondaryAction, IconButton, Chip, Typography, Box } from '@mui/material'; import { Edit, Delete, CheckCircle } from '@mui/icons-material'; import { Task } from '../types/task'; interface TaskListProps { tasks: Task[]; onEdit: (task: Task) => void; onDelete: (id: string) => void; onComplete: (id: string) => void; } export const TaskList: React.FC = ({ tasks, onEdit, onDelete, onComplete }) => { const [filteredTasks, setFilteredTasks] = useState(tasks); useEffect(() => { setFilteredTasks(tasks); }, [tasks]); const getPriorityColor = (priority: string) => { switch (priority) { case 'high': return 'error'; case 'medium': return 'warning'; case 'low': return 'success'; default: return 'default'; } }; const getStatusColor = (status: string) => { switch (status) { case 'done': return 'success'; case 'in_progress': return 'info'; case 'todo': return 'default'; default: return 'default'; } }; return ( タスク一覧 ({filteredTasks.length}件) {filteredTasks.map((task) => ( {task.description} {task.due_date && ( )} } /> onComplete(task.id)}> onEdit(task)}> onDelete(task.id)}> ))} ); };
27 / 100

🔄 状態管理の実装

📦 Redux Toolkitの実装

// store/taskSlice.ts - タスク状態管理 import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import { Task, CreateTaskData, UpdateTaskData } from '../types/task'; import { taskApi } from '../api/taskApi'; interface TaskState { tasks: Task[]; loading: boolean; error: string | null; selectedTask: Task | null; } const initialState: TaskState = { tasks: [], loading: false, error: null, selectedTask: null }; // 非同期アクション export const fetchTasks = createAsyncThunk( 'tasks/fetchTasks', async (_, { rejectWithValue }) => { try { const response = await taskApi.getTasks(); return response.data; } catch (error: any) { return rejectWithValue(error.response?.data?.message || 'タスクの取得に失敗しました'); } } ); export const createTask = createAsyncThunk( 'tasks/createTask', async (taskData: CreateTaskData, { rejectWithValue }) => { try { const response = await taskApi.createTask(taskData); return response.data; } catch (error: any) { return rejectWithValue(error.response?.data?.message || 'タスクの作成に失敗しました'); } } ); export const updateTask = createAsyncThunk( 'tasks/updateTask', async ({ id, data }: { id: string; data: UpdateTaskData }, { rejectWithValue }) => { try { const response = await taskApi.updateTask(id, data); return response.data; } catch (error: any) { return rejectWithValue(error.response?.data?.message || 'タスクの更新に失敗しました'); } } ); const taskSlice = createSlice({ name: 'tasks', initialState, reducers: { setSelectedTask: (state, action: PayloadAction) => { state.selectedTask = action.payload; }, clearError: (state) => { state.error = null; } }, extraReducers: (builder) => { builder // タスク取得 .addCase(fetchTasks.pending, (state) => { state.loading = true; state.error = null; }) .addCase(fetchTasks.fulfilled, (state, action) => { state.loading = false; state.tasks = action.payload; }) .addCase(fetchTasks.rejected, (state, action) => { state.loading = false; state.error = action.payload as string; }) // タスク作成 .addCase(createTask.fulfilled, (state, action) => { state.tasks.push(action.payload); }) // タスク更新 .addCase(updateTask.fulfilled, (state, action) => { const index = state.tasks.findIndex(task => task.id === action.payload.id); if (index !== -1) { state.tasks[index] = action.payload; } }); } }); export const { setSelectedTask, clearError } = taskSlice.actions; export default taskSlice.reducer;
28 / 100

✅ 実装完了の確認

🔍 実装チェックリスト

1 機能テスト: 各機能が正常に動作することを確認
2 APIテスト: エンドポイントの動作確認
3 UI/UX確認: ユーザーインターフェースの確認
4 パフォーマンス: レスポンス時間の確認

📊 実装完了レポート

実装完了レポート: ┌─────────────────────────────────────┐ │ 機能名 │ 状態 │ テスト結果 │ ├─────────────────────────────────────┤ │ ユーザー認証 │ ✅完了 │ ✅合格 │ │ タスク管理 │ ✅完了 │ ✅合格 │ │ チーム管理 │ ✅完了 │ ✅合格 │ │ ダッシュボード │ ✅完了 │ ✅合格 │ │ 通知機能 │ ✅完了 │ ✅合格 │ └─────────────────────────────────────┘ 総実装時間: 40時間 コード品質: A評価 テストカバレッジ: 85%

🎉 実装完了!

すべてのタスクが完了し、アプリケーションが正常に動作しています。次のステップは検証フェーズです。

29 / 100

🔍 検証フェーズ(詳細解説)

✅ ステップ5: 検証の開始

1 コマンド実行: /kiro:spec-validate-gap <プロジェクト名>
2 AI分析: 仕様と実装の整合性をチェック
3 ギャップ分析: 不足している機能を特定
4 修正提案: 改善点の提案
# 検証の開始 /kiro:spec-validate-gap task-app # 実行結果 🔍 プロジェクト: task-app の検証を開始します 📋 要件定義書を読み込み中... 💻 実装コードを分析中... 🔍 仕様と実装の整合性をチェック中... 📊 ギャップ分析を実行中... ✓ 検証が完了しました
30 / 100

📊 検証レポートの詳細

🔍 検証結果の分析

検証レポート: ┌─────────────────────────────────────┐ │ 検証項目 │ 状態 │ 詳細 │ ├─────────────────────────────────────┤ │ 機能要件準拠 │ ✅合格 │ 100%準拠 │ │ 非機能要件準拠 │ ✅合格 │ 95%準拠 │ │ セキュリティ │ ✅合格 │ 脆弱性なし │ │ パフォーマンス │ ✅合格 │ 基準内 │ │ ユーザビリティ │ ⚠️要改善 │ 一部改善必要 │ │ アクセシビリティ │ ⚠️要改善 │ WCAG準拠必要 │ └─────────────────────────────────────┘ ギャップ分析結果: ❌ 未実装機能: 0件 ⚠️ 部分実装: 2件 ✅ 完全実装: 8件

🎯 改善提案

📝 改善が必要な項目

  • ユーザビリティ: モバイル対応の改善
  • アクセシビリティ: スクリーンリーダー対応
  • エラーメッセージ: より分かりやすい表示
  • ローディング状態: ユーザーフィードバック改善
31 / 100

🧪 単体テストの実装

📝 Jest + React Testing Library

// __tests__/TaskList.test.tsx import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import { Provider } from 'react-redux'; import { configureStore } from '@reduxjs/toolkit'; import { TaskList } from '../components/TaskList'; import taskReducer from '../store/taskSlice'; import { Task } from '../types/task'; const mockTasks: Task[] = [ { id: '1', title: 'テストタスク', description: 'テスト用のタスクです', priority: 'high', status: 'todo', due_date: '2025-01-31T23:59:59Z', assignee_id: 'user1', creator_id: 'user1', team_id: 'team1', created_at: '2025-01-01T00:00:00Z', updated_at: '2025-01-01T00:00:00Z' } ]; const createMockStore = (initialState = {}) => { return configureStore({ reducer: { tasks: taskReducer }, preloadedState: { tasks: { tasks: mockTasks, loading: false, error: null, selectedTask: null, ...initialState } } }); }; describe('TaskList', () => { it('タスク一覧が正しく表示される', () => { const store = createMockStore(); const mockOnEdit = jest.fn(); const mockOnDelete = jest.fn(); const mockOnComplete = jest.fn(); render( ); expect(screen.getByText('テストタスク')).toBeInTheDocument(); expect(screen.getByText('テスト用のタスクです')).toBeInTheDocument(); expect(screen.getByText('high')).toBeInTheDocument(); }); it('タスク完了ボタンが正しく動作する', () => { const store = createMockStore(); const mockOnComplete = jest.fn(); render( ); const completeButton = screen.getByRole('button', { name: /complete/i }); fireEvent.click(completeButton); expect(mockOnComplete).toHaveBeenCalledWith('1'); }); });
32 / 100

🔗 統合テストの実装

🌐 API統合テスト

// __tests__/integration/auth.test.ts import request from 'supertest'; import app from '../../src/app'; import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); describe('認証API統合テスト', () => { beforeEach(async () => { // テストデータのクリーンアップ await prisma.user.deleteMany(); await prisma.team.deleteMany(); }); afterAll(async () => { await prisma.$disconnect(); }); describe('POST /api/auth/register', () => { it('新しいユーザーが正常に登録される', async () => { const userData = { email: 'test@example.com', password: 'password123', username: 'testuser', team_id: null }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(201); expect(response.body.success).toBe(true); expect(response.body.data.user.email).toBe(userData.email); expect(response.body.data.token).toBeDefined(); expect(response.body.data.user.password).toBeUndefined(); }); it('重複メールアドレスでエラーが返される', async () => { const userData = { email: 'test@example.com', password: 'password123', username: 'testuser' }; // 最初のユーザー登録 await request(app) .post('/api/auth/register') .send(userData); // 重複登録の試行 const response = await request(app) .post('/api/auth/register') .send(userData) .expect(400); expect(response.body.success).toBe(false); expect(response.body.message).toContain('既に登録されているメールアドレス'); }); }); describe('POST /api/auth/login', () => { beforeEach(async () => { // テスト用ユーザーの作成 await request(app) .post('/api/auth/register') .send({ email: 'test@example.com', password: 'password123', username: 'testuser' }); }); it('正しい認証情報でログインできる', async () => { const response = await request(app) .post('/api/auth/login') .send({ email: 'test@example.com', password: 'password123' }) .expect(200); expect(response.body.success).toBe(true); expect(response.body.data.token).toBeDefined(); expect(response.body.data.user.email).toBe('test@example.com'); }); it('間違ったパスワードでログインできない', async () => { const response = await request(app) .post('/api/auth/login') .send({ email: 'test@example.com', password: 'wrongpassword' }) .expect(401); expect(response.body.success).toBe(false); expect(response.body.message).toContain('認証情報が正しくありません'); }); }); });
33 / 100

🎭 E2Eテストの実装

🤖 Playwrightテスト

// e2e/task-management.spec.ts import { test, expect } from '@playwright/test'; test.describe('タスク管理機能', () => { test.beforeEach(async ({ page }) => { // ログインページに移動 await page.goto('http://localhost:3000/login'); // ログイン await page.fill('[data-testid="email-input"]', 'test@example.com'); await page.fill('[data-testid="password-input"]', 'password123'); await page.click('[data-testid="login-button"]'); // ダッシュボードに移動するまで待機 await page.waitForURL('http://localhost:3000/dashboard'); }); test('新しいタスクを作成できる', async ({ page }) => { // タスク作成ボタンをクリック await page.click('[data-testid="create-task-button"]'); // タスク作成フォームを入力 await page.fill('[data-testid="task-title-input"]', 'E2Eテストタスク'); await page.fill('[data-testid="task-description-input"]', 'Playwrightで作成されたテストタスクです'); await page.selectOption('[data-testid="priority-select"]', 'high'); await page.fill('[data-testid="due-date-input"]', '2025-12-31'); // タスクを作成 await page.click('[data-testid="submit-task-button"]'); // 成功メッセージの確認 await expect(page.locator('[data-testid="success-message"]')).toContainText('タスクが正常に作成されました'); // タスク一覧に表示されることを確認 await expect(page.locator('[data-testid="task-list"]')).toContainText('E2Eテストタスク'); }); test('タスクを編集できる', async ({ page }) => { // 既存のタスクをクリック await page.click('[data-testid="task-item-1"]'); // 編集ボタンをクリック await page.click('[data-testid="edit-task-button"]'); // タイトルを変更 await page.fill('[data-testid="task-title-input"]', '編集されたタスク'); // 変更を保存 await page.click('[data-testid="save-task-button"]'); // 変更が反映されることを確認 await expect(page.locator('[data-testid="task-item-1"]')).toContainText('編集されたタスク'); }); test('タスクを削除できる', async ({ page }) => { // タスクの削除ボタンをクリック await page.click('[data-testid="delete-task-button-1"]'); // 削除確認ダイアログでOKをクリック await page.click('[data-testid="confirm-delete-button"]'); // タスクが削除されることを確認 await expect(page.locator('[data-testid="task-item-1"]')).not.toBeVisible(); }); test('タスクを完了状態に変更できる', async ({ page }) => { // タスクの完了ボタンをクリック await page.click('[data-testid="complete-task-button-1"]'); // タスクの状態が完了に変わることを確認 await expect(page.locator('[data-testid="task-status-1"]')).toContainText('完了'); // 完了したタスクが完了済みセクションに表示されることを確認 await expect(page.locator('[data-testid="completed-tasks-section"]')).toContainText('タスク1'); }); });
34 / 100

⚡ パフォーマンステスト

📊 Lighthouseテスト

// performance/lighthouse.test.js const lighthouse = require('lighthouse'); const chromeLauncher = require('chrome-launcher'); async function runLighthouse(url, options, config = null) { const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] }); options.port = chrome.port; const runnerResult = await lighthouse(url, options, config); await chrome.kill(); return runnerResult.lhr; } describe('パフォーマンステスト', () => { test('メインページのパフォーマンス', async () => { const result = await runLighthouse('http://localhost:3000/dashboard'); // パフォーマンススコアの確認 expect(result.categories.performance.score).toBeGreaterThan(0.9); // 具体的なメトリクスの確認 const metrics = result.audits; // First Contentful Paint expect(metrics['first-contentful-paint'].numericValue).toBeLessThan(2000); // Largest Contentful Paint expect(metrics['largest-contentful-paint'].numericValue).toBeLessThan(2500); // Cumulative Layout Shift expect(metrics['cumulative-layout-shift'].numericValue).toBeLessThan(0.1); // Total Blocking Time expect(metrics['total-blocking-time'].numericValue).toBeLessThan(300); }, 60000); test('APIレスポンス時間', async () => { const startTime = Date.now(); const response = await fetch('http://localhost:3000/api/tasks'); const endTime = Date.now(); const responseTime = endTime - startTime; expect(responseTime).toBeLessThan(1000); // 1秒以内 expect(response.status).toBe(200); }); });

🎯 パフォーマンス改善のポイント

コード分割

React.lazy()でコンポーネントを遅延読み込み

画像最適化

WebP形式とレスポンシブ画像

キャッシュ戦略

Service WorkerとHTTPキャッシュ

データベース最適化

インデックスとクエリ最適化

35 / 100

🔒 セキュリティテスト

🛡️ 脆弱性スキャン

// security/security.test.js const request = require('supertest'); const app = require('../src/app'); describe('セキュリティテスト', () => { describe('認証・認可', () => { it('認証なしでAPIアクセスを拒否する', async () => { const response = await request(app) .get('/api/tasks') .expect(401); expect(response.body.message).toContain('認証が必要です'); }); it('無効なJWTトークンでアクセスを拒否する', async () => { const response = await request(app) .get('/api/tasks') .set('Authorization', 'Bearer invalid-token') .expect(403); expect(response.body.message).toContain('無効なトークン'); }); }); describe('入力値検証', () => { it('XSS攻撃を防ぐ', async () => { const maliciousInput = '<script>alert("XSS")</script>'; const response = await request(app) .post('/api/tasks') .set('Authorization', 'Bearer valid-token') .send({ title: maliciousInput, description: 'テストタスク' }) .expect(400); expect(response.body.message).toContain('無効な入力'); }); it('SQLインジェクションを防ぐ', async () => { const maliciousInput = "'; DROP TABLE users; --"; const response = await request(app) .post('/api/tasks') .set('Authorization', 'Bearer valid-token') .send({ title: 'テスト', description: maliciousInput }); // データベースが破壊されていないことを確認 const tasks = await request(app) .get('/api/tasks') .set('Authorization', 'Bearer valid-token') .expect(200); expect(tasks.body.data).toBeDefined(); }); }); describe('レート制限', () => { it('過度なリクエストを制限する', async () => { const promises = []; // 100回連続でリクエストを送信 for (let i = 0; i < 100; i++) { promises.push( request(app) .post('/api/auth/login') .send({ email: 'test@example.com', password: 'wrongpassword' }) ); } const responses = await Promise.all(promises); // レート制限に引っかかるレスポンスがあることを確認 const rateLimitedResponses = responses.filter(r => r.status === 429); expect(rateLimitedResponses.length).toBeGreaterThan(0); }); }); });
36 / 100

🤖 テスト自動化の設定

📋 GitHub Actions CI/CD

# .github/workflows/test.yml name: Test Suite on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run unit tests run: npm run test:unit env: DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }} JWT_SECRET: ${{ secrets.TEST_JWT_SECRET }} - name: Upload coverage uses: codecov/codecov-action@v3 with: file: ./coverage/lcov.info integration-tests: runs-on: ubuntu-latest services: postgres: image: postgres:15 env: POSTGRES_PASSWORD: postgres POSTGRES_DB: test_db options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run database migrations run: npm run db:migrate env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db - name: Run integration tests run: npm run test:integration env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db JWT_SECRET: test-secret e2e-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Install Playwright run: npx playwright install --with-deps - name: Build application run: npm run build - name: Start application run: npm start & - name: Run E2E tests run: npm run test:e2e - name: Upload test results uses: actions/upload-artifact@v3 if: failure() with: name: playwright-report path: playwright-report/
37 / 100

📈 テスト結果の分析

📊 テストカバレッジレポート

テストカバレッジレポート: ┌─────────────────────────────────────┐ │ ファイル名 │ 行数 │ カバレッジ │ ├─────────────────────────────────────┤ │ src/controllers/ │ 1200 │ 95% │ │ src/models/ │ 800 │ 98% │ │ src/routes/ │ 600 │ 92% │ │ src/middleware/ │ 400 │ 90% │ │ src/utils/ │ 300 │ 88% │ ├─────────────────────────────────────┤ │ 合計 │ 3300 │ 94% │ └─────────────────────────────────────┘ テスト結果サマリー: ✅ 単体テスト: 245/245 合格 (100%) ✅ 統合テスト: 89/89 合格 (100%) ✅ E2Eテスト: 45/45 合格 (100%) ✅ パフォーマンステスト: 合格 ✅ セキュリティテスト: 合格 品質メトリクス: - バグ密度: 0.5 bugs/KLOC - コード複雑度: 低 - 保守性指数: A評価

🎯 改善提案

📝 カバレッジ改善が必要な箇所

  • src/utils/: エラーハンドリングのテスト追加
  • src/middleware/: エッジケースのテスト追加
  • 例外処理: 異常系のテストケース追加
38 / 100

✅ テスト完了の確認

🔍 テスト完了チェックリスト

1 単体テスト: 全コンポーネント・関数のテスト完了
2 統合テスト: API・データベース連携のテスト完了
3 E2Eテスト: ユーザーフローのテスト完了
4 パフォーマンステスト: レスポンス時間の確認完了
5 セキュリティテスト: 脆弱性チェック完了

🎉 テスト完了!

すべてのテストが合格し、アプリケーションの品質が確認されました。次のステップはデプロイフェーズです。

📋 テスト完了レポート

最終テスト結果: ┌─────────────────────────────────────┐ │ テスト種別 │ 結果 │ 詳細 │ ├─────────────────────────────────────┤ │ 単体テスト │ ✅合格 │ 245/245 │ │ 統合テスト │ ✅合格 │ 89/89 │ │ E2Eテスト │ ✅合格 │ 45/45 │ │ パフォーマンス │ ✅合格 │ 基準クリア │ │ セキュリティ │ ✅合格 │ 脆弱性なし │ ├─────────────────────────────────────┤ │ 総合評価 │ ✅合格 │ リリース可 │ └─────────────────────────────────────┘
39 / 100

🚀 デプロイフェーズ(詳細解説)

🌐 ステップ6: デプロイの準備

1 環境設定: 本番環境の設定準備
2 インフラ構築: サーバー・データベースの準備
3 CI/CD設定: 自動デプロイパイプライン構築
4 デプロイ実行: 本番環境への展開

🎯 デプロイ戦略

Blue-Green

ゼロダウンタイムデプロイ

Canary

段階的ロールアウト

Rolling

順次更新デプロイ

Recreate

完全停止・再起動

40 / 100

🏗️ インフラ構築詳細

☁️ AWS環境構築

# terraform/main.tf - インフラ定義 provider "aws" { region = "ap-northeast-1" } # VPC設定 resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" enable_dns_hostnames = true enable_dns_support = true tags = { Name = "task-app-vpc" } } # サブネット設定 resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" availability_zone = "ap-northeast-1a" map_public_ip_on_launch = true tags = { Name = "task-app-public-subnet" } } # RDS PostgreSQL resource "aws_db_instance" "postgres" { identifier = "task-app-db" engine = "postgres" engine_version = "15.4" instance_class = "db.t3.micro" allocated_storage = 20 max_allocated_storage = 100 storage_type = "gp2" storage_encrypted = true db_name = "taskapp" username = "postgres" password = var.db_password vpc_security_group_ids = [aws_security_group.rds.id] db_subnet_group_name = aws_db_subnet_group.main.name backup_retention_period = 7 backup_window = "03:00-04:00" maintenance_window = "sun:04:00-sun:05:00" skip_final_snapshot = true tags = { Name = "task-app-database" } } # ECS クラスター resource "aws_ecs_cluster" "main" { name = "task-app-cluster" setting { name = "containerInsights" value = "enabled" } } # Application Load Balancer resource "aws_lb" "main" { name = "task-app-alb" internal = false load_balancer_type = "application" security_groups = [aws_security_group.alb.id] subnets = [aws_subnet.public.id] enable_deletion_protection = false tags = { Name = "task-app-alb" } }
41 / 100

🐳 Docker設定

📦 マルチステージビルド

# Dockerfile - バックエンド FROM node:18-alpine AS base # 依存関係のインストール FROM base AS deps WORKDIR /app COPY package*.json ./ RUN npm ci --only=production && npm cache clean --force # ビルドステージ FROM base AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 本番ステージ FROM base AS runner WORKDIR /app ENV NODE_ENV=production RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nodejs COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist COPY --from=builder --chown=nodejs:nodejs /app/package.json ./package.json USER nodejs EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" CMD ["node", "dist/index.js"] # docker-compose.yml version: '3.8' services: app: build: . ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=postgresql://postgres:password@db:5432/taskapp - JWT_SECRET=your-jwt-secret depends_on: - db networks: - app-network db: image: postgres:15-alpine environment: - POSTGRES_DB=taskapp - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" networks: - app-network nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/ssl depends_on: - app networks: - app-network volumes: postgres_data: networks: app-network: driver: bridge
42 / 100

🔄 CI/CDパイプライン

⚙️ GitHub Actions設定

# .github/workflows/deploy.yml name: Deploy to Production on: push: branches: [ main ] tags: [ 'v*' ] env: AWS_REGION: ap-northeast-1 ECR_REPOSITORY: task-app ECS_SERVICE: task-app-service ECS_CLUSTER: task-app-cluster ECS_TASK_DEFINITION: task-app-task-definition jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: npm run test:all - name: Build application run: npm run build build-and-deploy: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Build, tag, and push image to Amazon ECR id: build-image env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ github.sha }} run: | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" - name: Fill in the new image ID in the Amazon ECS task definition id: task-def uses: aws-actions/amazon-ecs-render-task-definition@v1 with: task-definition: .aws/task-definition.json container-name: task-app image: ${{ steps.build-image.outputs.image }} - name: Deploy Amazon ECS task definition uses: aws-actions/amazon-ecs-deploy-task-definition@v1 with: task-definition: ${{ steps.task-def.outputs.task-definition }} service: ${{ env.ECS_SERVICE }} cluster: ${{ env.ECS_CLUSTER }} wait-for-service-stability: true - name: Run database migrations run: | aws ecs run-task \ --cluster ${{ env.ECS_CLUSTER }} \ --task-definition task-app-migration \ --launch-type FARGATE \ --network-configuration "awsvpcConfiguration={subnets=[subnet-12345],securityGroups=[sg-12345],assignPublicIp=ENABLED}"
43 / 100

🔐 環境変数管理

📋 本番環境設定

# 環境変数設定例 # .env.production NODE_ENV=production PORT=3000 # データベース設定 DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@${DB_HOST}:5432/taskapp DB_HOST=task-app-db.cluster-xyz.ap-northeast-1.rds.amazonaws.com DB_PASSWORD=${DB_PASSWORD} # 認証設定 JWT_SECRET=${JWT_SECRET} JWT_EXPIRES_IN=30d # 外部サービス設定 AWS_REGION=ap-northeast-1 AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} # ログ設定 LOG_LEVEL=info LOG_FORMAT=json # セキュリティ設定 CORS_ORIGIN=https://task-app.example.com RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX_REQUESTS=100 # 監視設定 SENTRY_DSN=${SENTRY_DSN} NEW_RELIC_LICENSE_KEY=${NEW_RELIC_LICENSE_KEY} # AWS Systems Manager Parameter Store使用例 import { SSMClient, GetParametersCommand } from "@aws-sdk/client-ssm"; const ssmClient = new SSMClient({ region: process.env.AWS_REGION }); async function loadEnvironmentVariables() { try { const command = new GetParametersCommand({ Names: [ '/task-app/production/database-url', '/task-app/production/jwt-secret', '/task-app/production/sentry-dsn' ], WithDecryption: true }); const response = await ssmClient.send(command); response.Parameters?.forEach(param => { if (param.Name && param.Value) { const key = param.Name.split('/').pop()?.replace(/-/g, '_').toUpperCase(); if (key) { process.env[key] = param.Value; } } }); } catch (error) { console.error('環境変数の読み込みに失敗しました:', error); process.exit(1); } }
44 / 100

📊 監視・ログ設定

🔍 CloudWatch設定

# ログ設定 import winston from 'winston'; import CloudWatchTransport from 'winston-cloudwatch'; const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ), defaultMeta: { service: 'task-app' }, transports: [ new winston.transports.Console({ format: winston.format.simple() }), new CloudWatchTransport({ logGroupName: '/aws/ecs/task-app', logStreamName: 'application-logs', awsRegion: process.env.AWS_REGION, retentionInDays: 30 }) ] }); # メトリクス収集 import { createPrometheusMetrics } from 'prom-client'; const register = new promClient.Registry(); // HTTP リクエストメトリクス const httpRequestDuration = new promClient.Histogram({ name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'route', 'status_code'], buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10] }); // データベース接続メトリクス const dbConnectionGauge = new promClient.Gauge({ name: 'database_connections_active', help: 'Number of active database connections' }); // カスタムビジネスメトリクス const taskCreationCounter = new promClient.Counter({ name: 'tasks_created_total', help: 'Total number of tasks created', labelNames: ['priority', 'team_id'] }); register.registerMetric(httpRequestDuration); register.registerMetric(dbConnectionGauge); register.registerMetric(taskCreationCounter); # ヘルスチェックエンドポイント app.get('/health', async (req, res) => { const healthCheck = { uptime: process.uptime(), message: 'OK', timestamp: Date.now(), checks: { database: await checkDatabaseConnection(), redis: await checkRedisConnection(), external_apis: await checkExternalAPIs() } }; const isHealthy = Object.values(healthCheck.checks).every(check => check.status === 'OK'); res.status(isHealthy ? 200 : 503).json(healthCheck); }); # アラート設定 const alertRules = { highErrorRate: { condition: 'error_rate > 5%', duration: '5m', action: 'send_slack_notification' }, highResponseTime: { condition: 'avg_response_time > 2s', duration: '3m', action: 'send_email_alert' }, lowDiskSpace: { condition: 'disk_usage > 80%', duration: '1m', action: 'create_pagerduty_incident' } };
45 / 100

🔒 セキュリティ設定

🛡️ 本番環境セキュリティ

# セキュリティミドルウェア import helmet from 'helmet'; import rateLimit from 'express-rate-limit'; import cors from 'cors'; // Helmet設定 app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], fontSrc: ["'self'", "https://fonts.gstatic.com"], imgSrc: ["'self'", "data:", "https:"], scriptSrc: ["'self'"], connectSrc: ["'self'"] } }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true } })); // レート制限設定 const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15分 max: 100, // 最大100リクエスト message: 'リクエストが多すぎます。しばらくしてから再試行してください。', standardHeaders: true, legacyHeaders: false }); const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, // 認証エンドポイントは5回まで skipSuccessfulRequests: true }); app.use('/api/', limiter); app.use('/api/auth/', authLimiter); // CORS設定 app.use(cors({ origin: process.env.CORS_ORIGIN?.split(',') || ['http://localhost:3000'], credentials: true, optionsSuccessStatus: 200 })); # 入力値検証 import Joi from 'joi'; const taskSchema = Joi.object({ title: Joi.string().min(1).max(100).required(), description: Joi.string().max(1000).optional(), priority: Joi.string().valid('high', 'medium', 'low').required(), due_date: Joi.date().iso().greater('now').optional(), assignee_id: Joi.string().uuid().optional() }); const validateTask = (req, res, next) => { const { error } = taskSchema.validate(req.body); if (error) { return res.status(400).json({ success: false, message: '入力値が無効です', errors: error.details.map(detail => detail.message) }); } next(); }; # セキュリティヘッダー app.use((req, res, next) => { res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-XSS-Protection', '1; mode=block'); res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); next(); });
46 / 100

💾 バックアップ・災害復旧

🔄 データ保護戦略

# 自動バックアップ設定 import { PrismaClient } from '@prisma/client'; import AWS from 'aws-sdk'; const s3 = new AWS.S3(); const prisma = new PrismaClient(); // データベースバックアップ async function createDatabaseBackup() { try { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupName = `task-app-backup-${timestamp}.sql`; // RDSスナップショット作成 const rds = new AWS.RDS(); const snapshotParams = { DBInstanceIdentifier: 'task-app-db', DBSnapshotIdentifier: `task-app-snapshot-${timestamp}`, Tags: [ { Key: 'Environment', Value: 'production' }, { Key: 'Service', Value: 'task-app' } ] }; await rds.createDBSnapshot(snapshotParams).promise(); console.log(`データベーススナップショット作成完了: ${backupName}`); // S3へのエクスポート const exportParams = { DBInstanceIdentifier: 'task-app-db', S3BucketName: 'task-app-backups', S3Prefix: `database-exports/${timestamp}/`, ExportTaskIdentifier: `task-app-export-${timestamp}`, KmsKeyId: 'arn:aws:kms:ap-northeast-1:123456789012:key/12345678-1234-1234-1234-123456789012' }; await rds.startExportTask(exportParams).promise(); console.log('S3エクスポート開始'); } catch (error) { console.error('バックアップ作成エラー:', error); throw error; } } # 災害復旧手順 const disasterRecoveryPlan = { // RTO: 4時間以内 // RPO: 1時間以内 steps: [ { name: '障害検知', duration: '5分', action: 'CloudWatchアラートによる自動検知' }, { name: '影響範囲特定', duration: '15分', action: 'ログ分析とシステム状況確認' }, { name: '復旧手順実行', duration: '2時間', action: 'バックアップからの復旧' }, { name: '機能確認', duration: '30分', action: '全機能の動作確認' }, { name: '本格運用再開', duration: '30分', action: 'トラフィックの段階的復旧' } ] }; # 復旧テスト async function runDisasterRecoveryTest() { console.log('災害復旧テスト開始'); // 1. 最新バックアップの確認 const latestBackup = await findLatestBackup(); console.log(`最新バックアップ: ${latestBackup.timestamp}`); // 2. 復旧環境でのテスト const testEnvironment = await createTestEnvironment(); await restoreFromBackup(testEnvironment, latestBackup); // 3. 機能テスト実行 const testResults = await runFunctionalityTests(testEnvironment); // 4. テスト環境のクリーンアップ await cleanupTestEnvironment(testEnvironment); console.log('災害復旧テスト完了:', testResults); return testResults; }
47 / 100

⚡ パフォーマンス最適化

🚀 本番環境最適化

# Redis キャッシュ設定 import Redis from 'ioredis'; const redis = new Redis({ host: process.env.REDIS_HOST, port: process.env.REDIS_PORT, password: process.env.REDIS_PASSWORD, retryDelayOnFailover: 100, maxRetriesPerRequest: 3 }); // キャッシュミドルウェア const cacheMiddleware = (duration = 300) => { return async (req, res, next) => { const key = `cache:${req.originalUrl}`; try { const cached = await redis.get(key); if (cached) { return res.json(JSON.parse(cached)); } // レスポンスをキャッシュ const originalSend = res.json; res.json = function(data) { redis.setex(key, duration, JSON.stringify(data)); originalSend.call(this, data); }; next(); } catch (error) { console.error('キャッシュエラー:', error); next(); } }; }; # データベースクエリ最適化 import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient({ log: ['query', 'info', 'warn', 'error'], datasources: { db: { url: process.env.DATABASE_URL } } }); // 接続プール設定 const optimizedPrisma = new PrismaClient({ datasources: { db: { url: `${process.env.DATABASE_URL}?connection_limit=20&pool_timeout=20` } } }); // 効率的なクエリ例 async function getTasksWithOptimization(filters) { return await prisma.task.findMany({ where: filters, include: { assignee: { select: { id: true, username: true, email: true } }, creator: { select: { id: true, username: true } } }, orderBy: [ { priority: 'desc' }, { created_at: 'desc' } ], take: 50 // ページネーション }); } # フロントエンド最適化 // React.lazy による遅延読み込み const Dashboard = React.lazy(() => import('./pages/Dashboard')); const TaskList = React.lazy(() => import('./components/TaskList')); const UserManagement = React.lazy(() => import('./pages/UserManagement')); // Service Worker によるキャッシュ const CACHE_NAME = 'task-app-v1'; const urlsToCache = [ '/', '/static/js/bundle.js', '/static/css/main.css', '/manifest.json' ]; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then((cache) => cache.addAll(urlsToCache)) ); }); self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request) .then((response) => { if (response) { return response; } return fetch(event.request); }) ); });
48 / 100

✅ デプロイ完了の確認

🔍 デプロイ後チェックリスト

1 ヘルスチェック: アプリケーションの正常性確認
2 機能テスト: 主要機能の動作確認
3 パフォーマンス確認: レスポンス時間の測定
4 監視設定確認: アラート・ログの動作確認
5 セキュリティ確認: セキュリティヘッダー・SSL確認

📊 デプロイ完了レポート

デプロイ完了レポート: ┌─────────────────────────────────────┐ │ 項目 │ 状態 │ 詳細 │ ├─────────────────────────────────────┤ │ アプリケーション │ ✅正常 │ レスポンス正常 │ │ データベース │ ✅正常 │ 接続確認済み │ │ ロードバランサー │ ✅正常 │ ヘルスチェックOK│ │ SSL証明書 │ ✅正常 │ 有効期限OK │ │ 監視システム │ ✅正常 │ アラート動作 │ │ バックアップ │ ✅正常 │ 自動実行確認 │ ├─────────────────────────────────────┤ │ 総合評価 │ ✅完了 │ 本番運用開始可能│ └─────────────────────────────────────┘ デプロイ情報: - デプロイ時刻: 2025-01-15 14:30:00 JST - バージョン: v1.0.0 - 環境: production - URL: https://task-app.example.com

🎉 デプロイ完了!

アプリケーションが正常に本番環境にデプロイされました。ユーザーがアクセス可能な状態です。

49 / 100

🔧 運用・保守フェーズ

📋 継続的改善

監視・アラート

24/7システム監視

パフォーマンス分析

継続的な最適化

セキュリティ更新

定期的な脆弱性対応

ユーザーフィードバック

機能改善の継続

🎯 運用KPI

運用KPI指標: ┌─────────────────────────────────────┐ │ 指標名 │ 目標値 │ 現在値 │ ├─────────────────────────────────────┤ │ 稼働率 │ 99.9% │ 99.95% │ │ 平均レスポンス時間 │ <2秒 │ 1.2秒 │ │ エラー率 │ <1% │ 0.3% │ │ ユーザー満足度 │ >4.5 │ 4.7 │ │ セキュリティインシデント│ 0件 │ 0件 │ └─────────────────────────────────────┘

🚀 継続的改善

cc-sddを使用した開発プロセスにより、高品質なアプリケーションを継続的に改善していきます。

50 / 100