AWS Step FunctionsでDynamoDBに対して小数点2桁の数値の入出力や足し算をしてみた

2022.06.16

こんにちは、CX事業本部 IoT事業部の若槻です。

以前に下記エントリを書きましたが、この時に扱う数値を小数点2桁とする必要が出てきました。

しかし小数点を扱う場合は次の点に注意する必要があります。

  • DynamoDBへ入出力する小数点がある数値はFloat型とDecimal型の変換を行う必要がある
  • Float型の数値の計算を行うと誤差が生じる

そこで今回は、AWS Step FunctionsでDynamoDBに対して小数点2桁の数値の入出力や足し算をした際の動作を確認してみました。

やってみた

環境構築

検証に必要な環境の構築はAWS CDK v2(TypeScript)で行います。次のようなCDKスタックを作成します。

lib/process-stack.ts

import { Construct } from 'constructs';
import {
  aws_stepfunctions,
  aws_stepfunctions_tasks,
  aws_dynamodb,
  Stack,
  StackProps,
  RemovalPolicy,
} from 'aws-cdk-lib';

export class ProcessStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);

    //テーブル
    const someTable = new aws_dynamodb.Table(this, 'someTable', {
      tableName: 'someTable',
      partitionKey: {
        name: 'id',
        type: aws_dynamodb.AttributeType.STRING,
      },
      billingMode: aws_dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: RemovalPolicy.DESTROY,
    });

    //PutItem
    const putItemTask = new aws_stepfunctions_tasks.DynamoPutItem(
      this,
      'putItemTask',
      {
        table: someTable,
        item: {
          id: aws_stepfunctions_tasks.DynamoAttributeValue.fromString(
            aws_stepfunctions.JsonPath.stringAt('$.id'),
          ),
          valA: aws_stepfunctions_tasks.DynamoAttributeValue.fromNumber(
            aws_stepfunctions.JsonPath.numberAt('$.val1'),
          ),
          valB: aws_stepfunctions_tasks.DynamoAttributeValue.fromNumber(
            aws_stepfunctions.JsonPath.numberAt('$.val2'),
          ),
        },
        resultPath: '$.null',
      },
    );

    //GetItem
    const getItemTask = new aws_stepfunctions_tasks.DynamoGetItem(
      this,
      'getItemTask',
      {
        table: someTable,
        key: {
          id: aws_stepfunctions_tasks.DynamoAttributeValue.fromString(
            aws_stepfunctions.JsonPath.stringAt('$.id'),
          ),
        },
        resultPath: '$.null',
      },
    );

    //UpdateItem
    const updateItemTask = new aws_stepfunctions_tasks.DynamoUpdateItem(
      this,
      'updateItemTask',
      {
        table: someTable,
        key: {
          id: aws_stepfunctions_tasks.DynamoAttributeValue.fromString(
            aws_stepfunctions.JsonPath.stringAt('$.id'),
          ),
        },
        expressionAttributeNames: {
          '#valA': 'valA',
          '#valB': 'valB',
          '#valC': 'valC',
        },
        expressionAttributeValues: {
          ':val2': aws_stepfunctions_tasks.DynamoAttributeValue.fromNumber(
            aws_stepfunctions.JsonPath.numberAt('$.val2'),
          ),
        },
        updateExpression:
          'SET #valA = #valA + :val2, #valB = #valB + :val2, #valC = #valB - #valA',
      },
    );

    // ステートマシン
    new aws_stepfunctions.StateMachine(this, 'stateMachine', {
      stateMachineName: 'stateMachine',
      definition: putItemTask.next(getItemTask).next(updateItemTask),
    });
  }
}

CDK Deployをしてリソースを作成します。

作成されたステートマシンのDefinitionは次のようになりました。

{
  "StartAt": "putItemTask",
  "States": {
    "putItemTask": {
      "Next": "getItemTask",
      "Type": "Task",
      "ResultPath": "$.null",
      "Resource": "arn:aws:states:::dynamodb:putItem",
      "Parameters": {
        "Item": {
          "id": {
            "S.$": "$.id"
          },
          "valA": {
            "N.$": "$.val1"
          },
          "valB": {
            "N.$": "$.val2"
          }
        },
        "TableName": "someTable"
      }
    },
    "getItemTask": {
      "Next": "updateItemTask",
      "Type": "Task",
      "ResultPath": "$.getItemTaskOutput",
      "Resource": "arn:aws:states:::dynamodb:getItem",
      "Parameters": {
        "Key": {
          "id": {
            "S.$": "$.id"
          }
        },
        "TableName": "someTable",
        "ConsistentRead": false
      }
    },
    "updateItemTask": {
      "End": true,
      "Type": "Task",
      "Resource": "arn:aws:states:::dynamodb:updateItem",
      "Parameters": {
        "Key": {
          "id": {
            "S.$": "$.id"
          }
        },
        "TableName": "someTable",
        "ExpressionAttributeNames": {
          "#valA": "valA",
          "#valB": "valB",
          "#valC": "valC"
        },
        "ExpressionAttributeValues": {
          ":val2": {
            "N.$": "$.val2"
          }
        },
        "UpdateExpression": "SET #valA = #valA + :val2, #valB = #valB + :val2, #valC = #valB - #valA"
      }
    }
  }
}

動作確認

次の入力を指定してステートマシンを実行します。

{
  "id": "aaa",
  "val1": "1.23",
  "val2": "4.56"
}

上記入力によりステートマシン実行内で次のような計算が行われることを期待します。

  • putItemTask
    • 1.23をvalAにPut
    • 4.56をvalBにPut
  • getItemTask
    • 1.23をvalAからGet
    • 4.56をvalBからGet
  • updateItemTask
    • valAを5.79 = 1.23 + 4.56にUpdate(繰上げなしの足し算)
    • valBを9.12 = 4.56 + 4.56にUpdate(繰上げありの足し算)
    • valCを3.33 = 4.56 - 1.23にUpdate(引き算)

ステートマシンの実行結果を見ると成功しています。型の不整合によるエラーは発生していないようです!

DynamoDBテーブル上のデータを見ると期待通りの値となっていました!

{
 "id": "aaa",
 "valA": 5.79,
 "valB": 9.12,
 "valC": 3.33
}

各タスクでの入出力も見てみます。

putItemTaskでは、ステートマシン実行時の入力値であるval1: 1.23およびval2: 4.56がPutされています。

getItemTaskでは、putItemTaskでPutした値がそれぞれGetできています。

これらもそれぞれ期待通りの値となりました!

小数点の桁数が異なる場合

念のため、小数点2桁の範囲で小数点の桁数が異なる場合も確認してみます。

{
  "id": "aaa",
  "val1": "1.23",
  "val2": "4"
}

実行すると成功しました。

テーブルにも期待通りの値が格納されています。

問題は無さそうですね。

おわりに

AWS Step FunctionsでDynamoDBに対して小数点2桁の数値の入出力や足し算をしてみました。

冒頭で示したようにプログラム小数点を扱う場合には注意をする必要があるのですが、ステートマシンでは特に問題なく行えて良かったです。

以上