DynamoDBのCRUDコマンドについて

最近、業務でサーバレスアプリケーションを開発する際にDynamoDBをよく使うのですが、DynamoDBのCRUDコマンドがなかなか覚えられないので使い方をまとめてみました。

※今回の記事はAWS SDK for JavaScriptを使用する方向けです。

## 導入方法
- 下記コマンドをターミナルで実行します。
- `@aws-sdk/lib-dynamodb`を導入しているのは、`AttributeValue`形式でなくネイティブなTypeScriptの型を使って値を指定したいためです。
  ```bash
  npm i @aws-sdk/client-dynamodb
  npm i @aws-sdk/lib-dynamodb
  ```

## 基本的なCRUDコマンド
### ScanCommand
- **用途**: テーブル全体をスキャンしてデータを検索する際に使用。
- **特徴**:
  - 特定のキーを指定する必要はない。
  - テーブル全体をスキャンするため、時間とリソースを多く消費する。
  - フィルタを使用して結果を絞り込むことは可能だが、スキャン自体は全アイテムに対して行われる。
  - パーティションキーの指定は不要。
  - 並列スキャンの実行が可能
  ```ts
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
  import { ScanCommand } from "@aws-sdk/lib-dynamodb";

  const ddb = new DynamoDBClient();

  const params = {
    TableName: "TableName",
    FilterExpression: "AttributeName1 = :value1 AND AttributeName2 <= :value2",
    ExpressionAttributeValues: {
        ":value1": "AttributeName1",
        ":value2": "AttributeName2"
    },
    // 取得する属性を指定
    ProjectionExpression: "Id, Name, AttributeName1, AttributeName2",
    // 強い整合性の読み取りを行うか
    ConsistentRead: true, 
    // 並列スキャンのセグメント
    Segment: 0, 
    // 並列スキャンの合計セグメント数
    TotalSegments: 1, 
    // 返却するアイテム数
    Limit: 100,
    // クエリの開始点を指定(ページネーション用)
    ExclusiveStartKey: {
        "PartitionKeyName": "StartValue",
        "SortKeyName": "10"
    },
    // 実行に消費された容量ユニットの情報を取得
    ReturnConsumedCapacity: "TOTAL", 
  };
  await ddb.send(new ScanCommand(params))
  ```

### QueryCommand
- **用途**: 特定のパーティションキーに基づいてデータを検索する際に使用。
- **特徴**:
  - ソートキーを使用して範囲や条件を指定可能。
  - `KeyConditionExpression`でのパーティションキー指定は必須。
  - 単一のパーティションに対してのみクエリを実行するため、並列スキャンは不可
  ```ts
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
  import { QueryCommand } from "@aws-sdk/lib-dynamodb";

  const ddb = new DynamoDBClient();

  const params = {
      TableName: "TableName",
      // パーティションキー(必須)とソートキーに基づいてアイテムを検索
      KeyConditionExpression: "PartitionKeyName = :partitionkeyval AND SortKeyName <= :sortkeyval",
      // KeyConditionExpressionで使用される属性値のプレースホルダーを提供
      ExpressionAttributeValues: {
          ":partitionkeyval": "ExampleValue",
          ":sortkeyval": "100"
      },
      // クエリ結果に追加フィルターを適用します(クエリ結果に対して適用される！)
      FilterExpression: "Price > :price",
      ProjectionExpression: "Id, Name, Price",
      ConsistentRead: true, // 必要に応じてfalseに設定
      Limit: 100,
      ExclusiveStartKey: {
          "PartitionKeyName": "StartValue",
          "SortKeyName": "10"
      },
      ReturnConsumedCapacity: "TOTAL", 
      // GSIを指定
      IndexName: "GSIName" 
  };
  await ddb.send(new QueryCommnand(params))
  ```


### GetCommand
- **用途**: テーブルから単一のアイテムを取得するために使用。
- **特徴**:
  - プライマリキー(PKもしくはPKとSKの組)を指定して、特定のアイテムを取得する。
  - 必要に応じて、取得する属性を`ProjectionExpression`を使用して指定できる。
  - 必ず単一のアイテムのみを取得するため、最も効率的でコストが低いデータ取得方法
  ```ts
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
  import { GetCommand } from "@aws-sdk/lib-dynamodb";

  const ddb = new DynamoDBClient();

  const params = {
    TableName: "TableName",
    Key: {
        "Key1": "PrimaryKeyName"
    }
  };
  await ddb.send(new GetCommand(params))
  ```

### PutCommand
- **用途**: 新しいアイテムをテーブルに追加する、または既存のアイテム全体を置き換えるために使用。
- **特徴**:
  - 指定されたプライマリキー(PKもしくはPKとSKの組)がテーブル内に存在しない場合、新しいアイテムとして追加される。
  - 既存のプライマリキーと一致するアイテムが存在する場合、そのアイテムは新しいデータで置き換えられる。
  - アイテムの属性とその値を明示的に指定する必要がある。
  ```ts
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
  import { PutCommand } from "@aws-sdk/lib-dynamodb";

  const ddb = new DynamoDBClient();

  const params = {
    TableName: "TableName",
    Item: {
        "Attribute1": "PrimaryKeyName",
        "Attribute2": "AttributeName1",
        "Attribute3": "AttributeName2"
    }
  };
  await ddb.send(new PutCommand(params))
  ```

### UpdateCommand
- **用途**: 既存のアイテムの一部を更新するために使用。
- **特徴**:
  - プライマリキー(PKもしくはPKとSKの組)を指定して、特定のアイテムの一部または全部の属性を更新する。
  - 指定したキーの条件に一致するアイテムが存在しない場合、更新は行われない
  - プライマリキーは更新できない
  - 条件式を使用して、特定の条件下でのみアップデートを行うことができる。
  - 属性の追加、削除、値の更新など、さまざまな更新操作が可能。
  - `UpdateExpression: "REMOVE attribute名"`で項目の削除が行える
  ```ts
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
  import { UpdateCommand } from "@aws-sdk/lib-dynamodb";

  const ddb = new DynamoDBClient();

  const params = {
    TableName: "TableName",
    Key: {
        "Key1": "PrimaryKeyName"
    },
    UpdateExpression: "set Attribute1 = :AttributeName1",
    // レコードが存在しない場合エラーがスローされる
    ConditionExpression: "attribute_exists(PrimaryKeyName)",
    ExpressionAttributeValues: {
        ":Value1": "NewValue1"
    }
  };
  await ddb.send(new UpdateCommand(params))
  ```

### DeleteCommand
- **用途**: テーブルから単一のアイテムを削除するために使用。
- **特徴**:
  - プライマリキー(PKもしくはPKとSKの組)を指定して、特定のアイテムを削除する。
  - 条件式を使用して、特定の条件下でのみ削除を行うことができる。
  ```ts
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
  import { DeleteCommand } from "@aws-sdk/lib-dynamodb";

  const ddb = new DynamoDBClient();

  const params = {
      TableName: "TableName",
      Key: {
          "Key1": "PrimaryKeyName"
      }
  };
  await ddb.send(new DeleteCommand(params))
  ```
## その他のコマンド
#### `TransactGetCommand`/`TransactWriteCommand`
- 同一トランザクション内でのデータの読み取り/書き込みを行うことができる。
#### `ExecuteTransactionCommand`
- トランザクション内で複数の読み取りおよび書き込み操作を一度に実行することができる。
#### `BatchGetCommand`/`BatchWriteCommand`
- 複数のテーブルやパーティションキーに紐づくデータを一括取得することができる。
#### `ExecuteStatementCommand`/`BatchExecuteStatementCommand`
- `Amazon DynamoDB PartiQL`というSQLと似た言語を使用してデータを取得することができる
   - RDBに慣れている人には便利な機能ですが、パーティションキーを指定しないとupdateできないなど、DynamoDBの制約を把握した上で使う必要がありそうです
  ```ts
  const statement = `
    SELECT *
    FROM "TableName"
    WHERE "id" = 'example-id';
  `;

  const params = {
    Statement: statement,
  };

  const command = new ExecuteStatementCommand(params);
  ```

## まとめ
今回は、DynamoDBのCRUDコマンドについてまとめてみました。
個人的には、`PartiQL`を用いたSQLライクなクエリ操作が気になったので、また機会があれば使用してみたいと思います。