Skip to content

Commit 50af6f8

Browse files
committed
docs(db): apply v3 migration feedback from sample-ai-storybook
Lessons learned from migrating a real app (sample-ai-storybook) to v3 revealed gaps in documentation and a false-positive bug in SQL validation. - dsql-compat: strip SQL comments (block/inline) before pattern matching to prevent false positives on keywords inside comments (e.g. "-- was DROP COLUMN but recreated") - AGENTS.md: add missing TRUNCATE constraint to DSQL constraints list - db/README: add snapshot sync verification procedure after custom migrations (generate → nothing to migrate) - migration-prompt: add Json column conversion checklist (Prisma auto parse vs Drizzle manual), FK cascade deletion warning, String[] array literal conversion for pg_dump data, .env.local Docker contamination warning
1 parent f764b29 commit 50af6f8

5 files changed

Lines changed: 103 additions & 44 deletions

File tree

.serverless-full-stack-webapp-starter-kit/docs/v3.0.0/migration-prompt.ja.md

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
このドキュメントは v2 から v3 への移行計画を立案するための下地(テンプレート)である。各派生プロジェクトはユーザー固有のスキーマ、独自拡張、データ量を持つため、このドキュメントをそのまま実行するのではなく、Phase 1 の事前評価結果を踏まえてプロジェクト固有の移行計画を作成すること。
66

77
計画立案の流れ:
8+
89
1. このドキュメント全体を読み、フェーズ構成と行動規約を把握する
910
2. Phase 1(バックアップと事前評価)を実行し、ユーザーのスキーマ・データ・独自拡張を分析する
1011
3. 分析結果に基づき、各フェーズの具体的なタスクとチェックポイントをプロジェクト固有の計画として文書化する
@@ -31,38 +32,38 @@
3132

3233
以下のファイルはユーザー固有のカスタマイズを含まない基盤コードであり、v3 キットからそのままコピーする。変換や手書きは不要。
3334

34-
| コピー元(v3 キット) | 備考 |
35-
|----------------------|------|
36-
| `pnpm-workspace.yaml` ||
37-
| `oxlintrc.json` ||
38-
| `.oxfmtrc.json` ||
39-
| `.dockerignore` ||
40-
| `packages/db/src/client.ts` | Proxy 遅延初期化 + globalThis シングルトン |
41-
| `packages/db/src/migrate.ts` | マイグレーションランナーコアロジック |
42-
| `packages/db/src/dsql-compat.ts` | SQL 変換 + バリデーション |
43-
| `packages/db/src/cli.ts` | CLI エントリポイント |
44-
| `packages/db/src/check-dsql-compat.ts` | drizzle-kit generate 後処理 |
45-
| `packages/db/drizzle.config.ts` ||
46-
| `packages/db/package.json` ||
47-
| `packages/db/tsconfig.json` ||
48-
| `packages/shared-types/package.json` ||
49-
| `packages/shared-types/tsconfig.json` ||
50-
| `apps/cdk/lib/constructs/database.ts` | DSQL CfnCluster + IAM 認証 |
51-
| `apps/cdk/lib/constructs/dsql-migrator/` | Dockerfile, handler.ts, index.ts 一式 |
52-
| `scripts/dsql.sh` | 開発用 DSQL クラスタの作成・削除 |
35+
| コピー元(v3 キット) | 備考 |
36+
| ---------------------------------------- | ------------------------------------------ |
37+
| `pnpm-workspace.yaml` | |
38+
| `oxlintrc.json` | |
39+
| `.oxfmtrc.json` | |
40+
| `.dockerignore` | |
41+
| `packages/db/src/client.ts` | Proxy 遅延初期化 + globalThis シングルトン |
42+
| `packages/db/src/migrate.ts` | マイグレーションランナーコアロジック |
43+
| `packages/db/src/dsql-compat.ts` | SQL 変換 + バリデーション |
44+
| `packages/db/src/cli.ts` | CLI エントリポイント |
45+
| `packages/db/src/check-dsql-compat.ts` | drizzle-kit generate 後処理 |
46+
| `packages/db/drizzle.config.ts` | |
47+
| `packages/db/package.json` | |
48+
| `packages/db/tsconfig.json` | |
49+
| `packages/shared-types/package.json` | |
50+
| `packages/shared-types/tsconfig.json` | |
51+
| `apps/cdk/lib/constructs/database.ts` | DSQL CfnCluster + IAM 認証 |
52+
| `apps/cdk/lib/constructs/dsql-migrator/` | Dockerfile, handler.ts, index.ts 一式 |
53+
| `scripts/dsql.sh` | 開発用 DSQL クラスタの作成・削除 |
5354

5455
以下はユーザー固有の変換が必要なため、コピーではなく手書き・変換する:
5556

56-
| ファイル | 理由 |
57-
|---------|------|
58-
| `packages/db/src/schema.ts` | ユーザーが追加したテーブル・カラムを含む |
59-
| `packages/db/migrations/` | ユーザーのスキーマに対応した初期マイグレーション SQL。v3 キットの `0001_initial.sql` はサンプルスキーマ用なのでコピーしない |
60-
| `packages/shared-types/src/job-payload.ts` | ユーザーが追加したジョブ型を含む |
61-
| `apps/async-job/src/handler.ts` | ユーザーが追加したジョブの switch 分岐を含む |
62-
| `apps/async-job/src/jobs/` | ユーザーが追加したジョブハンドラを含む |
63-
| `apps/webapp/Dockerfile` | v3 キットをベースに、ユーザーが追加した依存やビルド引数を反映する |
64-
| `apps/async-job/Dockerfile` | 同上 |
65-
| `apps/webapp/next.config.ts` | v3 では `transpilePackages: ['@repo/db', '@repo/shared-types']` の追加が必要。ユーザーの既存設定を保持しつつマージする |
57+
| ファイル | 理由 |
58+
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
59+
| `packages/db/src/schema.ts` | ユーザーが追加したテーブル・カラムを含む |
60+
| `packages/db/migrations/` | ユーザーのスキーマに対応した初期マイグレーション SQL。v3 キットの `0001_initial.sql` はサンプルスキーマ用なのでコピーしない |
61+
| `packages/shared-types/src/job-payload.ts` | ユーザーが追加したジョブ型を含む |
62+
| `apps/async-job/src/handler.ts` | ユーザーが追加したジョブの switch 分岐を含む |
63+
| `apps/async-job/src/jobs/` | ユーザーが追加したジョブハンドラを含む |
64+
| `apps/webapp/Dockerfile` | v3 キットをベースに、ユーザーが追加した依存やビルド引数を反映する |
65+
| `apps/async-job/Dockerfile` | 同上 |
66+
| `apps/webapp/next.config.ts` | v3 では `transpilePackages: ['@repo/db', '@repo/shared-types']` の追加が必要。ユーザーの既存設定を保持しつつマージする |
6667

6768
## Phase 1: バックアップと事前評価
6869

@@ -91,16 +92,17 @@ pg_dump --data-only -h <aurora-endpoint> -U <user> -d <db> > data-v2.sql
9192

9293
schema.prisma を主たるデータソースとする。理由: Prisma スキーマはモデル定義が構造化されており、フィールド型・リレーション・デフォルト値の対応関係が明確。ダンプ SQL は PostgreSQL の DDL がそのまま含まれ、DSQL 非互換パターンの分離が困難。schema-v2.sql はインデックスや実行時に追加された制約の確認に補助的に使う。
9394

94-
| 検出対象 | DSQL での対処 | 判断基準 |
95-
|---------|-------------|---------|
96-
| `SERIAL` / `BIGSERIAL` 主キー | `uuid().defaultRandom()` または IDENTITY 列 | 既存データに外部参照がある場合は UUID 変換時に参照元も更新が必要 |
97-
| `ENUM`| `text()` + Zod バリデーション | 既存の ENUM 値を洗い出し、Zod スキーマに列挙する |
98-
| `JSON` / `JSONB` カラム | `text()` + アプリ層でシリアライズ | 既存データの JSON 構造を確認し、型定義を作成する |
99-
| 外部キー制約(`@relation`| 削除(Drizzle `relations()` で代替) | アプリ層で参照整合性を担保する必要があるか評価 |
100-
| インデックス(`@@index`| `CREATE INDEX ASYNC` に変換 ||
101-
| `Decimal` / `Float`| Drizzle は `string` を返す(Prisma は `number`| アプリコードで数値演算している箇所を特定 |
102-
| `@updatedAt` | `.$onUpdate(() => new Date())` ||
103-
| `zod-prisma-types` 等の生成 Zod スキーマ | 手書きまたは `drizzle-zod` で置き換え | 生成ファイルの一覧を特定 |
95+
| 検出対象 | DSQL での対処 | 判断基準 |
96+
| ---------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
97+
| `SERIAL` / `BIGSERIAL` 主キー | `uuid().defaultRandom()` または IDENTITY 列 | 既存データに外部参照がある場合は UUID 変換時に参照元も更新が必要 |
98+
| `ENUM`| `text()` + Zod バリデーション | 既存の ENUM 値を洗い出し、Zod スキーマに列挙する |
99+
| `JSON` / `JSONB` カラム | `text()` + アプリ層でシリアライズ | 既存データの JSON 構造を確認し、型定義を作成する。**Prisma は Json 型を自動で parse/stringify するが、Drizzle の text() は手動変換が必要。** Phase 3-4 で全ての読み書き箇所に `JSON.parse`/`JSON.stringify` を追加すること |
100+
| 外部キー制約(`@relation`| 削除(Drizzle `relations()` で代替) | **`onDelete: Cascade` / `onDelete: SetNull` に依存する削除ロジックを特定すること。** DSQL は FK をサポートしないため、cascade 削除はアプリ層で `db.transaction()` 内の明示的な削除に変換が必要 |
101+
| `String[]`| `text()` + JSON シリアライズ | **`pg_dump` は PostgreSQL 配列リテラル `{}` 形式で出力する。** Phase 5-2 のデータ移行時に JSON 配列 `[]` に変換が必要(`JSON.parse('{}')` はオブジェクトを返すため) |
102+
| インデックス(`@@index`| `CREATE INDEX ASYNC` に変換 ||
103+
| `Decimal` / `Float`| Drizzle は `string` を返す(Prisma は `number`| アプリコードで数値演算している箇所を特定 |
104+
| `@updatedAt` | `.$onUpdate(() => new Date())` ||
105+
| `zod-prisma-types` 等の生成 Zod スキーマ | 手書きまたは `drizzle-zod` で置き換え | 生成ファイルの一覧を特定 |
104106

105107
各テーブルの行数も記録する — 3,000行超のテーブルは Phase 5-2 でバッチ移行が必要。
106108

@@ -135,6 +137,7 @@ v3 キットのディレクトリ構造を参照し、ユーザーのプロジ
135137
- `packages/shared-types/` を新規作成
136138

137139
ユーザーが追加した独自コードの配置先を判断する:
140+
138141
- DB アクセスを含む共有ロジック → `packages/` 配下に抽出を検討
139142
- webapp 固有のロジック → `apps/webapp/` に残す
140143
- 非同期ジョブ → `apps/async-job/src/jobs/` に移動し、ペイロード型を `packages/shared-types/` に追加
@@ -163,6 +166,7 @@ rg 'npm |npx ' -g '!node_modules' -g '!pnpm-lock.yaml'
163166
```
164167

165168
主な変換:
169+
166170
- `npm ci``pnpm install --frozen-lockfile`
167171
- `npm run <script>``pnpm run <script>`
168172
- `npx <cmd>``pnpm exec <cmd>`
@@ -212,10 +216,13 @@ pnpm --filter @repo/db run generate
212216
ユーザーのコードベースで Prisma を import している全ファイルを特定し(`rg '@prisma|from.*prisma' --type ts`)、Drizzle API に変換する。v3 キットの Server Action 実装を参照パターンとして使うこと。
213217

214218
主な変換ポイント:
219+
215220
- `import { prisma } from '@/lib/prisma'``import { db } from '@repo/db/client'`
216221
- `import { ... } from '@prisma/client'``import { ... } from '@repo/db/schema'`
217222
- v2 の `prisma.ts`(リトライ拡張付き PrismaClient)は削除。DSQL は IAM 認証で接続し、Aurora v2 のコールドスタート・idle timeout 問題がないためリトライロジックは不要
218223
- `next.config.ts``transpilePackages: ['@repo/db', '@repo/shared-types']` を追加(ユーザーの既存設定を保持しつつマージ)
224+
- **Json カラムの全使用箇所を洗い出す**`rg 'Json|\.json\b' --type ts` でスキーマ定義と読み書き箇所を特定)。Prisma は Json 型を自動で parse/stringify するが、Drizzle の `text()` は手動変換が必要。読み出し時に `JSON.parse()`、書き込み時に `JSON.stringify()` を追加すること
225+
- **Prisma の nested create(暗黙トランザクション)を `db.transaction()` に変換する。** 特に `onDelete: Cascade` に依存していた削除ロジックは、`db.transaction()` 内で子テーブルを先に削除してから親テーブルを削除するように書き換えること
219226

220227
### 3-5. クリーンアップ
221228

@@ -234,6 +241,7 @@ bash scripts/dsql.sh create --region <region>
234241
このスクリプトは開発用 DSQL クラスタを作成し、`packages/db/.env` に接続情報を自動で書き込む。
235242

236243
検証手順:
244+
237245
1. マイグレーションを実行してスキーマが DSQL に通ることを確認:
238246
```bash
239247
pnpm --filter @repo/db run migrate
@@ -245,6 +253,7 @@ bash scripts/dsql.sh create --region <region>
245253
3. 問題があればスキーマやアプリコードを修正し、再検証する
246254

247255
検証完了後、開発用クラスタは残しておく(Phase 5 の本番移行完了後に削除):
256+
248257
```bash
249258
# Phase 5 完了後に実行
250259
bash scripts/dsql.sh delete --region <region>
@@ -264,10 +273,12 @@ v3 キットの CDK コードを参照し、ユーザーの CDK コードを更
264273
### 4-1. Dockerfile の更新
265274

266275
webapp と async-job の Dockerfile を v3 のパターンに更新する。v3 キットの Dockerfile をベースに、ユーザーが追加した独自の依存やビルド引数を反映する。主な変更点:
276+
267277
- `npm ci` → pnpm workspaces 対応(`corepack enable` + `pnpm install --frozen-lockfile`
268278
- `npx prisma generate` の削除
269279
- esbuild の ESM 出力(`--format=esm`、出力ファイルは `.mjs` 拡張子)
270280
- モノレポルートからのビルドコンテキスト
281+
- **`.dockerignore``**/.env.local`を含めること。**`COPY apps/webapp/``.env.local`が Docker イメージに混入すると、Next.js がビルド時・ランタイムで読み込み、Lambda 環境変数より優先される(例:`AMPLIFY_APP_ORIGIN=http://localhost:3011` が Cognito コールバックを localhost に向ける)
271282

272283
### 4-2. CDK Construct の更新
273284

@@ -312,6 +323,7 @@ docker build --platform linux/arm64 -f apps/webapp/Dockerfile -t test-webapp:loc
312323
```
313324

314325
確認事項:
326+
315327
- esbuild の出力が `.mjs` 拡張子であること
316328
- `@aws/aurora-dsql-node-postgres-connector` がバンドルに含まれていること(`--external:@aws-sdk/*` で除外されないこと)
317329
- migrations/ ディレクトリが正しくコピーされていること
@@ -327,6 +339,7 @@ pnpm --filter @repo/db run migrate
327339
```
328340

329341
確認事項:
342+
330343
- マイグレーションが成功し、`_migrations` テーブルにレコードが挿入されること
331344
- 再実行で冪等(既に適用済みのマイグレーションがスキップされること)
332345

@@ -339,6 +352,7 @@ cd apps/webapp && pnpm run dev
339352
```
340353

341354
確認事項:
355+
342356
- サインインページが表示されること
343357
- Cognito Managed Login でログインできること
344358
- Todo の CRUD(作成・完了・編集・削除)が動作すること
@@ -385,6 +399,12 @@ pnpm --filter @repo/db run migrate
385399

386400
Phase 1-3 で記録した各テーブルの行数に基づいて移行方法を選択する。
387401

402+
`pg_dump --data-only` で取得したダンプを DSQL に投入する場合、以下の変換が必要:
403+
404+
- `pg_dump` のプリアンブル(`SET``SELECT pg_catalog.*``\restrict``\unrestrict`)を除去
405+
- `_prisma_migrations` テーブルのデータを除去(Drizzle は `_migrations` テーブルを使用)
406+
- **`String[]` 型カラム**: `pg_dump` は PostgreSQL 配列リテラル `{}` 形式で出力する。Drizzle では `text` 型に JSON 文字列 `[]` として格納するため、`{}``[]` に変換が必要。変換しないと `JSON.parse('{}')` がオブジェクト `{}` を返し、配列として扱う箇所でエラーになる
407+
388408
v3 のマイグレーションランナーは `.ts` ファイルをサポートしている。`packages/db/migrations/` にデータ移行用の `.ts` ファイルを作成し、以下の形式で実装する:
389409

390410
```typescript
@@ -422,6 +442,7 @@ SELECT count(*) FROM "TableName";
422442
1. **CDK を更新**: Aurora v2 リソース定義を削除(RETAIN が設定されているため実リソースは残る)。webapp と async-job の環境変数を DSQL エンドポイントに変更。Lambda 関数から VPC 設定を削除。Phase 4-2 で特定した VPC 依存の独自 Construct がある場合は、ここで VPC 依存を解消する。
423443

424444
2. **デプロイ**(本番環境ではメンテナンスウィンドウを推奨):
445+
425446
```bash
426447
cd apps/cdk && pnpm exec cdk deploy --all
427448
```

0 commit comments

Comments
 (0)