経緯は「IBM Bobを使って、PostgreSQLのToDoアプリをDb2へ変換してみよう」を参照下さい。
こちらはIBM Bobが作成したアーキテクチャ説明文書です。
アーキテクチャ説明文書:PostgreSQL → Db2 移行
作成日: 2026-03-04
プロジェクト: PERN TODO → Db2 TODO 移行
📋 目次
概要
本ドキュメントでは、PostgreSQLベースのTodoアプリケーションをDb2ベースに移行する際のアーキテクチャ変更について説明します。
移行の目的
- PostgreSQL依存からDb2への移行
- コネクションプール実装による性能向上
- エラーハンドリングの強化
- ユーザー体験の向上(ローディング表示)
変更前のアーキテクチャ(PostgreSQL)
システム構成図
主要コンポーネント
1. クライアント層
- React + Vite: フロントエンドフレームワーク
- 機能: Todo CRUD操作のUI提供
2. アプリケーション層
- Express: Node.jsウェブフレームワーク
- CORS: クロスオリジンリクエスト対応
- Todo Routes: CRUD APIエンドポイント
3. データアクセス層
- pg Pool: PostgreSQL接続プール
-
特徴:
- 自動接続管理
- トップレベルで初期化
-
pool.query()で直接SQL実行
4. データベース層
- PostgreSQL: リレーショナルデータベース
-
テーブル:
todos(id, title, done)
PostgreSQL版の特徴
// db.js - シンプルな初期化
export const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20,
idleTimeoutMillis: 30000
});
// routes/todos.js - 直接クエリ実行
const { rows } = await pool.query(
'INSERT INTO todos (title) VALUES ($1) RETURNING *',
[title]
);
利点:
- シンプルな実装
- RETURNING句でINSERT結果を即座に取得
- 自動的な接続管理
課題:
- PostgreSQL専用(他DBへの移行困難)
- エラーハンドリングが不十分
変更後のアーキテクチャ(Db2)
システム構成図
主要コンポーネント
1. クライアント層(拡張)
- React + Vite: フロントエンドフレームワーク
- Loading State: 処理中の表示管理
- Error Display: エラーメッセージ表示
-
新機能:
- ローディング表示(⏳ 処理中、お待ちください...)
- ボタンのdisable制御
- エラーメッセージ表示
2. アプリケーション層(拡張)
- Express: Node.jsウェブフレームワーク
- Pool Initializer: プール初期化処理
-
新機能:
- 起動時のプール初期化
- グレースフルシャットダウン
3. データアクセス層(新設計)
- Connection Pool: ibm_db.Pool
- executeQuery Helper: クエリ実行ヘルパー関数
- Connection Manager: 接続の取得・返却管理
- Error Handler: エラーハンドリング
-
特徴:
- 明示的な接続管理(open/close)
- タイムアウト処理
- エラーハンドリング強化
4. データベース層
- IBM Db2 v12.1: エンタープライズデータベース
- スキーマ: 環境変数で指定
-
テーブル:
SCHEMA.TODOS(ID, TITLE, DONE)
Db2版の特徴
// db.js - 明示的な初期化
export const pool = new ibm_db.Pool();
pool.setMaxPoolSize(MAX_CONNECTIONS);
export async function initializePool() {
return new Promise((resolve, reject) => {
pool.open(connStr, (err, conn) => {
if (err) reject(err);
else {
poolInitialized = true;
conn.close(() => resolve());
}
});
});
}
// executeQuery - ヘルパー関数
export async function executeQuery(sql, params = []) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Query timeout after 30 seconds'));
}, 30000);
pool.open(connStr, (err, conn) => {
if (err) {
clearTimeout(timeout);
return reject(err);
}
conn.query(sql, params, (err, result) => {
clearTimeout(timeout);
conn.close(() => {
if (err) reject(err);
else resolve(result);
});
});
});
});
}
// routes/todos.js - 2段階クエリ
// 1. INSERT実行
await executeQuery(
`INSERT INTO ${TABLE} (TITLE, DONE) VALUES (?, ?)`,
[title, false]
);
// 2. 挿入されたデータを取得
const rows = await executeQuery(
`SELECT ID, TITLE, DONE FROM ${TABLE}
ORDER BY ID DESC
FETCH FIRST 1 ROW ONLY`
);
利点:
- Db2に最適化された実装
- 明示的なリソース管理
- 強化されたエラーハンドリング
- タイムアウト処理
課題:
- 実装が複雑
- RETURNING句が使えない(2段階クエリ必要)
主要な変更点
1. データベースドライバ
| 項目 | PostgreSQL | Db2 |
|---|---|---|
| パッケージ | pg |
ibm_db |
| 接続方法 | new Pool({ ... }) |
new ibm_db.Pool() + pool.open()
|
| クエリ実行 | pool.query(sql, params) |
conn.query(sql, params, callback) |
| 接続管理 | 自動 | 手動(open/close) |
2. SQL構文
| 項目 | PostgreSQL | Db2 |
|---|---|---|
| パラメータ | $1, $2, $3 |
?, ?, ? |
| RETURNING句 | サポート | 非サポート |
| カラム名 | 小文字 | 大文字 |
| スキーマ指定 | オプション | 推奨 |
| LIMIT句 | LIMIT n |
FETCH FIRST n ROWS ONLY |
3. データ型
| 項目 | PostgreSQL | Db2 |
|---|---|---|
| BOOLEAN | true/false |
1/0 |
| SERIAL | SERIAL |
INT GENERATED ALWAYS AS IDENTITY |
| TEXT | TEXT |
VARCHAR(n) または CLOB
|
4. 接続管理
PostgreSQL(自動管理)
// 接続取得・返却は自動
const { rows } = await pool.query('SELECT * FROM todos');
Db2(手動管理)
// 接続取得
pool.open(connStr, (err, conn) => {
// クエリ実行
conn.query('SELECT * FROM TODOS', (err, result) => {
// 接続返却(必須)
conn.close(() => {
// 処理完了
});
});
});
データフロー
CREATE(Todo作成)のフロー
READ(Todo取得)のフロー
コネクションプール設計
プール設定
// 環境変数から設定を読み込み
const MAX_CONNECTIONS = parseInt(process.env.DB2_MAX_CONNECTIONS || '5', 10);
const MIN_CONNECTIONS = parseInt(process.env.DB2_MIN_CONNECTIONS || '1', 10);
// プール作成
export const pool = new ibm_db.Pool();
pool.setMaxPoolSize(MAX_CONNECTIONS);
// 初期化(最小接続数を確保)
pool.init(MIN_CONNECTIONS, connStr);
プールのライフサイクル
接続の取得・返却フロー
エラーハンドリング戦略
エラーの種類と対応
エラーハンドリングの実装
// executeQuery内のエラーハンドリング
export async function executeQuery(sql, params = []) {
return new Promise((resolve, reject) => {
// タイムアウト設定
const timeout = setTimeout(() => {
console.error('Query timeout after 30 seconds');
reject(new Error('Query timeout after 30 seconds'));
}, 30000);
pool.open(connStr, (err, conn) => {
if (err) {
clearTimeout(timeout);
console.error('Failed to get connection from pool:', err);
return reject(err);
}
conn.query(sql, params, (err, result) => {
clearTimeout(timeout);
// 必ず接続を返却
conn.close(() => {
if (err) {
console.error('Query execution error:', err);
console.error('SQL:', sql);
console.error('Params:', params);
reject(err);
} else {
resolve(result);
}
});
});
});
});
}
クライアント側のエラー表示
// App.jsx
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
async function addTodo(e) {
e.preventDefault();
if (!title.trim()) return;
setLoading(true);
setError(null);
try {
const response = await fetch(`${API}/todos`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ title })
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
setTitle('');
await load();
} catch (err) {
console.error(err);
setError('Todo の追加に失敗しました');
} finally {
setLoading(false);
}
}
まとめ
移行による改善点
- エンタープライズDB対応: Db2 v12.1への完全対応
- リソース管理の明示化: 接続の取得・返却を明示的に管理
- エラーハンドリング強化: タイムアウト、接続エラー等の適切な処理
- UX向上: ローディング表示、エラーメッセージ表示
技術的な学び
- ドライバの違い: PostgreSQLとDb2のドライバAPIの違いを理解
- SQL方言: データベースごとのSQL構文の違いを理解
- 接続管理: 明示的な接続管理の重要性を理解
- エラーハンドリング: 多層的なエラーハンドリングの実装
作成者: Bob (AI Assistant)
最終更新: 2026-03-04
バージョン: 1.0