惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

I
Intezer
人人都是产品经理
人人都是产品经理
博客园_首页
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
I
InfoQ
美团技术团队
罗磊的独立博客
F
Full Disclosure
Hugging Face - Blog
Hugging Face - Blog
T
The Blog of Author Tim Ferriss
Security Latest
Security Latest
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Last Week in AI
Last Week in AI
Vercel News
Vercel News
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
M
MIT News - Artificial intelligence
H
Hacker News: Front Page
IT之家
IT之家
S
Security Affairs
N
News and Events Feed by Topic
W
WeLiveSecurity
H
Help Net Security
C
Cyber Attacks, Cyber Crime and Cyber Security
雷峰网
雷峰网
B
Blog RSS Feed
Hacker News: Ask HN
Hacker News: Ask HN
The Cloudflare Blog
小众软件
小众软件
The GitHub Blog
The GitHub Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
H
Hackread – Cybersecurity News, Data Breaches, AI and More
A
Arctic Wolf
Google DeepMind News
Google DeepMind News
C
CERT Recently Published Vulnerability Notes
The Register - Security
The Register - Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Cloudbric
Cloudbric
Cyberwarzone
Cyberwarzone
爱范儿
爱范儿
阮一峰的网络日志
阮一峰的网络日志
Martin Fowler
Martin Fowler
月光博客
月光博客
MongoDB | Blog
MongoDB | Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Threat Research - Cisco Blogs
L
Lohrmann on Cybersecurity
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 热门话题

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Understanding AWS Blocks as a CDK Developer
Kenta Goto · 2026-06-17 · via DEV Community

Understanding AWS Blocks as a CDK Developer

I tried out AWS Blocks, announced as a preview on June 17, 2026, from the perspective of someone who normally writes AWS CDK.

What Is AWS Blocks?

AWS Blocks is a toolkit for quickly building the backend of a full-stack app. It was announced as a preview on June 17, 2026, and its source code is published as open source at aws-devtools-labs/aws-blocks.

At its center is a component called a Building Block. It is a unit of functionality such as a data store or authentication, and a single Building Block plays the following three roles at the same time.

  • A CDK Construct (the infrastructure definition at deploy time)
  • A runtime implementation (AWS SDK calls running on Lambda)
  • A local mock (runs with npm run dev, no AWS environment needed)

In other words, the same Building Block description acts as a mock during local development, as a CDK Construct at deploy time, and as AWS SDK calls in production. As a result, a single piece of code can run locally on your machine without an AWS account, and can be deployed to AWS as is.

The Building Blocks provided include KVStore and DistributedTable (data), AuthBasic (authentication), Realtime (real-time communication), FileBucket (files), and Database (SQL).

Developers write both the infrastructure (such as data) and the API logic that uses it together in aws-blocks/index.ts (which can also be split into multiple files).

// aws-blocks/index.ts
import { Scope, ApiNamespace, KVStore } from '@aws-blocks/blocks';

const scope = new Scope('my-app');
const cache = new KVStore(scope, 'cache');   // ← at deploy time, this becomes a DynamoDB table (infrastructure)

export const api = new ApiNamespace(scope, 'api', () => ({
  getValue: (key: string) => cache.get(key), // ← API logic that uses that table
}));

The infrastructure definition new KVStore(...) and the API logic that calls it sit side by side in the same file. If CDK is about "declaring infrastructure as code (Infrastructure as Code)," then AWS Blocks is framed as "deriving infrastructure from application code (Infrastructure from Code, IfC)."

The frontend simply calls this api directly, like import { api } from 'aws-blocks', so that the frontend and backend share types without any code-generation step in between.

For type integration, type-safe clients are provided not only for web frameworks such as Next, Nuxt, React, and Vue, but also for native mobile targets such as Swift, Kotlin, and Dart/Flutter. In TypeScript you can load them directly via import, whereas for native targets the client code is generated at build time from a spec called blocks.spec.json, and the backend is called over JSON-RPC.

The Same import Resolves to Different Files Depending on Context

This is probably the most distinctive feature of AWS Blocks.

Both the import { api } from 'aws-blocks' you write on the frontend and the new DistributedTable(...) you call inside the backend's aws-blocks/index.ts look like ordinary TypeScript. Yet this 'aws-blocks' (and each Building Block) resolves to a completely different file depending on the context in which it runs.

This "resolves to something different depending on context" behavior is stated explicitly in the official README. It explains that the same new KVStore(scope, 'todos') becomes a local store during development, an Amazon DynamoDB table at deploy time, and an SDK call in production (without changing a single line of code). Below, I'll trace how this works from the implementation.

Context Resolves to Role
Local development (npm run dev) Mock implementation Stores to disk (.bb-data/). No AWS environment needed
CDK synth Infrastructure (CDK Construct) Defines DynamoDB tables, Lambdas, etc.
Lambda runtime Application implementation Calls the AWS SDK

Mechanism: conditional exports + a global variable

The mechanism consists of two elements.

The first is Node.js conditional exports (the exports field in package.json). Looking at the package.json of each Building Block, exports is defined as follows.

// package.json of @aws-blocks/bb-distributed-table (exports excerpt)
{
  "exports": {
    ".": {
      "browser": "./dist/index.browser.js",
      "cdk": {
        "types": "./dist/index.cdk.d.ts",
        "default": "./dist/index.cdk.js"
      },
      "aws-runtime": "./dist/index.aws.js",
      "types": "./dist/index.mock.d.ts",
      "default": "./dist/index.mock.js"
    }
  }
}

exports is a mechanism that assigns a different file per condition; when an import is resolved, the file matching an active condition is loaded. browser / types / default are standard Node.js conditions, whereas cdk and aws-runtime are conditions that AWS Blocks defines on its own, and they are not active unless explicitly specified at startup.

And the reason the resolution target changes per context is that each command passes "which condition to activate" at startup.

cdk synth runs with NODE_OPTIONS=--conditions=cdk, so it resolves to cdk (= Construct); the Lambda bundle passes --conditions: aws-runtime to esbuild, so it resolves to aws-runtime (= runtime). npm run dev specifies no condition, so it resolves to default (= mock), and for type checking TypeScript always looks at types.

The second is the hand-off at synth time. Before dynamically importing the aws-blocks/index.ts that the developer implemented, the CDK layer holds a reference to the stack instance currently being synthesized in a global variable. The Scope and each Building Block created inside index.ts are not given a parent stack explicitly, so they refer to this global variable to resolve which stack they belong to.

NOTE: packages/core/src/cdk/blocks-backend.ts

// packages/core/src/cdk/blocks-backend.ts (excerpt; the infrastructure-building parts are omitted)
private constructor(scope: Construct, id: string, props: BlocksBackendProps) {
  super(scope, id);
  // Expose self to Building Blocks at CDK time
  (globalThis as any).CURRENT_BLOCKS_STACK = this;   // ← expose the current stack globally
  // ...
}

static async create(scope: Construct, id: string, props: BlocksBackendProps) {
  assertCdkConditionActive();
  const backend = new BlocksBackend(scope, id, props);
  const mod = await import(`${props.backendCDKPath}?stack=${id}`);   // ← load index.ts
  // ...
  return backend;
}

In short, when new DistributedTable(...) runs inside the index.ts the developer implemented, that Construct gets the current stack from this global variable, creates a DynamoDB table, and grants the stack's Lambda read/write permissions on the table. This is how writing application code itself becomes the infrastructure definition.

Setting Up a Project

An AWS Blocks app starts from npm's create command (Node.js 22 or later / npm 10 or later is required).

npm create @aws-blocks/blocks-app@latest my-app
cd my-app
npm install
npm run dev

npm run dev starts a local server at http://localhost:3000, and all Building Blocks run as mock implementations (no AWS account or credentials needed). You can also choose a template, like --template react.

The generated directory looks roughly like this (the frontend contents vary by template, but the structure of aws-blocks/ is the same).

my-app/
├── aws-blocks/            # the entire backend
│   ├── index.ts           #   backend definition (API, data, auth) ← what developers mainly write
│   ├── index.cdk.ts       #   CDK entry (assembles the stack with BlocksStack.create)
│   ├── index.handler.ts   #   entry for the Lambda handler at deploy time
│   ├── client.js          #   typed client for the frontend (auto-generated)
│   └── scripts/           #   helper scripts such as the dev server
├── src/                   # frontend (React / Next, etc., depending on the template)
├── cdk.json
└── package.json

The aws-blocks/index.ts we've seen so far is the body of the backend definition, and aws-blocks/index.cdk.ts is the entry point as CDK.

Basically, what developers implement is aws-blocks/index.ts, and there's no need to touch aws-blocks/index.cdk.ts. However, as described later, you can also customize the CDK configuration by touching this file.

Implementing a Sample

Here's what it looks like in practice. Using AuthBasic, DistributedTable, and ApiNamespace, I've implemented a simple Todo app.

// aws-blocks/index.ts
import { ApiNamespace, Scope, AuthBasic, DistributedTable } from '@aws-blocks/blocks';
import { z } from 'zod';

const scope = new Scope('todo-app');

const auth = new AuthBasic(scope, 'auth', { passwordPolicy: { minLength: 8 } });
export const authApi = auth.createApi();

// The Zod schema serves as runtime validation + the TypeScript type + the DynamoDB table shape, all at once
const todoSchema = z.object({
  userId: z.string(),    // partition key (isolated per user)
  todoId: z.string(),    // sort key
  title: z.string(),
  completed: z.boolean(),
});

const todos = new DistributedTable(scope, 'todos', {
  schema: todoSchema,
  key: { partitionKey: 'userId', sortKey: 'todoId' },
});

export const api = new ApiNamespace(scope, 'api', (context) => ({
  async createTodo(title: string) {
    const user = await auth.requireAuth(context);   // ← this makes the method require authentication
    const todo = {
      userId: user.username,
      todoId: Date.now().toString(36),
      title,
      completed: false,
    };
    await todos.put(todo);
    return todo;
  },

  async listTodos() {
    const user = await auth.requireAuth(context);
    return Array.fromAsync(
      todos.query({ where: { userId: { equals: user.username } } })
    );
  },
}));

This sample implementation shows the following characteristics as well.

  • Authentication is specified explicitly per method: each method is a public RPC endpoint with no authentication by default. Call auth.requireAuth(context) at the beginning of any method you want to require authentication.
  • Reusing the Zod schema: a single Zod schema can cover runtime validation, the TS type, and the DynamoDB table definition.

Also, the frontend can call the API definition implemented here in a typed way, like import { api } from 'aws-blocks'.

Deploying

Once it works locally, you can deploy the same code to AWS as is.

npm run sandbox   # to a per-developer temporary environment (backend on AWS, frontend served locally)
npm run deploy    # full production deploy (including frontend hosting)

npm run sandbox is a temporary stack for quickly checking behavior, and npm run deploy is a production deploy that includes frontend hosting. Both run CDK internally (synth → deploy with --conditions=cdk, plus generating client code with --conditions=aws-runtime), and the first time, just like ordinary CDK, you need AWS credentials and cdk bootstrap.

Whereas npm run dev used local mocks to realize the app's behavior, here the app is deployed to a real environment without changing a single line of code. The earlier mechanism where "the same import resolves to different files depending on context" comes in handy here.

AWS Blocks from a CDK Perspective

A distinctive feature of AWS Blocks is that you can implement with it even without being familiar with AWS or CDK, but as a CDK user you may sometimes want to extend the CDK definition. AWS Blocks supports those use cases too.

You Can Extend the CDK Definition

aws-blocks/index.cdk.ts is the CDK layer, where BlocksStack is invoked. Here you can also define additional resources you want to create and pass them to blocksStack.handler (the Lambda's NodejsFunction).

// aws-blocks/index.cdk.ts
import * as sqs from 'aws-cdk-lib/aws-sqs';

// ...
// ...

export const blocksStack = await BlocksStack.create(app, 'my-app', {
  backendHandlerPath: join(__dirname, 'index.handler.ts'),
  backendCDKPath: join(__dirname, 'index.ts'),
});

// Create raw CDK resources in the same stack as Blocks, and pass permissions and environment variables to the Lambda
const jobsQueue = new sqs.Queue(blocksStack, 'JobsQueue');

jobsQueue.grantSendMessages(blocksStack.handler);
blocksStack.handler.addEnvironment('JOBS_QUEUE_URL', jobsQueue.queueUrl);

blocksStack.handler exposes methods like addToRolePolicy (adding IAM) and addEnvironment (injecting environment variables), and you can also grant the Lambda IAM permissions on your own CDK resources using grant~ and similar methods.

Checking the Generated CloudFormation with cdk synth

Since AWS Blocks is ultimately AWS CDK under the hood, you can of course run synth too.

npx cdk synth

Among the files generated when you set up the project is cdk.json, whose app command is npx tsx -C cdk aws-blocks/index.cdk.ts. The point is this -C cdk (= --conditions=cdk), which makes each Building Block resolve to a CDK Construct.

Conversely, if you run it without --conditions=cdk, the Building Blocks would resolve as mocks, so AWS Blocks stops you with an explicit Missing --conditions=cdk error.

Error: Missing --conditions=cdk: Building Blocks will silently load mock implementations instead of CDK constructs.

Fix: Set NODE_OPTIONS="--conditions=cdk" before running CDK synth:
  NODE_OPTIONS="--conditions=cdk" npx cdk synth

Integration Patterns

The documentation presents four patterns for integrating with existing infrastructure.

  • CDK-in-Blocks: connect raw CDK resources to the Blocks Lambda and use them (for resources that have no corresponding Building Block)
  • fromExisting: wrap already-deployed AWS resources with a Building Block and use them (for resources that have a corresponding Building Block)
  • Custom Block: build your own Building Block
  • Vendorize: pull the Block's code into your own repository

Let's look at a concrete example. SQS has no corresponding Building Block, so you connect it as raw CDK with CDK-in-Blocks. For instance, if you want to "send a message from the Blocks API to an existing SQS queue created by another stack," you reference the existing queue from index.cdk.ts and pass permissions and environment variables to the Lambda.

// aws-blocks/index.cdk.ts — reference an existing queue owned by another stack and wire it to the Lambda
import * as sqs from 'aws-cdk-lib/aws-sqs';

const externalQueue = sqs.Queue.fromQueueArn(
  blocksStack, 'ExternalQueue',
  'arn:aws:sqs:ap-northeast-1:123456789012:my-queue',
);
externalQueue.grantSendMessages(blocksStack.handler);
blocksStack.handler.addEnvironment('EXTERNAL_QUEUE_URL', externalQueue.queueUrl);

On the other hand, for resources that have a corresponding Building Block (existing DynamoDB tables, S3 buckets, RDS, Cognito, and so on), wrapping them with fromExisting is a better fit. Just by passing the existing resource name to something like table: when creating the Building Block, you can use the typed API and the local mock as is, and the Building Block grants IAM permissions automatically too. You write this on the index.ts (backend definition) side.

// aws-blocks/index.ts — wrap an existing DynamoDB table with DistributedTable
const todos = new DistributedTable(scope, 'todos', {
  schema: todoSchema,
  key: { partitionKey: 'userId', sortKey: 'todoId' },
  table: DistributedTable.fromExisting('my-existing-todos-table'), // ← pass the existing table name
});

IAM Is Automatic and Least-Privilege per Block

When you instantiate a Building Block, the Lambda is automatically given permissions limited to that resource. For example, with KVStore, read/write permissions on the generated DynamoDB table (and its indexes) are granted scoped to that table's ARN (internally equivalent to CDK's grantReadWriteData).

That is, it doesn't take the broad approach of granting dynamodb:* on all tables, and it can't touch other tables or other resources. The reason least privilege is applied without you hand-writing IAM policies is that the Building Block makes good use of CDK's grant~ behind the scenes.

Constraints You Should Know

The Architecture Is a Lambda-lith (All Methods in a Single Lambda)

When I actually ran cdk synth and checked the contents, all the API methods coexisted in a single Lambda function (memory 2048 MB / timeout 900 seconds). It's a configuration where API Gateway's proxy integration routes everything to a single Lambda (a so-called Lambda-lith).

This comes with the following constraints.

  • You can't separate scaling, permission isolation, or deployment units per method
  • Bundle size and cold starts grow in proportion to the size of the API

As of now, it's not suited to use cases where you want to split functions in a microservices style. By contrast, for a prototype or a small-to-medium API, a single function works perfectly well, so this won't be a problem as long as you understand it as a design trade-off. That said, this configuration reflects how it's built at this point in time, and there seems to be room for it to change in future updates.

Side Effects of new

new DistributedTable(...) and new KVStore(...) are not just object creation; behind the scenes they are converted into CDK resource definitions. AWS Blocks is built on a philosophy that blurs the boundary between app and infrastructure even more than AWS CDK does, so those accustomed to recent, modern app development may feel some discomfort here.

For example, suppose you new a resource you access in your business logic within that logic, and then, in order to access the same resource from another endpoint, you also new that resource definition in a different file. The same resource definition then ends up being new-ed in the CDK code, and an Already Exists error occurs at the CDK layer. It's also worth keeping in the back of your mind that casually increasing instances as if they were ordinary classes unintentionally increases your infrastructure, and casually deleting them deletes the actual resources too.

To avoid this, you need to declare the resource definition somewhere in a separate file (a class or a function) and have each piece of logic load it. With careful design and proper model separation, it's entirely possible to implement this without any awkwardness, but if you don't do it well, you end up with an implementation that has merely separated app and infrastructure, which can look like it goes against the philosophy of AWS Blocks.

Conversely, if you have concerns about this area, I think you may be better off simply using AWS CDK.

When to Use It

The first cases that come to mind are when you want to write code without being conscious of AWS or CDK, or when you don't have members who are very familiar with them. I also think it fits cases where the application you're building isn't very large—such as a prototype or internal tool you just want to launch quickly, or when you want to build something full-stack easily.

As a CDK user too, I think it's a good fit, since—as mentioned earlier—you can also extend CDK in index.cdk.ts. After all, CDK's flavor seeps through in how you write the code, with things like scope and id, so knowing CDK may let you implement smoothly.