Skip to content

feat(cli): generate per-table stacks for multiple DDB tables#14688

Open
9pace wants to merge 9 commits intogen2-migrationfrom
multi-ddb-per-table-stacks
Open

feat(cli): generate per-table stacks for multiple DDB tables#14688
9pace wants to merge 9 commits intogen2-migrationfrom
multi-ddb-per-table-stacks

Conversation

@9pace
Copy link

@9pace 9pace commented Mar 17, 2026

Summary

  • Generate now creates a separate nested stack per DynamoDB table via backend.createStack('storage<resourceName>'), instead of placing all DDB tables in a single shared storage stack. This matches Gen1's per-resource nested stack naming and enables independent refactor/rollback of each table.
  • Refactor (forward + rollback) threads the resourceName through StorageDynamoForwardRefactorer and StorageDynamoRollbackRefactorer, using 'storage' + resourceName for both Gen1 and Gen2 nested stack lookups via findNestedStack prefix matching.
  • S3 refactorer fix: The S3 forward/rollback refactorers previously used findNestedStack(facade, 'storage') which ambiguously matched DDB per-table stacks (e.g. storageactivity) before the actual S3 stack (storage0EC3F24A). Fixed by using 'storage' + resourceName for Gen1 lookup and a new findS3NestedStack method that distinguishes the CDK-hashed S3 stack from lowercase-prefixed DDB stacks.
  • Validation replaces validateSingleResourcePerCategory with validateSingleResourcePerStack, which maps each resource (DDB and S3) to its own stack name and rejects only when multiple resources target the same stack.
  • Bug fix: resolveOutputs() no longer crashes when a Gen2 storage stack has no Outputs section (CDK omits Outputs when there are no cross-stack references to a table). CFNTemplate.Outputs is now optional.

Changes

Source (packages/amplify-cli/src/commands/gen2-migration/)

  • generate/amplify/backend.generator.ts — new createDynamoDBStack(resourceName) method
  • generate/amplify/storage/dynamodb.generator.ts — calls createDynamoDBStack instead of ensureStorageStack
  • generate/amplify/storage/dynamodb.renderer.ts — accepts scopeVarName parameter
  • refactor/refactor.tsvalidateSingleResourcePerStack, threads resourceName into DDB and S3 refactorers
  • refactor/storage/storage-forward.ts — accepts resourceName, uses findS3NestedStack for Gen2 lookup
  • refactor/storage/storage-rollback.ts — accepts resourceName, uses findS3NestedStack for Gen2 lookup
  • refactor/storage/storage-dynamo-forward.ts — per-table stack lookup
  • refactor/storage/storage-dynamo-rollback.ts — per-table stack lookup
  • refactor/resolvers/cfn-output-resolver.ts — handles missing Outputs section
  • cfn-template.tsOutputs made optional

Tests

  • Updated S3 refactorer tests to use realistic stack names (storageavatars, storage0EC3F24A) and pass resourceName
  • Updated validation test to expect 'storageavatars' in error message
  • New tests for createDynamoDBStack, custom scope variable, multi-DDB generate, no-Outputs templates

Snapshots (amplify-migration-apps/discussions/)

  • Fresh snapshots from clean main/gen2-main deployment with auth, API, 2 DDB tables (activity, bookmarks), S3 (avatars), 2 lambdas
  • Per-table stacks: storageactivityA346F297, storagebookmarks210DAF62, storage0EC3F24A (S3)

E2E Verification

Tested end-to-end on discussions app (d2l28asetq9wsc, us-east-1):

  1. Deploy Gen1 — auth, API (AppSync), 2 DDB tables (activity, bookmarks), S3 (avatars), 2 lambdas
  2. Generate — produced createStack('storageactivity') + createStack('storagebookmarks') + S3 storage
  3. Deploy Gen2 — separate storageactivityA346F297, storagebookmarks210DAF62, storage0EC3F24A nested stacks
  4. Forward refactor — auth (5 resources), activity DDB, bookmarks DDB, and avatars S3 all moved from Gen1 to Gen2
  5. Rollback — all resources moved back to Gen1
  6. Forward again — full round-trip verified
  7. All 389 unit tests pass (48 suites, 67 snapshots)

Test plan

  • Unit tests pass (389/389 in 48 suites)
  • Snapshot tests pass (67 snapshots)
  • E2E: generate produces per-table stacks for multi-DDB app
  • E2E: deploy creates separate nested stacks (including S3)
  • E2E: forward refactor moves DDB tables and S3 bucket independently
  • E2E: rollback restores original Gen1 state
  • E2E: forward again succeeds (full round-trip)
  • S3 refactorer correctly distinguishes S3 stack from DDB per-table stacks

Closes #14608, #14597

9pace added 2 commits January 15, 2026 14:05
Previously, all DynamoDB tables were placed in a single shared storage
stack, causing conflicts when multiple tables existed. Each DDB table
now gets its own nested stack via backend.createStack('storage<name>'),
matching the Gen1 nested stack naming convention.

Also fixes resolveOutputs() crash when a Gen2 storage stack has no
Outputs section (happens when no cross-stack references exist for a
table).

Closes #14608, #14597
@9pace 9pace requested a review from a team as a code owner March 17, 2026 22:29
9pace added 3 commits March 18, 2026 00:10
…ture data

Recapture discussions app snapshots from live deployments:
- Gen1: amplify-discussionsblade-blade-4edfd (activity + bookmarks tables)
- Gen2: amplify-d1skq8aomhb772-e2etest-branch (per-table stacks)

Pre-generate inputs use real amplify-pull data for bookmarks resource.
Pre-refactor templates fetched from deployed CloudFormation stacks.
Post-generate and post-refactor regenerated by test framework.

Also adds bookmarks to migration-config.json and fixes snapshot.ts
to use TemplateStage: Original for unprocessed templates.
Replace raw app ID d1skq8aomhb772 with sanitized name 'discussions'
in all snapshot file contents and filenames.
categoryCounts.set(r.category, (categoryCounts.get(r.category) ?? 0) + 1);
let stackName: string;
switch (r.key) {
case 'storage:DynamoDB':
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this never happens

9pace and others added 4 commits March 20, 2026 14:46
…er-table-stacks

# Conflicts:
#	amplify-migration-apps/discussions/README.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Re-deployed discussions app with 3 storage types (activity DDB,
bookmarks DDB, avatars S3) and captured fresh snapshots for all 4
stages. Per-table stacks refactor verified working for both DDB tables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The S3 forward/rollback refactorers used findNestedStack with a bare
'storage' prefix, which matched DDB per-table stacks (e.g.
'storageactivity') before the S3 stack ('storage0EC3F24A'). Fixed by
using 'storage' + resourceName for Gen1 lookup and a new
findS3NestedStack method that distinguishes the CDK-hashed S3 stack
from lowercase-prefixed DDB stacks for Gen2 lookup.

Also refreshes discussions snapshots from a clean main/gen2-main deploy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant