An Item collection is a set of items with the same partition key value but different sort key values where the items of different entity types have relationships with the partition key.
As you read in the previous module, you should optimize DynamoDB tables for the number of requests it receives. It was also mentioned that DynamoDB does not have joins that a relational database has. Instead, you design your table to allow for join-like behavior in your requests.
In this step, you retrieve multiple entity types in a single request. In the gaming application, you may want to fetch details about a game. These details include information about the game itself such as the time it started, time it ended, and details about the users that played in the game.
This request spans two entity types: the Game
entity and the UserGameMapping
entity. However, this doesn’t mean you need to make multiple requests.
In the code you downloaded, a fetch_game_and_players.py script is in the scripts/ directory. This script shows how you can structure your code to retrieve both the Game
entity and the UserGameMapping
entity for the game in a single request.
The following code composes the fetch_game_and_players.py script:
import boto3
from entities import Game, UserGameMapping
dynamodb = boto3.client('dynamodb')
GAME_ID = "3d4285f0-e52b-401a-a59b-112b38c4a26b"
def fetch_game_and_users(game_id):
resp = dynamodb.query(
TableName='battle-royale',
KeyConditionExpression="PK = :pk AND SK BETWEEN :metadata AND :users",
ExpressionAttributeValues={
":pk": { "S": "GAME#{}".format(game_id) },
":metadata": { "S": "#METADATA#{}".format(game_id) },
":users": { "S": "USER$" },
},
ScanIndexForward=True
)
game = Game(resp['Items'][0])
game.users = [UserGameMapping(item) for item in resp['Items'][1:]]
return game
game = fetch_game_and_users(GAME_ID)
print(game)
for user in game.users:
print(user)
At the beginning of this script, you import the Boto 3 library and some simple classes to represent the objects in the application code. You can see the definitions for those entities in the scripts/entities.py file.
The real work of the script happens in the fetch_game_and_users
function that’s defined in the module. This is similar to a function you would define in your application to be used by any endpoints that need this data.
The fetch_game_and_users
function does a few things. First, it makes a Query
request to DynamoDB. This Query
uses a PK
of GAME#<GameId>
. Then, it requests any entities where the sort key is between #METADATA#<GameId>
and USER$
. This grabs the Game
entity, whose sort key is #METADATA#<GameId>
, and all UserGameMapping
entities, whose keys start with USER#
. Sort keys of the string type are sorted by ASCII character codes. The dollar sign ($) comes directly after the pound sign (#) in ASCII , so this ensures that you will get all mappings in the UserGameMapping
entity.
When you receive a response, you assemble the data entities into objects known by the application. You know that the first entity returned is the Game
entity, so you create a Game
object from the entity. For the remaining entities, you create a UserGameMapping
object for each entity and then attach the array of users to the Game
object.
The end of the script shows the usage of the function and prints out the resulting objects.
You can run the script in the Cloud9 Terminal with the following command:
python scripts/fetch_game_and_players.py
The script should print the Game
object and all UserGameMapping
objects to the console.
Game: 3d4285f0-e52b-401a-a59b-112b38c4a26b Map: Green Grasslands
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: branchmichael
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: deanmcclure
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: emccoy
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: emma83
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: iherrera
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: jeremyjohnson
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: lisabaker
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: maryharris
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: mayrebecca
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: meghanhernandez
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: nruiz
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: pboyd
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: richardbowman
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: roberthill
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: robertwood
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: victoriapatrick
UserGameMapping: 3d4285f0-e52b-401a-a59b-112b38c4a26b Username: waltervargas
This script shows how you can model your table and write your queries to retrieve multiple entity types in a single DynamoDB request. In a relational database, you use joins to retrieve multiple entity types from different tables in a single request. With DynamoDB, you specifically model your data, so that entities you should access together are located next to each other in a single table. This approach replaces the need for joins in a typical relational database and keeps your application high-performing as you scale up.
In this module, you designed a primary key and created a table. Then, you bulk-loaded data into the table and saw how to query for multiple entity types in a single request.
With the current primary key design, you can satisfy the following access patterns:
Create user profile (Write)
Update user profile (Write)
Get user profile (Read)
Create game (Write)
View game (Read)
Join game for a user (Write)
Start game (Write)
Update game for a user (Write)
Update game (Write)