DynamoDBでTransactionの使い方(正常系)

MySQLOracleでお馴染みのトランザクションAWS DynamoDBでも実装されました。
今回はDynamoDBのトランザクションを使ってみようと思います。

制約事項などもありますので、細かい部分はAWSの公式サイトをチェックしてください。
当ブログでは使い方に重点を置いて紹介したいと思います。

 

流れ

Lambda(Node.js)からDynamoDBへ処理を投げて、トランクションの動きを確認してみます。

シチュエーションとしてゲームのプレイヤーがアイテムを購入するシーンを想定してみます。
所持金やアイテムを保持するプレイヤーテーブルと、在庫管理のアイテムテーブルを用意します。

 

データの準備

DynamoDBへデータを用意します。
プレイヤーテーブル(test_players)と、アイテムテーブル(test_items)を用意します。

DynamoDBへデータを用意

playerIdが主キーです。

 

DynamoDBへデータを用意

itemIdが主キーです。
itemCだけavailable=true(在庫有り)にしてあります。

この後、MarioにitemCを購入してもらいます。

 

Lambdaの用意

node.jsのLambdaを作成してください。

 

node.jsのLambdaを作成

テンプレートを選択する所など、色々設定する場所がありますが、
後で削除するのでnode.jsであればどれを選んでも大丈夫です。

アクション>関数のエクスポート
を選択してLambdaのソースコードをダウンロードします。

Lambdaのソースコードをダウンロード

 

「デプロイパッケージのダウンロード」を押すとダウンロードが始まります。

Lambdaのソースコードをダウンロード

 

ダウンロードしたファイルを適当な場所へ解凍し、
コマンドプロンプト(windowsの場合)かterminal(macの場合)を開いて、
解凍したフォルダへ移動してください。

 

cd xxxxx(解凍先)

 

npmを使いAWS SDKをダウンロードします。
npmがインストールされていない人は別途インストールしておいてください。

 

npm install aws-sdk

 

下記ようなindex.jsとnode_modulesのある状態になったと思います。

index.jsとnode_modulesのある状態

 

次にindex.jsを編集してトランザクション処理を書いていきます

 

console.log('Loading function');
var AWS = require('aws-sdk');
var dynamoDb = new AWS.DynamoDB();

exports.handler = async (event, context) => {
  var data = await dynamoDb.transactWriteItems({
    TransactItems: [
      {
        Update: {
          TableName: 'test_items',
          Key: { itemId: { S: 'itemC' } },
          ConditionExpression: 'available = :true',
          UpdateExpression: 'set available = :false, ' +
          'ownedBy = :player',
          ExpressionAttributeValues: {
            ':true': { BOOL: true },
            ':false': { BOOL: false },
            ':player': { S: 'Mario' }
          }
        }
      },
      {
        Update: {
          TableName: 'test_players',
          Key: { playerId: { S: 'Mario' } },
          ConditionExpression: 'coins >= :price',
          UpdateExpression: 'set coins = coins - :price, ' +
            'inventory = list_append(inventory, :items)',
          ExpressionAttributeValues: {
            ':items': { L: [{ S: 'itemC' }] },
            ':price': { N: '30' }
          }
        }
      }
    ]
  }).promise();
  return `Successfully`;
};

test_itemsテーブルとtest_playersテーブルを同時に更新しに行っています。
アイテムテーブルの有効なアイテム(available=true)を無効へ(available=false)変更します。
同時にプレイヤーテーブルのMarioのコインを30減らします。

出来上がったらindex.jaとnode_modulesをzipへ圧縮します。

 

Lambdaへアップロード

zipファイルをLambdaへアップロードし、テストで実行してみましょう。

 

Lambdaへアップロード

 

実行結果にSuccessfullyと出れば成功です。

 

Lambdaへアップロード 成功

 

ValidationErrorが出てしまった場合は、更新に失敗しています。

 

Lambdaへアップロード 失敗

 

errorMessageの後ろの方に[ValidationError]と出ていた場合は1つめのUpdateが失敗しています。
[None, ValidationError]と出ていた場合は2つめのUpdateが失敗しています。
項目の型(ListかNumberかなど)を確認してみてください。

成功後の各テーブルを見てみましょう。

プレイヤーテーブルではMarioのcoinsが30減って、inventoryに1つ増えているのが確認できます。

<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crosspower/20230228/20230228054638.png" alt="Lambdaへアップロード" width="287" height="167" />

 

アイテムテーブルでは、itemCが無効(available=false)でMarioによって更新された状態になっています。

 

Lambdaへアップロード アイテムテーブル

 

最後に

今回は正常系の紹介をしたので、あまりトランザクションの恩恵は体感できなかったかもしれません。
実行した所で[None, ValidationError]が出た方は2つめまで実行したのに、
1つめがロールバックされているのを体験できたかもしれませんが。

まだまだ実装されたばかりのトランザクション機能ですが、
機会があれば掘り下げてお伝えしていきたいと思います。

 

 

 

AWS相談会