🚕

[GAS×DynamoDB] BatchWriteItemでテーブルデータを操作する

2023/11/19に公開

GASでDynamoDBのテーブルデータを操作(新規作成、更新、削除)するツールを作成してみました。

1. DynamoDBのテーブルとツール(GAS)の概要

(1) DynamoDBのテーブルについて

テーブル構成は以下の通りで、ロボットID(RobotId)とその設定ID(SettingId)を紐づけるために使用します。

  • テーブル名: Robots-xxx (xxxは、環境(開発、テスト、本番)で変わるものとする)
  • 属性1: RobotId, 文字列型, パーティションキー
  • 属性2: SettingId, 文字列型

(2) ツール(GAS)について

テーブル: Robots-xxxのデータを新規作成、更新または削除します。

2. 手順

(1) テーブル作成

①まず、AWSコンソールにログインし、AWS Cloud Shellを用いて、テーブルを作成します。

②以下のコマンドを実行します。
今回、環境(開発、テスト、本番)に応じて、テーブル名の一部が異なる内容にしました。

# 1. 環境変数を確認(実効の結果、設定済の場合は4へ)
printenv | grep DynamoDBTableName

# 2. テーブル名を環境変数に設定: 設定済みの場合は不要
export DynamoDBTableName=Robots-dev    # 開発環境
export DynamoDBTableName=Robots-test   # test環境
export DynamoDBTableName=Robots-prod   # 本番環境

# 3. 環境変数を確認設定済みの場合は不要
printenv | grep DynamoDBTableName

# 4. テーブル作成
aws dynamodb create-table \
--table-name $DynamoDBTableName \
--attribute-definitions \
AttributeName=RobotId,AttributeType=S \
--key-schema \
AttributeName=RobotId,KeyType=HASH \
--provisioned-throughput \
ReadCapacityUnits=5,WriteCapacityUnits=5

# 5. テーブルが作成されたか確認
aws dynamodb list-tables

(2) Google Sheetsの編集

今回、Google Sheetsに入力した内容を使用します。
以下の通り作成しました。

作成シート


→ シート名は写っていませんが、「Input」としています。

説明

①C3列: データ数を入力(BatchWriteItemは最大25個)
②7行目~32行目のB, C列: RobotId, SettingIdをそれぞれ入力
③7行目~32行目のD列: 「データ」>「データの入力規則」からプルダウンを選択し、「新規」、「追加」、「削除」を設定

④7行目~32行目のE列: H6~I9セルに「作成シート」のキャプチャの通り入力し、以下の通り関数を設定

=INDEX(I$6:J$9,MATCH(D7,I$6:I$9,0),2)

⑤H2~H4列: スクリプトを実行すると、コマンドが出力される

(3) GASでスクリプト作成

GASでスクリプトを作成します。
①(2)のGoogle Sheetsで、「拡張機能」(①) > 「Apps Script」(②)をクリック

②DynamoDBのテーブルデータ操作用のスクリプトを作成します。
エディタに以下の通り入力し、保存(適宜、コメントアウトの内容を消す or 使用してください)

generateAndDownloadJson
/**
 * DynamoDB Robots-xxxテーブルデータ操作用
 */
function generateAndDownloadJson() {
  // 対象シート名を設定
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Input"); 
  /** テーブル名を環境変数名より取得する場合 */
  const tableName = `$DynamoDBTableName`;
  /** テーブル名が全環境同じ場合
  const tableName = sheet.getRange("C2").getValue();
  */
  // 対象データ数を取得
  const dataCount = parseInt(sheet.getRange("C3").getValue(), 10);

  // データ数が25個より多い場合はアラートを出して処理を終了
  if(dataCount > 25){
    SpreadsheetApp.getUi().alert(`データ数は25個以内にしてください`);
    return;
  }

  const keyNameRobotId = 'RobotId';
  const keyNameSettingId = 'SettingId';

  const dataRange = sheet.getRange(7, 2, dataCount, 4).getValues();

  let items = [];

  for (let i = 0; i < dataCount; i++) {
    const row = dataRange[i];
    if (!row || row.length < 2 || !row[0] || !row[1]|| !row[2]|| !row[3]) {
      // 不完全な行がある(B~E列のいずれかが欠落している)場合にアラートを表示し、処理を中断
      SpreadsheetApp.getUi().alert(`エラー: ${i + 7} 行目のデータが不完全です`);
      return;
    }

    let item = {};
    if(row[3] == 'PutRequest'){
      // PutRequest(新規作成、更新)の場合
      item[keyNameRobotId] = {"S": row[0]};
      item[keyNameSettingId] = {"S": row[1]};
      items.push({[row[3]]: {"Item": item}});
    }else if (row[3] == 'DeleteRequest'){
      // DeleteRequest(削除)の場合
      item[keyNameRobotId] = {"S": row[0]};
      items.push({[row[3]]: {"Key": item}});
    }else{
      // PutRequest, DeleteRequestが指定された場合は処理を中断
      SpreadsheetApp.getUi().alert(`${i + 7} 行目は不正なリクエストです`);
    }
  }

  /** テーブル名が全環境同じ場合
  let jsonData = {};
  jsonData[tableName] = items;
  */

  // 現在の日時を取得し、ファイル名を生成
  const fileName = `Robots-xxx_${Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyyMMddHHmmss")}.json`;

  // Google DriveにJSONファイルを保存
  /** テーブル名が環境ごとに異なり、直に指定する場合 */
  /** テーブル名を環境変数名より取得する場合 */
  const file = DriveApp.createFile(fileName, JSON.stringify(items), "application/json");
  /* テーブル名が全環境同じ場合
  const file = DriveApp.createFile(fileName, JSON.stringify(jsonData), "application/json");
  */
  // ダウンロードリンクの作成
  const url = file.getDownloadUrl();
  
  /** テーブル名が全環境同じ場合
  sheet.getRange("H2").setValue(`aws dynamodb batch-write-item --request-items file://${fileName}`);
  */
  
  /** テーブル名が環境ごとに異なり、直に指定する場合
  sheet.getRange("H2").setValue(`aws dynamodb batch-write-item --request-items "{\\"Robots-dev\\": $(cat ${fileName})}"`);
  sheet.getRange("H3").setValue(`aws dynamodb batch-write-item --request-items "{\\"Robots-test\\": $(cat ${fileName})}"`);
  sheet.getRange("H4").setValue(`aws dynamodb batch-write-item --request-items "{\\"Robots-prod\\": $(cat ${fileName})}"`);
  */


  /** テーブル名を環境変数名より取得する場合: 必要に応じて、環境変数の設定コマンド(export DynamoDBTableName=Robots-xxx)は消すこと */
  sheet.getRange("H2").setValue(`export DynamoDBTableName=Robots-dev && aws dynamodb batch-write-item --request-items "{\\"$DynamoDBTableName\\": $(cat ${fileName})}"`);
  sheet.getRange("H3").setValue(`export DynamoDBTableName=Robots-test && aws dynamodb batch-write-item --request-items "{\\"$DynamoDBTableName\\": $(cat ${fileName})}"`);
  sheet.getRange("H4").setValue(`export DynamoDBTableName=Robots-prod && aws dynamodb batch-write-item --request-items "{\\"$DynamoDBTableName\\": $(cat ${fileName})}"`);
  
  // モーダルの表示
  showDownloadModal(url);
}

function showDownloadModal(url) {
  const html = `<html><body><a href="${url}" target="_blank" style="color: blue; text-decoration: underline; padding: 10px 20px; text-align: center; display: inline-block; font-size: 16px; cursor: pointer;">ダウンロード</a></body></html>`;
  const ui = HtmlService.createHtmlOutput(html).setWidth(300).setHeight(100);
  SpreadsheetApp.getUi().showModalDialog(ui, ' ');
}

③任意でスクリプト名を変更します。
「︙」(①)>「名前を変更」をクリック(②)し、任意の名前に変更(③)(今回、関数名に合わせ、generateAndDownloadJson にしました)

④続いて、スクリプトをメニューバーから実行できるようにします。
「ファイルを追加」(①) > 「スクリプト」をクリックし、任意の名前を入力(③)(今回、createMenuにしました)

⑤エディタに以下の通り入力し、保存

function onOpen() {
  const ui = SpreadsheetApp.getUi();
  // メニューの項目を作成
  ui.createMenu('AWS操作')
    .addItem('DynamoDBデータ操作用', 'generateAndDownloadJson')
    .addToUi();
}

※addItemの第2引数が②で作成した関数名と一致させてください

⑥これによって、シートが開かれた時にメニューバーに「AWS操作」というタブが追加され、「DynamoDBデータ操作用」をクリックすると、②で作成した関数(generateAndDownloadJson)が実行されます。

(4) お試し実行

最後に、お試し実行してみます。

①テーブルは作成済で、以下のデータが存在している状態で行います。

②Google Sheets(「Input」シート)に以下の通り入力

  • C3セル: データ数を入力(①)
  • B列~D列: データ数で指定した行数分、操作データに関する情報を入力 or 選択(②)

③メニューバーにて、「AWS操作」(①)>「DynamoDBデータ操作用」をクリック(②)

④スクリプトの実行に成功すると、JSONダウンロード用のモーダルが表示されるので、「ダウンロード」リンクをクリック(ダウンロードが完了したら閉じること)

⑤「実行コマンド」欄に出力されたコマンドをコピー
※今回、テーブル名をRobots-devにしたので、H2セル(①)の内容をコピーしました(②)

⑥AWSコンソールにログインし、「CloudShell」をクリック

⑦コンソールが起動したら、「アクション」(①)>「ファイルをアップロード」をクリックし(②)、④で作成&ダウンロードしたファイルをアップロード

※ファイルのアップロードに成功すると、以下のようなメッセージが表示されます。

⑧⑤でコピーしたコマンドをコンソールに貼り付け、Enter

⑨実行後のDynamoDB Robots-devテーブルです。

実行前の内容と比較すると、

  • RobotId: 0001のアイテム SettingId: jp-002 → jp-003 に更新
  • RobotId: 0002のアイテム 削除
  • RobotId: 0003 SettingId:ch-001 のアイテム 新規作成
  • RobotId: 0004 SettingId:ch-002 のアイテム 新規作成

されています。

うまくいきました!

※テーブルデータについては、AWS CloudShellから以下のコマンドでも確認できます

aws dynamodb scan --table-name $DynamoDBTableName

3. 終わりに

実務で使用するサンプルとして作成しました。
最初、テーブル名、データ型、属性はシートから取得する内容にしていたのですが、作業者が誤って書き換え → 事故が起こるとたいへんなので、ソースコード内に埋め込みました。

本ツールによって、DynamoDBのテーブル操作が少しでも楽になったら嬉しいです。

4. 参考

※1: 他、本記事の執筆にあたって大部分をChatGPT: GPT-4のお力をお借りしました

※2: BatchGetItem を使う可能性も加味し、対象ページへのリンクを貼っておきます(調べればすぐ出ますが、、
batch-get-item - Amazon DynamoDB | aws

Discussion