Use Cases (Actualizado: 3/6/2026)

Claude Code y AWS DynamoDB: diseño de tablas, escrituras seguras y costes

Guía práctica de Claude Code con DynamoDB: claves, pruebas locales, TTL, IAM, costes y particiones calientes.

Claude Code y AWS DynamoDB: diseño de tablas, escrituras seguras y costes

Pedirle a Claude Code “añade DynamoDB” no basta para una aplicación real. DynamoDB parece flexible porque cada item puede tener atributos distintos, pero el verdadero diseño está en los patrones de acceso: qué lee la pantalla, con qué clave se consulta, qué escritura no debe sobrescribir datos y cómo se reparte el tráfico entre particiones.

Esta guía usa Claude Code como asistente de diseño, no solo como generador de código. Veremos claves de partición, diseño simple frente a single-table design, escrituras condicionales, TTL, pruebas locales, IAM, costes y errores de hot partition. Para completar el contexto en ClaudeCodeLab, revisa también la guía de AWS Lambda, la guía de AWS IAM y la guía de CloudWatch.

Trabaja con la documentación oficial de AWS abierta: data modeling foundations, partition key best practices, Query key condition expressions, condition expressions, TTL, DynamoDB local, throughput capacity y IAM fine-grained access control.

Empieza por los patrones de acceso

Un patrón de acceso es una operación concreta de la aplicación: listar tareas por proyecto, completar una tarea, caducar una sesión en siete días o procesar un webhook una sola vez. Si Claude Code no conoce estas operaciones, suele producir CRUD genérico y compensarlo con índices o Scan.

Antes de pedir implementación, usa un prompt como este:

Revisa este diseño DynamoDB antes de escribir código.
Requisitos:
- Listar tareas por proyecto
- Actualizar una tarea por taskId
- Caducar sesiones de usuario después de 7 días
- Procesar cada webhook eventId solo una vez

Devuelve:
1. Tabla de patrones de acceso
2. Propuesta de PK/SK
3. Operaciones que pueden usar Query y las que no
4. Escrituras condicionales necesarias
5. Riesgos de partición caliente y coste

La razón es importante: Query en DynamoDB exige una condición de igualdad sobre la partition key y permite acotar con la sort key. FilterExpression no es un WHERE de SQL que ahorre lectura; filtra después de consumir capacidad. Si Claude Code propone Scan para una lista de usuario, detén la implementación y revisa el diseño.

Diseño simple o single-table

El single-table design guarda varios tipos de entidad en una tabla con claves genéricas como PK y SK. Puede reducir lecturas cuando una pantalla necesita datos relacionados, pero complica el aprendizaje, IAM, Streams, backups y nombres de clave.

El diseño simple separa entidades por tabla o limita una tabla a pocos patrones relacionados. Para MVP, herramientas internas y equipos que empiezan con Claude Code, suele ser más fácil de revisar.

CriterioDiseño simpleSingle-table design
InicioFácil de explicarRequiere revisión fuerte
Pantallas con varias entidadesPuede requerir varias lecturasA menudo basta un Query
IAMSeparación por tablaImportan las LeadingKeys
CambiosPuedes añadir tablasLas claves deben ser consistentes
Mejor usoMVP y aprendizajeNegocio con patrones estables

El ejemplo usa una tabla, pero con alcance limitado: proyectos, tareas, sesiones y deduplicación de webhooks.

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

Consultas:
- Tareas del proyecto: PK = PROJECT#alpha AND begins_with(SK, TASK#)
- Sesiones del usuario: PK = USER#u-001 AND begins_with(SK, SESSION#)
- Dedupe de webhooks: PutItem condicional sobre la misma PK/SK

Casos prácticos

Primer caso: un tablero de tareas por proyecto. Con PK = PROJECT#projectId y SK = TASK#taskId, la pantalla de lista se resuelve con Query. Si además necesitas filtrar por estado, pide a Claude Code que justifique si hace falta un GSI o si basta con consultar el proyecto y ordenar en la aplicación.

Segundo caso: sesiones, invitaciones y tokens temporales. TTL usa un timestamp Unix epoch en segundos, guardado como Number. Sirve para borrar datos que ya no importan, pero no es un programador exacto. Un item caducado puede seguir leyéndose hasta que DynamoDB lo borre en segundo plano, así que la aplicación debe comprobar expiresAt cuando haya seguridad de por medio.

Tercer caso: idempotencia de webhooks. La idempotencia evita repetir el efecto secundario cuando un proveedor reenvía un evento. Usa WEBHOOK#provider y EVENT#eventId como clave, y attribute_not_exists(PK) AND attribute_not_exists(SK) para que solo gane el primer procesamiento.

Cuarto caso: rate limiting. Puedes usar PK = RATE#userId y SK = WINDOW#2026-06-03T10:00. Funciona en APIs internas moderadas, pero en tráfico alto puede crear una partición caliente si un usuario, tenant o ruta concentra demasiadas escrituras.

Entorno local ejecutable

Arranca DynamoDB Local con Docker Compose:

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

Crea la tabla local. Revisa el --endpoint-url antes de ejecutar.

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

Activa el atributo 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

Instala dependencias:

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

Implementación para copiar y ejecutar

Guarda esto como app.mjs. Crea una tarea, lista tareas del proyecto, completa una tarea con condición y crea una sesión con 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

Al pedir a Claude Code que lo convierta en Lambda, protege las reglas importantes:

Refactoriza app.mjs como Lambda handler.
No elimines ConditionExpression.
No añadas Scan salvo que expliques por qué Query no sirve.
Mantén expiresAt como Number en Unix epoch seconds.
Mantén ReturnConsumedCapacity en desarrollo.

IAM, costes y riesgos

En una tabla compartida, permitir toda la tabla suele ser demasiado amplio. Con dynamodb:LeadingKeys puedes limitar el acceso por partition key. Sustituye cuenta, región, tabla y modelo de tags según tu entorno.

{
  "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 es cómodo para empezar porque pagas por petición. Provisioned puede convenir si el tráfico es estable y quieres previsibilidad. Los errores más comunes son usar Scan para listas, confiar en FilterExpression, concentrar tráfico en claves como GLOBAL, creer que TTL borra al segundo exacto, añadir GSI sin patrón de acceso y omitir escrituras condicionales en pedidos o webhooks.

Audita esta implementación DynamoDB por dependencia de Scan, particiones calientes, malentendidos de TTL, escrituras condicionales faltantes, permisos IAM excesivos y picos de coste on-demand. Devuelve hallazgos primero y luego correcciones.

Para convertir estos prompts y checklists en material reutilizable, visita productos de ClaudeCodeLab. Para equipos que quieren revisar AWS, IAM, CI y validación de producción, la ruta natural es formación y consultoría de Claude Code.

Cierre

DynamoDB funciona bien cuando el diseño es explícito: patrones de acceso, claves, condiciones de fallo, TTL, IAM y señales de coste. Claude Code acelera la implementación, pero no debe inventar esas restricciones sin revisión.

Nota práctica (実際に試した結果): el flujo más fiable fue pedir primero una tabla de patrones de acceso, revisar después PK/SK y casos de fallo, y solo entonces ejecutar escrituras condicionales contra DynamoDB Local. El ejemplo de webhook con attribute_not_exists convierte una idea abstracta en una comprobación de seguridad concreta.

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

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.