Use Cases (Diperbarui: 3/6/2026)

Claude Code dan AWS DynamoDB: desain tabel, write aman, TTL, dan biaya

Panduan praktis Claude Code dengan DynamoDB: key, tes lokal, TTL, IAM, biaya, dan hot partition.

Claude Code dan AWS DynamoDB: desain tabel, write aman, TTL, dan biaya

Meminta Claude Code “tambahkan DynamoDB” terlalu longgar untuk aplikasi produksi. DynamoDB memang terasa fleksibel karena setiap item bisa punya atribut berbeda, tetapi desain sebenarnya ada pada access pattern: halaman apa membaca data apa, key apa yang dipakai untuk query, write mana yang tidak boleh menimpa data, dan apakah traffic tersebar merata di partition key.

Panduan ini memakai Claude Code sebagai reviewer desain DynamoDB, bukan sekadar pembuat kode. Kita akan membahas desain tabel, partition key, desain sederhana versus single-table design, conditional writes, TTL, local testing, IAM, biaya, dan kesalahan hot partition. Untuk konteks AWS lain, baca juga panduan AWS Lambda, panduan AWS IAM, dan panduan CloudWatch.

Selalu cocokkan detail dengan dokumentasi resmi AWS: data modeling foundations, partition key best practices, Query key condition expressions, condition expressions, TTL, DynamoDB local, throughput capacity, dan IAM fine-grained access control.

Mulai dari access pattern

Access pattern adalah operasi nyata aplikasi: menampilkan task per project, menyelesaikan satu task, membuat session kedaluwarsa setelah tujuh hari, atau memproses webhook hanya sekali. Tanpa daftar ini, Claude Code sering membuat CRUD generik lalu menambahkan Scan atau index sebagai tambalan.

Sebelum meminta kode, gunakan prompt ini:

Review desain DynamoDB ini sebelum menulis kode.
Kebutuhan:
- List task berdasarkan project
- Update satu task berdasarkan taskId
- Session user kedaluwarsa setelah 7 hari
- Setiap webhook eventId diproses hanya sekali

Kembalikan:
1. Tabel access pattern
2. Usulan PK/SK
3. Operasi yang bisa memakai Query dan yang tidak
4. Conditional writes yang diperlukan
5. Risiko hot partition dan biaya

Di DynamoDB, Query membutuhkan equality pada partition key. Sort key bisa mempersempit hasil dengan kondisi seperti begins_with. FilterExpression bukan pengganti WHERE SQL yang menghemat read; ia memfilter setelah kapasitas terpakai. Jika Claude Code menyarankan Scan untuk halaman list, perlakukan itu sebagai masalah desain.

Desain sederhana atau single-table

Single-table design menyimpan beberapa tipe entity dalam satu tabel, biasanya dengan key generik seperti PK dan SK. Ini kuat ketika satu layar membutuhkan data terkait dalam satu Query, tetapi membuat IAM, Streams, backup, dan evolusi nama key lebih menantang.

Desain sederhana memisahkan entity atau membatasi satu tabel pada sedikit access pattern. Untuk MVP, alat internal, dan tim yang baru memakai Claude Code, pendekatan ini lebih mudah direview.

KriteriaDesain sederhanaSingle-table design
Awal proyekMudah dijelaskanButuh review lebih ketat
Layar multi-entityBisa perlu beberapa readSering cukup satu Query
IAMPemisahan per tabel mudahLeadingKeys penting
PerubahanTambah tabel lebih mudahNama key harus konsisten
Cocok untukMVP dan pembelajaranDomain stabil

Contoh ini memakai satu tabel, tetapi scope-nya kecil: project, task, session, dan deduplikasi webhook.

ClaudeCodeLabDemo

PK                 SK                   entityType
PROJECT#alpha      META                 Project
PROJECT#alpha      TASK#task-001        Task
USER#u-001         SESSION#s-001        Session
WEBHOOK#stripe     EVENT#evt_001        WebhookEvent

Query:
- Task project: PK = PROJECT#alpha AND begins_with(SK, TASK#)
- Session user: PK = USER#u-001 AND begins_with(SK, SESSION#)
- Dedupe webhook: conditional PutItem pada PK/SK yang sama

Use case konkret

Pertama, board task per project. Dengan PK = PROJECT#projectId dan SK = TASK#taskId, list task menjadi Query, bukan Scan. Jika perlu filter status, minta Claude Code menjelaskan apakah GSI benar-benar diperlukan atau cukup query project lalu kelompokkan di aplikasi.

Kedua, session, invite link, dan token sementara. TTL memakai Unix epoch seconds yang disimpan sebagai Number. TTL berguna untuk membersihkan data sementara, tetapi bukan scheduler real-time. Item yang sudah expired bisa tetap terbaca sebelum dihapus background process, jadi aplikasi tetap perlu mengecek expiresAt.

Ketiga, idempotency untuk webhook. Provider sering mengirim ulang event. Dengan WEBHOOK#provider, EVENT#eventId, dan attribute_not_exists(PK) AND attribute_not_exists(SK), hanya proses pertama yang berhasil.

Keempat, rate limit sederhana. PK = RATE#userId dan SK = WINDOW#2026-06-03T10:00 cukup untuk API internal kecil. Pada traffic tinggi, satu user atau tenant populer bisa membuat hot partition.

Setup lokal

Jalankan DynamoDB Local:

services:
  dynamodb-local:
    image: "amazon/dynamodb-local:latest"
    command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data"
    ports:
      - "8000:8000"
    volumes:
      - "./docker/dynamodb:/home/dynamodblocal/data"
    working_dir: /home/dynamodblocal
docker compose up -d
export AWS_ACCESS_KEY_ID=fakeMyKeyId
export AWS_SECRET_ACCESS_KEY=fakeSecretAccessKey
export AWS_REGION=us-west-2

Buat tabel lokal:

aws dynamodb create-table \
  --table-name ClaudeCodeLabDemo \
  --attribute-definitions AttributeName=PK,AttributeType=S AttributeName=SK,AttributeType=S \
  --key-schema AttributeName=PK,KeyType=HASH AttributeName=SK,KeyType=RANGE \
  --billing-mode PAY_PER_REQUEST \
  --endpoint-url http://localhost:8000 \
  --region us-west-2

Aktifkan TTL:

aws dynamodb update-time-to-live \
  --table-name ClaudeCodeLabDemo \
  --time-to-live-specification "Enabled=true,AttributeName=expiresAt" \
  --endpoint-url http://localhost:8000 \
  --region us-west-2

Pasang dependency:

npm init -y
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb

Implementasi siap salin

Simpan sebagai app.mjs. Script ini membuat task, menampilkan task project, menyelesaikan task dengan conditional update, dan membuat session dengan TTL.

import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
  DynamoDBDocumentClient,
  PutCommand,
  QueryCommand,
  UpdateCommand,
} from "@aws-sdk/lib-dynamodb";

const TABLE_NAME = process.env.TABLE_NAME ?? "ClaudeCodeLabDemo";
const isLocal = process.env.DDB_LOCAL !== "0";

const client = new DynamoDBClient({
  region: process.env.AWS_REGION ?? "us-west-2",
  ...(isLocal
    ? {
        endpoint: "http://localhost:8000",
        credentials: {
          accessKeyId: "fakeMyKeyId",
          secretAccessKey: "fakeSecretAccessKey",
        },
      }
    : {}),
});

const ddb = DynamoDBDocumentClient.from(client, {
  marshallOptions: { removeUndefinedValues: true },
});

const nowIso = () => new Date().toISOString();
const ttlAfterDays = (days) => Math.floor(Date.now() / 1000) + days * 86400;
const taskKey = (projectId, taskId) => ({
  PK: `PROJECT#${projectId}`,
  SK: `TASK#${taskId}`,
});

async function createTask({ projectId, taskId, title, ownerId }) {
  const item = {
    ...taskKey(projectId, taskId),
    entityType: "Task",
    title,
    ownerId,
    status: "OPEN",
    createdAt: nowIso(),
    updatedAt: nowIso(),
  };

  await ddb.send(
    new PutCommand({
      TableName: TABLE_NAME,
      Item: item,
      ConditionExpression: "attribute_not_exists(PK) AND attribute_not_exists(SK)",
    }),
  );

  return item;
}

async function listProjectTasks(projectId) {
  const result = await ddb.send(
    new QueryCommand({
      TableName: TABLE_NAME,
      KeyConditionExpression: "PK = :pk AND begins_with(SK, :taskPrefix)",
      ExpressionAttributeValues: {
        ":pk": `PROJECT#${projectId}`,
        ":taskPrefix": "TASK#",
      },
      ReturnConsumedCapacity: "TOTAL",
    }),
  );

  console.log("consumed capacity:", result.ConsumedCapacity);
  return result.Items ?? [];
}

async function completeTask({ projectId, taskId, expectedOwnerId }) {
  const result = await ddb.send(
    new UpdateCommand({
      TableName: TABLE_NAME,
      Key: taskKey(projectId, taskId),
      UpdateExpression: "SET #status = :done, updatedAt = :now",
      ConditionExpression: "ownerId = :ownerId AND #status <> :done",
      ExpressionAttributeNames: {
        "#status": "status",
      },
      ExpressionAttributeValues: {
        ":done": "DONE",
        ":ownerId": expectedOwnerId,
        ":now": nowIso(),
      },
      ReturnValues: "ALL_NEW",
    }),
  );

  return result.Attributes;
}

async function createSession({ userId, sessionId }) {
  await ddb.send(
    new PutCommand({
      TableName: TABLE_NAME,
      Item: {
        PK: `USER#${userId}`,
        SK: `SESSION#${sessionId}`,
        entityType: "Session",
        createdAt: nowIso(),
        expiresAt: ttlAfterDays(7),
      },
      ConditionExpression: "attribute_not_exists(PK) AND attribute_not_exists(SK)",
    }),
  );
}

async function main() {
  const projectId = "alpha";
  const taskId = `task-${Date.now()}`;

  await createTask({
    projectId,
    taskId,
    title: "Review DynamoDB key design",
    ownerId: "masa",
  });

  await createSession({
    userId: "masa",
    sessionId: `session-${Date.now()}`,
  });

  console.log(await listProjectTasks(projectId));
  console.log(
    await completeTask({
      projectId,
      taskId,
      expectedOwnerId: "masa",
    }),
  );
}

main().catch((error) => {
  if (error.name === "ConditionalCheckFailedException") {
    console.error("Condition failed:", error.message);
    process.exit(2);
  }

  console.error(error);
  process.exit(1);
});
DDB_LOCAL=1 node app.mjs

Saat meminta Claude Code melakukan refactor, berikan batasan:

Refactor app.mjs menjadi Lambda handler.
Jangan hapus ConditionExpression.
Jangan tambah Scan kecuali menjelaskan mengapa Query tidak bisa dipakai.
Pertahankan expiresAt sebagai Number Unix epoch seconds.
Pertahankan ReturnConsumedCapacity di development.

IAM, biaya, dan jebakan

Pada single table, akses ke seluruh tabel sering terlalu luas. dynamodb:LeadingKeys dapat membatasi akses berdasarkan partition key.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ProjectScopedDynamoDBAccess",
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:UpdateItem",
        "dynamodb:DeleteItem",
        "dynamodb:Query"
      ],
      "Resource": [
        "arn:aws:dynamodb:ap-northeast-1:123456789012:table/ClaudeCodeLabProd",
        "arn:aws:dynamodb:ap-northeast-1:123456789012:table/ClaudeCodeLabProd/index/*"
      ],
      "Condition": {
        "ForAllValues:StringEquals": {
          "dynamodb:LeadingKeys": [
            "PROJECT#${aws:PrincipalTag/projectId}"
          ]
        }
      }
    }
  ]
}

On-demand mudah untuk awal karena bayar per request. Provisioned bisa lebih terkontrol saat traffic stabil. Kesalahan umum: halaman list memakai Scan, FilterExpression dianggap seperti SQL WHERE, key tetap seperti GLOBAL, TTL dianggap hapus tepat waktu, GSI ditambah tanpa access pattern, conditional write dilewati, dan endpoint lokal tertinggal di production.

Audit implementasi DynamoDB ini untuk ketergantungan Scan, risiko hot partition, salah paham TTL, conditional writes yang hilang, IAM terlalu luas, dan lonjakan biaya on-demand. Tampilkan temuan dulu, lalu saran perbaikan.

Untuk prompt dan checklist yang bisa dipakai ulang, lihat produk ClaudeCodeLab. Untuk tim yang ingin merapikan AWS, IAM, CI review, dan validasi produksi, gunakan training dan konsultasi Claude Code.

Penutup

DynamoDB berhasil jika access pattern, key, kondisi gagal, TTL, IAM, dan sinyal biaya dibuat eksplisit. Claude Code mempercepat implementasi, tetapi batasan desainnya tetap perlu ditentukan dan direview manusia.

Catatan praktik (実際に試した結果): alur paling stabil adalah meminta Claude Code membuat tabel access pattern dulu, lalu meninjau PK/SK dan failure cases, kemudian menjalankan conditional writes di DynamoDB Local. Contoh webhook dengan attribute_not_exists sangat berguna sebagai aturan review produksi yang konkret.

#claude-code #aws #dynamodb #nosql #typescript #database
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.