🚀 cc-sdd 超詳細完全ガイド
📋
仕様駆動開発ツール
初学者向け・超詳細・実践的マスター講座
1 / 100
2025年1月
KEELシステム部 開発サービス1課 内藤翔太
©2025 KEEL
📚 今日学ぶこと(超詳細版)
- 🎯 cc-sddとは何か?(詳細解説)
- 💡 仕様駆動開発の基本概念(実例付き)
- ⚙️ 環境構築とセットアップ(手順詳細)
- 🛠️ 基本操作とコマンド(実践的解説)
- 📈 開発フローの実践(画面付き)
- ✅ 実際のプロジェクト例(完全デモ)
- 🔧 トラブルシューティング(解決法詳細)
- 🚀 高度な活用法(プロ向け)
2 / 100
🤔 cc-sddとは?(超詳細解説)
📖 正式名称と定義
cc-sdd = Code Completion for Spec-Driven Development
仕様駆動開発を支援する国産AI開発ツール
🏢 開発元と背景
- 開発元: 日本国内の開発チーム
- 開発背景: 日本の開発現場の課題を解決
- 対象言語: 日本語での仕様記述に最適化
- リリース: 2024年後半に正式リリース
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
📚 仕様駆動開発の歴史と背景
🕰️ 開発手法の進化
🎯 日本の開発現場の課題
- 仕様の曖昧性: 要件が曖昧で実装時に迷いが生じる
- ドキュメント不足: 設計書が古くなり実装と乖離
- 品質のばらつき: 開発者によって品質に差が出る
- 保守性の低下: 仕様変更時の影響範囲が不明
- コミュニケーションコスト: 仕様確認のための会議が増加
6 / 100
🌟 cc-sddのメリット(超詳細分析)
🎯 品質向上のメカニズム
1
仕様の明確化: 曖昧な要件を具体的な仕様に変換
2
自動検証: 仕様と実装の整合性を自動チェック
3
バグの早期発見: 仕様段階で問題を発見
4
テストケース自動生成: 仕様からテストケースを自動作成
📊 定量的な効果
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環境
- Claude Code: 公式推奨環境
- Cursor: AI統合IDE
- VS Code: 拡張機能対応
- JetBrains系: プラグイン対応
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
品質確認
🎯 各フェーズの詳細
- 要件定義: 機能要件・非機能要件の詳細化
- 設計: アーキテクチャ・データベース・API設計
- タスク分解: 実装可能な単位への細分化
- 実装: AI支援による効率的なコーディング
- 検証: 仕様との整合性確認
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が生成するコードの特徴
📝 生成されるコード例
// ユーザー登録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
パフォーマンス: 効率性の確認
⚠️ よくある問題と対策
🚨 実装時の注意点
- 環境変数: 本番環境の設定を確認
- データベース: マイグレーションの実行
- 依存関係: パッケージのバージョン確認
- エラーハンドリング: 適切な例外処理
- ログ出力: デバッグ情報の確認
🔄 実装サイクル
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()でコンポーネントを遅延読み込み
キャッシュ戦略
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
デプロイ実行: 本番環境への展開
🎯 デプロイ戦略
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
🔧 運用・保守フェーズ
📋 継続的改善
🎯 運用KPI
運用KPI指標:
┌─────────────────────────────────────┐
│ 指標名 │ 目標値 │ 現在値 │
├─────────────────────────────────────┤
│ 稼働率 │ 99.9% │ 99.95% │
│ 平均レスポンス時間 │ <2秒 │ 1.2秒 │
│ エラー率 │ <1% │ 0.3% │
│ ユーザー満足度 │ >4.5 │ 4.7 │
│ セキュリティインシデント│ 0件 │ 0件 │
└─────────────────────────────────────┘
🚀 継続的改善
cc-sddを使用した開発プロセスにより、高品質なアプリケーションを継続的に改善していきます。
50 / 100
❮
❯