dynamodb-toolbox-patterns について
AWS SDK v3 DocumentClient と DynamoDB-Toolbox v2 を使う開発者向けの TypeScript パターンをまとめたスキルです。型安全なスキーマ、コマンド構築、シングルテーブルのアクセス設計を扱います。
主な機能
- 01型安全なスキーマモデリング
- 02`.build()` による CRUD コマンド
- 03クエリとスキャンのアクセスパス
- 04バッチ処理とトランザクションのパターン
- 05シングルテーブル設計の指針
活用シーン
- 01DynamoDB のエンティティとスキーマ定義
- 02型付き CRUD 操作の実装
- 03キーと GSI アクセスパスの設計
- 04バッチ書き込みとトランザクションの選択
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill dynamodb-toolbox-patternsインストール後、Claude Code で/dynamodb-toolbox-patternsを入力して読み込まれたか確認します。
同梱ファイルreferences/api-dynamodb-toolbox-v2.md
詳細
DynamoDB-Toolbox v2 のパターン(TypeScript)
概要
このスキルでは、AWS SDK v3 DocumentClient で DynamoDB-Toolbox v2 を使用するための、実践的な TypeScript のパターンを紹介します。型安全なスキーマモデリング、.build() コマンドの使用、本番環境に対応したシングルテーブル設計に焦点を当てています。
使用する場面
- 厳密な TypeScript の型推論を利用して DynamoDB のテーブルとエンティティを定義する場合
item、string、number、list、set、map、recordを使ってスキーマをモデリングする場合.build()を使用してGetItem、PutItem、UpdateItem、DeleteItemを実装する場合- プライマリキーと GSI を使ってクエリおよびスキャンのアクセスパスを構築する場合
- バッチ処理およびトランザクション処理を扱う場合
- 計算キーとエンティティパターンを使ってシングルテーブルシステムを設計する場合
手順
- アクセスパターンから始める:まず読み取り/書き込みクエリを特定し、その後でキーを設計します。
- テーブルとエンティティの境界を作る:シングルテーブル設計を使用する場合は、1 つのテーブルに複数のエンティティを作成します。
- 制約付きでスキーマを定義する:
.key()、.required()、.default()、.transform()、.link()を適用します。 - すべての箇所で
.build()コマンドを使用する:一貫性と型安全性を保つため、場当たり的なコマンドの構築は避けます。 - クエリ/インデックスのカバレッジを確保する:必要な各アクセスパターンについて、GSI/LSI のパスを検証します。
- バッチ処理/トランザクションを適切に使う:スループットにはバッチ処理、アトミック性にはトランザクションを使用します。
- アイテムを進化可能に保つ:スキーマの進化に備えて、オプションフィールド、デフォルト値、派生属性を使用します。
例
インストールとセットアップ
npm install dynamodb-toolbox @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import { Table } from 'dynamodb-toolbox/table';
import { Entity } from 'dynamodb-toolbox/entity';
import { item, string, number, list, map } from 'dynamodb-toolbox/schema';
const client = new DynamoDBClient({ region: process.env.AWS_REGION ?? 'eu-west-1' });
const documentClient = DynamoDBDocumentClient.from(client);
export const AppTable = new Table({
name: 'app-single-table',
partitionKey: { name: 'PK', type: 'string' },
sortKey: { name: 'SK', type: 'string' },
indexes: {
byType: { type: 'global', partitionKey: { name: 'GSI1PK', type: 'string' }, sortKey: { name: 'GSI1SK', type: 'string' } }
},
documentClient
});
修飾子と複雑な属性を含むエンティティスキーマ
const now = () => new Date().toISOString();
export const UserEntity = new Entity({
name: 'User',
table: AppTable,
schema: item({
tenantId: string().required('always'),
userId: string().required('always'),
email: string().required('always').transform(input => input.toLowerCase()),
role: string().enum('admin', 'member').default('member'),
loginCount: number().default(0),
tags: list(string()).default([]),
profile: map({
displayName: string().optional(),
timezone: string().default('UTC')
}).default({ timezone: 'UTC' })
}),
computeKey: ({ tenantId, userId }) => ({
PK: `TENANT#${tenantId}`,
SK: `USER#${userId}`,
GSI1PK: `TENANT#${tenantId}#TYPE#USER`,
GSI1SK: `EMAIL#${userId}`
})
});
.build() による CRUD コマンド
import { PutItemCommand } from 'dynamodb-toolbox/entity/actions/put';
import { GetItemCommand } from 'dynamodb-toolbox/entity/actions/get';
import { UpdateItemCommand, $add } from 'dynamodb-toolbox/entity/actions/update';
import { DeleteItemCommand } from 'dynamodb-toolbox/entity/actions/delete';
await UserEntity.build(PutItemCommand)
.item({ tenantId: 't1', userId: 'u1', email: '[email protected]' })
.send();
const { Item } = await UserEntity.build(GetItemCommand)
.key({ tenantId: 't1', userId: 'u1' })
.send();
await UserEntity.build(UpdateItemCommand)
.item({ tenantId: 't1', userId: 'u1', loginCount: $add(1) })
.send();
await UserEntity.build(DeleteItemCommand)
.key({ tenantId: 't1', userId: 'u1' })
.send();
クエリとスキャンのパターン
import { QueryCommand } from 'dynamodb-toolbox/table/actions/query';
import { ScanCommand } from 'dynamodb-toolbox/table/actions/scan';
const byTenant = await AppTable.build(QueryCommand)
.query({
partition: `TENANT#t1`,
range: { beginsWith: 'USER#' }
})
.send();
const byTypeIndex = await AppTable.build(QueryCommand)
.query({
index: 'byType',
partition: 'TENANT#t1#TYPE#USER'
})
.options({ limit: 25 })
.send();
const scanned = await AppTable.build(ScanCommand)
.options({ limit: 100 })
.send();
バッチ処理とトランザクションのワークフロー
import { BatchWriteCommand } from 'dynamodb-toolbox/table/actions/batchWrite';
import { TransactWriteCommand } from 'dynamodb-toolbox/table/actions/transactWrite';
await AppTable.build(BatchWriteCommand)
.requests(
UserEntity.build(PutItemCommand).item({ tenantId: 't1', userId: 'u2', email: '[email protected]' }),
UserEntity.build(PutItemCommand).item({ tenantId: 't1', userId: 'u3', email: '[email protected]' })
)
.send();
await AppTable.build(TransactWriteCommand)
.requests(
UserEntity.build(PutItemCommand).item({ tenantId: 't1', userId: 'u4', email: '[email protected]' }),
UserEntity.build(UpdateItemCommand).item({ tenantId: 't1', userId: 'u1', loginCount: $add(1) })
)
.send();
シングルテーブル設計の指針
- 各ビジネス概念を、厳密なスキーマを持つエンティティとしてモデリングします。
- PK/SK は予測可能で組み合わせ可能な形式にします(
TENANT#、USER#、ORDER#)。 - アクセスパスはメモリ内のフィルターではなく、GSI キーにエンコードします。
- 監査/履歴データには、追記専用のタイムラインを優先します。
- 必要に応じてスコープを限定したパーティションやシャーディングを使用し、ホットパーティションを抑制します。
ベストプラクティス
- まずアクセスパターンからキーを設計し、その後でエンティティ属性を導き出します。
- キー構成のずれを防ぐため、キーの構成には単一の信頼できる情報源(
computeKey)を使用します。 - 厳密な読み取り後書き込みが必要な場合に限り、
.options({ consistent: true })を使用します。 - 実行時のリクエストパスでは、スキャンより対象を絞ったクエリを優先します。
- 冪等性と楽観的同時実行制御のために、条件式を追加します。
- 部分的な失敗を防ぐため、実行前にバッチ/トランザクションのサイズ制限を検証します。
制約と警告
- DynamoDB-Toolbox v2 は AWS SDK v3 DocumentClient との統合を前提としています。
- 明示的に範囲を限定しない限り、リクエストパスでのテーブルスキャンは避けてください。
- 同時実行に影響される更新には、条件付き書き込みを使用します。
- トランザクションには制限があり、単一アイテムの書き込みより遅いため、真にアトミック性が必要な場合にのみ使用します。
- 実装前に、想定するスループットに照らしてキー設計を検証します。
参考資料
Context7 から厳選された主な参考資料は、次のファイルにあります。
references/api-dynamodb-toolbox-v2.md
コメント
Markdown 対応