Brief introduction of AWS DynamoDB Transactions
Here, we will try a new feature, namely "DynamoDB Transactions", which was announced during AWS reInvent 2018 conference. When using Lambda to write items into DynamoDB tables by a transaction, I found that the current Lambda native boto3 does not support DynamoDB's Transactions APIs. Specifically, when you see below error message, that means the boto3 on Lambda is not of the latest version.
AttributeError: 'DynamoDB' object has no attribute 'transact_write_items'.However, it is not impossible to overcome. Here, we utilize another new AWS feature, namely "Lambda Layer", which was announced during AWS reInvent 2018 conference as well, to serve as the latest boto3 library.
Upgrade AWS CLI
In the next steps, I need to issue below command, which need newer version of AWS CLI.
aws lambda publish-layer-version
So, on the server where I will issue the AWS CLI commands, I use "pip" to upgrade the AWS CLI tool to the latest version, because the primary distribution method for the AWS CLI on Linux is pip.
python -m pip install --upgrade awscliOr, use below command if you don't have particular requirements.
pip install --upgrade awscli
Legal path
For Python, the package should reside in below paths:
"python/lib/python2.7/site-packages", "python/lib/python3.6/site-packages", and other legal paths
mkdir python cd python
Install boto3 package to the local storage. Use --ignore-installed to force all dependencies to be reinstalled using this new prefix.
pip install --install-option="--prefix=/root/python" boto3 --ignore-installed
pip install --target /myfolder [packages]
Installs ALL packages including dependencies under /myfolder. Does not take into account that dependent packages are already installed elsewhere in Python. You will find packages from /myfolder/[package_name]. In case you have multiple Python versions, this doesn't take that into account (no Python version in package folder name).
pip install --prefix /myfolder [packages]
Checks if dependencies already installed. Will install packages into /myfolder/lib/python3.5/site-packages/[packages]
cd .. zip -r python_libs.zip ./python aws lambda publish-layer-version --layer-name python27-layer-boto3-goodexample --description "My layer" --compatible-runtimes python2.7 --zip-file fileb://python_libs.zip --region us-east-1
Nota bene
Below is not a legal path:
"python_lib/lib/python2.7/site-packages".
An incorrect path will lead to below error:
AttributeError: 'DynamoDB' object has no attribute 'transact_write_items'.
Just for you to compare this to the correct example.
mkdir python_lib cd python_lib pip install --install-option="--prefix=/root/python_lib" boto3 --ignore-installed cd .. zip -r python_libs.zip ./python_lib aws lambda publish-layer-version --layer-name python27-layer-boto3-badexample --description "My layer" --compatible-runtimes python2.7 --zip-file fileb://python_libs.zip --region us-east-1
Prepare Two DynamoDB tables
The first DynamoDB table is called "dynamodb-transaction-items" table. It had below item.
itemId (pt_key) |
available |
itemId000 |
TRUE |
The second DynamoDB table is called "dynamodb-transactions-players" table. It had below item.
playerId (pt_key) |
coins |
playerId000 |
100 |
Lambda function code
Below is the code in Lambda function.
import json import boto3 itemId = 'itemId000' playerId = 'playerId000' dynamodb_client = boto3.client( 'dynamodb', region_name="us-east-1", aws_access_key_id="###", aws_secret_access_key="###" ) response = dynamodb_client.transact_write_items( TransactItems=[ { 'Update': { 'TableName': 'dynamodb-transaction-items', 'Key': { 'itemId': { 'S': itemId } }, 'ConditionExpression': 'available = :true', 'UpdateExpression': 'set available = :false, ' + 'ownedBy = :player', 'ExpressionAttributeValues': { ':true': {'BOOL': True}, ':false': {'BOOL': False}, ':player': {'S': playerId} } } }, { 'Update': { 'TableName': 'dynamodb-transactions-players', 'Key': { 'playerId': { 'S': playerId } }, 'ConditionExpression': 'coins >= :price', 'UpdateExpression': 'set coins = coins - :price', 'ExpressionAttributeValues': { ':price': {'N': "100"} } } } ] )*
Associate this Lambda function with Lambda Layer, namely "python27-layer-boto3-goodexample".
Test case 1
Create a test event in Lambda. Here you use an empty JSON body.
{}
After run the Lambda function, the two items in the DynamoDB tables changed to below values.
dynamodb-transaction-items
itemId |
available | ownedBy |
itemId000 |
FALSE |
playerId000 |
dynamodb-transactions-players
playerId |
coins |
playerId000 |
0 |
Below is test result:
Test case 2
In this test, we will simulate a scenario where ConditionExpression is not met on one statement.
Change coins back to 100, as below.
playerId |
coins |
playerId000 |
100 |
After running the Lambda function, you should not observe any change take place, because the transaction criteria doesn't meet.
dynamodb-transaction-items
itemId |
available | ownedBy |
itemId000 |
FALSE |
playerId000 |
dynamodb-transactions-players
playerId |
coins |
playerId000 |
100 |
This time, there will be an expected error, meaning that the criteria was not met and hence the transaction was not proceeded:
botocore.errorfactory.TransactionCanceledException: An error occurred (TransactionCanceledException) when calling the TransactWriteItems operation: Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None]
To install Python 3-compatible boto3, follow below steps:
yum install -y python36Install pip.
Below package installation method is recommended.
python36 -m ensurepip --default-pip python3 -m pip install --install-option="--prefix=/root/python" boto3 --ignore-installedIf the above method doesn’t allow you to run pip, then download get-pip.py and install pip.
wget https://bootstrap.pypa.io/get-pip.py python36 get-pip.py /usr/local/bin/pip3 install --install-option="--prefix=/root/python" boto3 --ignore-installed
zip -r python_libs.zip ./python aws lambda publish-layer-version --layer-name python3-layer-boto3-goodexample --description "Custom Layer for latest boto3 under Py3" --compatible-runtimes python3.6 --zip-file fileb://python_libs.zip --region us-east-1Attach the Layer to the Lambda function
And above code is suitable for Python 3 runtime as well.
References
New – Amazon DynamoDB Transactions
AWS Lambda Layers
Tutorial – Publishing a Custom Runtime
Install a Python package into a different directory using pip?
Installing the AWS Command Line Interface
Installing the AWS Command Line Interface on Amazon Linux
How to install pip with Python 3?