loading...

Updating Values in DyanmoDB Map Attributes

matthewvielkind profile image Matthew Vielkind ・3 min read

One project I’m working on translates a favorite party game among my friends into a SMS-based version that can be played anywhere. A requirement for the project is being able to manage the game state across players. To do this I created a DynamoDB record for each game that maintains the state of the game. Each player has an entry in a Map attribute, which stores data in key-value pairs, with details like their name and score. Throughout the game the score for players needs to be updated by accessing the nested Map attribute of each player. This post will explain a pattern for how you can dynamically build a pattern for updating individual data elements within DynamoDB Maps.

Setting Up an Example

Let's suppose we have a DynamoDB record for a game where a game_id uniquely identify the game and there is a players Map attribute containing all the information about each player, in this case just a score, like the example record below. In this example there are two players, Charlotte and Becky, who each have their own record nested in the players Map containing the player's score. As the game progresses the score for each player needs to be updated.

Item={  
    "game_id": "test",
    "players": {  
            "Charlotte": {  
                "score": 0  
             },  
             "Becky": {  
                "score": 0  
             }  
        }  
    }

Building the Update Pattern

As the game progresses the score attribute needs to be updated for each player. The function below shows how the DynamoDB update_item function can be used to update new nested Map attributes.

def update_player_score(game_id, player_id, score_val):  
    dynamo = boto3.resource('dynamodb')  
    tbl = dynamo.Table('<TableName>')  

    result = tbl.update_item(  
        Key={  
            "game_id": game_id  
        },  
        UpdateExpression="SET players.#player_id.score = :score_val",  
        ExpressionAttributeNames={  
            "#player_id": player_id  
        },  
        ExpressionAttributeValues={  
            ":score_val": score_val  
        }  
    )

The function takes parameters for the game_id, player_id, and score_val. Given those parameters the function will update the player's score to be equal to what is provided in score_val. For example, if you were to call, update_player_score("test", "Charlotte", 1) we would change the score of Charlotte to be 1. Let's walkthrough how that happens.

The first part of update_item. defines what key we will be updating. In this case game_id is the Key to the table, so we'll be updating records associated with the "test" game_id.

Next the UpdateExpression is defined. In the UpdateExpression we define the pattern for the value we want to update. For this function we want to update the nested parameters within the players map that could vary by the name of the player. In UpdateExpression the name of the players map is hardcoded, but accessing the nested Map attribute needs to be dynamic. There are two placeholders, #player_id and :score_val, in UpdateExpression defining the location of the specific attribute in the map that needs to be updated and the value to set to that nested attributed. The subsequent ExpressionAttributeNames and ExpressionAttributeValues definitions provide a mapping to what those placeholders should be.

The values provided in ExpressionAttributeNames and ExpressionAttributeValues will replace the placeholders that are in UpdateExpression when the function is called. For example consider the case where Charlotte's score becomes 1. When the function is called the UpdateExpression would be equivalent to SET players.Charlotte.score = 1 after the substitutions for #player_id and :score_val are made from ExpressionAttributeNames and ExpressionAttributeValues.

In DynamoDB an ExpressionAttributeNames is the placeholder for dynamic attribute values. When constructing the UpdateExpression all attribute values must be prefaced with a #. In our example we want to update the score for a player, but since the player that's being updated is dynamic the ExpressionAttributeNames fills in the #player_id placeholder within UpdateExpression.

Similarly, ExpressionAttributeValues are placeholders for when the the value of an attribute are not known until runtime. In our case the score to assign to the player. All ExpressionAttributeValues are prefaced with a :.

If we wanted to update Becky's score to give her 3 points we could use this function call and the UpdateExpression would handle the proper mapping to where Becky is located in the players attribute map:

update_player_score("test", "Becky", 3)

And that’s it! Patterns similar to this are helpful when you have Map attributes in DynamoDB where the path to the attribute you want to update will be dynamoic. When working with Map attributes in DynamoDB it's important to define the ExpressionAttributeNames and ExpressionAttributeValues that will be dynamic when building your UpdateExpression then construct the expression accordingly. Hopefully this was a helpful exercise for you! There are a couple more projects I’m working on that I can’t wait to share so stay tuned!

Discussion

pic
Editor guide
Collapse
adilfulara profile image
Adil Fulara

Interesting article..! Thank you for posting it

Curious to know why you didn’t select a schema where game id is the partition key and player is is the sort key. That way your writes would be sharded and you could get a query to read the two items. Assuming that this is a write heavy application.

Collapse
matthewvielkind profile image
Matthew Vielkind Author

Yes! In this use case you could definitely include the player as a kind of sort key in the design.