Some contents of this page was copied or inspired by Alex DeBries article on single table design. Alex is the authority on DynamoDB and his DynamoDB book is a must-read for anyone designing data models on DynamoDB.
On the previous page we discussed how to approach storing different types of data with our application, and the answer to that question is: by using a DynamoDB table with a single table design approach.
This page will deal with code changes needed for this design to work.
It is not possible to change DynamoDB table keys after the table was created. Since we’re still in the development phase of our application it is OK to simply re-create the table.
To delete the existing table please execute:
aws dynamodb delete-table --table-name $WORKSHOP_NAME-savealife-$ENV --profile workshop
Now re-create your table with:
aws dynamodb create-table --table-name $WORKSHOP_NAME-savealife-$ENV \
--profile workshop \
--attribute-definitions AttributeName=PK,AttributeType=S \
--key-schema AttributeName=PK,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
The end result will be the same.
Let’s talk about the DB layer changes in chalicelib/db.py
. What changed with our table and how should our DB layer
reflect those changes?
A “donor” item looked like this, with first_name
being the primary key:
Now the primary key will be the PK
field and everything else, the whole JSON payload, will become attributes.
You may use the following code sample to implement the required changes in chalicelib/db.py
:
import logging
from os import getenv
from uuid import uuid4
import boto3
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
ENV = getenv("ENV", "dev")
first_name = getenv("WORKSHOP_NAME", "ivica") # replace with your own name of course
logger = logging.getLogger(f"{first_name}-savealife")
_DB = None
TABLE_NAME = getenv("TABLE_NAME")
def get_app_db():
global _DB
if _DB is None:
_DB = SavealifeDB(
table=boto3.resource("dynamodb").Table(TABLE_NAME), logger=logger
)
return _DB
class SavealifeDB:
def __init__(self, table, logger):
self._table = table
self._logger = logger
def donor_signup(self, donor_dict):
uid = str(uuid4()).split("-")[-1]
try:
self._table.put_item(
Item={
"first_name": donor_dict.get("first_name"),
"city": donor_dict.get("city"),
"type": donor_dict.get("type"),
"email": donor_dict.get("email"),
"PK": f"DONOR#{uid}",
}
)
self._logger.debug(
f"Inserted donor '{donor_dict.get('email')}' into DynamoDB table '{self._table}'"
)
return True
except Exception as exc:
self._logger.exception(exc)
def donation_create(self, donation_dict):
uid = str(uuid4()).split("-")[-1]
try:
self._table.put_item(
Item={
"city": donation_dict.get("city"),
"datetime": donation_dict.get("datetime"),
"address": donation_dict.get("address"),
"PK": f"DONATION#{uid}",
}
)
self._logger.debug(
f"Inserted donation '{donation_dict.get('city')}, {donation_dict.get('address')}' "
f"into DynamoDB table '{self._table}'"
)
return True
except Exception as exc:
self._logger.exception(exc)
return False
Highlighted lines are the needed changes, and we only need a few. Each “donor” and each “donation” must be unique, so
we reach out to the trusty uuid4()
for that. You have also probably noticed that both the “donor” and the “donation” item
have the PK
field added to them with a unique string.
How unique are uuid4
strings? We would need to generate 1 billion UUIDs
per second for 85 years to have a 50% chance of generating two identical UUIDs. Unique enough for me. Source
We went through a lot of text and a lot of code. Now’s the time to see the fruits of our labor: chalice deploy
time.
# add a donor
http -b POST $(chalice url)/donor/signup first_name=ivica email="ivica@server.com" type="A+" city="Amsterdam"
# add a donation
http -b POST $(chalice url)/donation/create city=Haarlem address="Main street" datetime="2022-04-06T12:00:00"
Table explorer in the DynamoDB UI shows that both items were inserted into the single table we have.
As we saw on the DynamoDB 101 page, Alex DeBrie’s resources are a gold mine. Watch part 1 and part 2 of his “Data modeling with DynamoDB presentation” for more information on single table design, access patterns and more.