A serverless order flow on API Gateway, Lambda, DynamoDB, SNS and SQS, provisioned end to end with AWS CDK across multiple stacks. Later migrated to Aurora PostgreSQL to demonstrate the same flow on a relational backend.
An order goes from API Gateway to a stored receipt in S3. A message that fails three times moves to a dead-letter queue. Aurora sits outside the CDK app and is filled by a re-runnable migration script.
Decisions
Split the system into multiple CDK stacks instead of one.
Rejected: A single CDK stack for the whole system.
This let the data layer be replaced without touching the API or messaging stacks - it's what made the Aurora migration tractable.
Wrote the migration script idempotent and re-runnable.
Rejected: A one-shot script that needed the target cleaned before every run.
This meant a partial run could be repeated safely instead of requiring the target cleaned first.
Used contract tests comparing API responses either side of the cutover.
Rejected: Verifying only that row counts matched between DynamoDB and Postgres.
Row counts only prove the data arrived, not that every field still means what callers expect - comparing full API responses on both sides catches representation differences a count can't see.
What broke
The contract tests failed on representation, not data. Timestamps stored as ISO strings in DynamoDB came back from Postgres as epoch numbers. The rows were correct and the counts reconciled, so a data-level check would have passed - but every consumer parsing those fields would have broken. Several similar type mismatches surfaced the same way. All caught before staging.