High performance and scale with Amazon DynamoDB

Introduction

In this course, you create a restaurant-review application whose users rate restaurants and leave reviews. Other users can then browse these reviews to find popular restaurants.

In this lesson, you build the core service for rating and reviewing restaurants. Users can review or rate restaurants, and other users can browse reviews and ratings when choosing a restaurant.

For this service, you’ll use Amazon DynamoDB, a NoSQL database from AWS. This lesson teaches you how to use a fully managed DynamoDB table in your application. First, you learn why you would want to use DynamoDB. Then you work through the steps to create a DynamoDB table and use it in your application. At the end of this lesson, you should feel confident in your ability to use DynamoDB in your application.

Time to complete: 3045 minutes

Purpose-built Databases - DynamoDB (34:13)
Why use DynamoDB?

DynamoDB is a fully managed NoSQL database that provides fast, consistent performance at any scale. It has a flexible billing model, tight integration with infrastructure as code, and a hands-off operational model.

DynamoDB has become the database of choice for two categories of applications:

  • High-scale applications: DynamoDB was built for scale. It is based on the learning from Amazon engineers as they scaled the Amazon.com retail infrastructure to handle worldwide scale. DynamoDB is used at scale by many engineering teams, from fast-growing startups such as Airbnb and Lyft to enormous enterprises such as Capital One and Samsung. DynamoDB can provide the same consistent performance at any scale, so you won't need to refactor your database as your data grows.
  • Serverless applications: DynamoDB is popular with developers building serverless applications with services such as AWS Lambda and AWS API Gateway. The ephemeral compute nature of Lambda is difficult for traditional databases to handle. DynamoDB has an HTTP connection model and AWS Identity and Access Management (IAM) authentication that works well with serverless compute. Additionally, DynamoDB has a pay-per-use pricing option that fits well in the serverless world.

Though DynamoDB is the database of choice for high-scale and serverless applications, it can work for nearly all online transaction processing (OLTP) application workloads. DynamoDB can handle relationships between entities as well as complex filtering and sorting requirements.

Lesson contents

In this lesson, you learn how to build a service that uses DynamoDB for data storage. This lesson has five parts.

  • In this module, you create and prepare an AWS Cloud9 environment. AWS Cloud9 is a cloud-based integrated development environment (IDE). It gives you a development environment that is available from anywhere and from which you can quickly build AWS applications.


    To get started, navigate to the AWS Cloud9 console. Choose Create environment to start the AWS Cloud9 environment creation wizard.

    (click to zoom)
    On the first page of the wizard, give your environment a name, description and choose New EC2 Instance.
    (click to zoom)
    The next step allows you to configure environment settings, such as the instance type for your environment, the platform, and network settings.
    The default settings work for this lesson.
    (click to zoom)
    The last step shows your connection settings. The default settings work for this lesson. Scroll to the bottom and choose Create environment.
    (click to zoom)

    Your AWS Cloud9 environment should take a few minutes to provision. As it is being created, the following screen is displayed.

    (click to zoom)

    After a few minutes, you should see your AWS Cloud9 environment. There are three areas of the AWS Cloud9 console to know, as illustrated in the following screenshot:

    • File explorer: On the left side of the IDE, the file explorer shows a list of the files in your directory.
    • File editor: In the upper right area of the IDE, the file editor is where you view and edit files that you’ve chosen in the file explorer.
    • Terminal: In the lower right area of the IDE, the terminal is where you run commands to execute code samples.
    (click to zoom)

    In this lesson, you use Python to interact with your DynamoDB table. Run the following commands in your AWS Cloud9 terminal to download and unpack the module code.

    cd ~/environment
    curl -sL https://s3.amazonaws.com/aws-data-labs/restaurant-dynamodb.tar | tar -xv

    Run the following command in your AWS Cloud9 terminal to view the contents of your directory.

    ls

    You should see two directories in your AWS Cloud9 terminal:

    • scripts/: This directory contains administrative scripts to manipulate your DynamoDB table, such as creating the table, adding a secondary index, and deleting your table when you are finished.
    • application/: This directory contains code for interacting with the data in your DynamoDB table. It is similar to the code you have in your application.

    Execute the following command in your terminal to install the dependencies for this lesson.

    sudo pip install -r requirements.txt

    pip install boto3

    pip install ksuid


    In this module, you configured an AWS Cloud9 instance to use for development. In the next module, you plan your data model with DynamoDB.

  • When working with any database, you need to ensure you model your data in a way that conforms to the style of database you are using. Data modeling styles vary across databases.

    With relational databases, you normalize your entities into atomic units with each entity being represented in separate tables. You then use references across tables to indicate relationships between entities. After you have normalized your data, you handle your access patterns by writing SQL queries to fetch the data you want. In this way, your data is designed before addressing your access patterns.

    With NoSQL databases, the reverse is true. You consider your access patterns first, and then design your data model to handle those access patterns. By designing specifically for your access patterns, you can make a more efficient database that is able to scale further than a relational database.

    If you are coming from a relational database, data modeling with a NoSQL database such as DynamoDB will seem different. But you don't want to force a relational mindset on DynamoDB. You need to ensure you follow the principles of data modeling with DynamoDB.

    When modeling your data with a NoSQL database such as DynamoDB, follow three steps:

    1. Create an entity-relationship diagram (ERD). An ERD lists the different objects (or entities) in your application and shows how they relate to each other via relationships. This diagram helps you to understand the core concepts in your application.
    2. Understand your access patterns. After you have created your ERD, list the various ways you access your objects in your application. This includes all read-based and write-based access patterns.
    3. Design your data model to handle your access patterns. After you have listed your access patterns, craft your data model to handle those specific access patterns.

    NoSQL data modeling with DynamoDB is a broad topic, and this lesson doesn’t cover all the principles. For additional information about NoSQL data modeling with DynamoDB, see the DynamoDB documentation on Best Practices for Designing and Architecting with DynamoDB. Additionally, you can see other hands-on examples of data modeling with DynamoDB.

    With these principles in mind, let's get started preparing the data model.

     


    Create your entity-relationship diagram

    For this service, you are handling restaurant reviews. Users can create an account in the application and leave reviews about restaurants they have visited. A review contains both a rating (1–5) and a text-based summary of a user's visit. The application allows users to see a summary of a restaurant, such as the restaurant details, rating summary, and most recent reviews. The application also allows users to browse restaurants by ZIP code.

    Note that you are not handling users in this service because they are handled in the Amazon Keyspaces lesson later in this course.

    With that in mind, let's look at the ERD.

    (click to zoom)

    The ERD is pretty simple and contains only two entities: Restaurants and Reviews. There is a one-to-many relationship between Restaurants and Reviews because Restaurants can receive Reviews from many different users.

    Now that you know the entities and relationships, you can move on to the next step.

     

    List your application access patterns

    The next step is to list the access patterns in the application. It is important to be thorough here and consider all access patterns up front so that you can design an effective table.

    The most important access pattern is Fetch Restaurant Summary. When a user navigates to a restaurant's page, they want to see the most relevant information about a restaurant, such as the restaurant details, a summary of all ratings, and the five most recent reviews.

    As part of this access pattern, we have the basic create and read access patterns for both of the core entities. Also, note that for the Create Review access pattern, you want to enforce uniqueness to ensure the submitting user has not already reviewed the given restaurant. This prevents a user from skewing a restaurant's rating by submitting multiple negative or positive reviews.

    Finally, you may occasionally find that you need to remove a review for one reason or another. It could be because the review was unnecessarily malicious or that the review was posted by someone affiliated with the restaurant to inflate its overall rating. Accordingly, we'll have a Remove Review access pattern.

    There is a total of six access patterns you need to handle for the application:

    • Fetch Restaurant Summary
    • Create Restaurant
    • Get Restaurant
    • Create Review
    • Get Review
    • Remove Review

    Now that you have the access patterns for your application, you will work through designing your table to handle these access patterns.


    In this module, you learned about preparing your data model with DynamoDB. First, you learned how the data modeling process with DynamoDB is different from the data modeling process with a relational database. Then we built the entity-relationship diagram for the restaurant rating application. Finally, we listed the access patterns in the application.

    In the next module, you learn how to plan your data model by designing a primary key and secondary indexes.

  • In this module, we start to plan a DynamoDB data model to handle access patterns. As we plan this data model, you learn some key principles about data modeling with DynamoDB.


    In the previous module, you learned that the process for modeling your data in DynamoDB is different from the process for modeling your data in a relational database. In addition to the difference in process, there are principles that are different when modeling data in DynamoDB.

    To understand the principles of DynamoDB data modeling, you should understand two design decisions about DynamoDB.

    First, DynamoDB does not allow joins across tables. Joins are a common feature of relational databases where you combine values from multiple tables when querying to arrive at a final result. As your data scales, joins perform more and more slowly because they need to read large chunks of values from multiple tables. To avoid these scaling problems, DynamoDB removed joins altogether.

    Second, you are limited in the attributes you can use to query your data. In a relational database, you can use any column in your tables in your query. With DynamoDB, the focus is on using the primary key to access data. Proper modeling of the primary key is crucial for an effective DynamoDB data model.

    These two design decisions help to ensure your DynamoDB table has consistent performance as it scales to any size. As a result of these two design decisions, you should use the following principles in modeling your data:

    • Put multiple entities in a single table. As noted, DynamoDB does not allow joins across tables. In order to allow for join-like capabilities, you’ll put multiple different entity types in a single table. In this example, that means Restaurants and Reviews are in the same table.
    • Use generic names and values for your primary keys. All data access is done via the primary key. However, because a DynamoDB table has multiple different entities, you can't use primary key names such as RestaurantName or RatingId because some entities won't have those properties. Rather, you use names such as PK (for partition key) and SK (for sort key). The values for the primary key are specifically designed to help with your access patterns.
    • Use secondary indexes to handle additional access patterns. The primary key of your table handles your basic access patterns around creation and deletion of individual items. You may even be able to handle some more-advanced access patterns that require you to fetch multiple items in a single request. However, sometimes you have additional access patterns that don't fit with your primary key design on your table. You can use secondary indexes to enable these additional patterns. Secondary indexes act like additional primary keys on your table and enable fast access on secondary access patterns. When you insert items into your table, DynamoDB handles the copying of those items into your secondary index with the new primary key pattern.

    With these principles in mind, let's design the primary key to handle the access patterns.

    Designing the primary key

    Primary key design is a crucial part of DynamoDB data modeling. The primary key determines how you identify and access data in your table.

    The first step is to decide which type of primary key you want. There are two types of primary keys in DynamoDB. The first type is a simple primary key and consists of one element—a partition key. The second type is a composite primary key and is composed of two elements: a partition key and a sort key.

    With a simple primary key, you can do only individual key-value operations on your DynamoDB items. With a composite primary key, you can do more advanced patterns such as using the Query operation to retrieve multiple items with the same partition key.

    In most applications, you want to choose a composite primary key because it allows for more complex modeling and additional flexibility as your application grows. We use a composite primary key in this lesson.

    After you have chosen a primary key type, you need to design the primary key for your entities. The first thing to consider is any uniqueness requirements. If your entities need to be unique on a specific dimension, you must build that requirement into your primary key.

    In this lesson, we have uniqueness requirements for both of our entities. First, a Restaurant must be uniquely identified by its name. Second, we want to ensure that a user is prevented from leaving multiple reviews for a single restaurant. Thus, a Review item must be uniquely identified by the combination of restaurant name and user name.

    With that in mind, we can use the following primary key structure for these two entities.

    (click to zoom)

    For the Restaurant entity, the value for both the PK and the SK is REST#<RestaurantName>.

    For the Review entity, the value for the PK is USER#<Username> and the value for the SK is REST#<RestaurantName>. By encoding both the Username and the RestaurantName into the primary key, we can assert that the combination of the two properties is unique for any given Review.

    For both entity types, notice that we are including prefixes (REST# and USER#) in the primary key values. This helps to identify the type of item and to prevent collisions across entity types.

    You can use NoSQL Workbench for DynamoDB to help with data modeling. This tool allows you to create tables and insert items, and see how they look in your table.

    The following is an example of some Restaurant and Review items loaded into NoSQL Workbench.

    (click to zoom)

    The first two items are for Susan's Steaks and Thai Time restaurants. The next two items are reviews for Thai Time restaurant.

    This basic primary key pattern has already satisfied five of the six access patterns:

    • Create Restaurant
    • Get Restaurant
    • Create Review
    • Get Review
    • Remove Review
     
    In the next module, we walk through some code to handle some of these access patterns. Next, let's look at handling the Fetch Restaurant Summary access pattern, which is a more complex access pattern.
    Using denormalization and secondary indexes

    The final access pattern we want to handle is more complex. We want to Fetch Restaurant Summary, which includes:

    • The Restaurant item with restaurant details.
    • A summary of ratings (1–5) from all reviews of the restaurant.
    • The text of the five most recent reviews for the restaurant.

    We'll use two different strategies to help with this access pattern.

    First, it would be inefficient to fetch every Review item for a restaurant to recalculate its ratings summary every time a user looked at the restaurant. Instead, you denormalize your data by storing a summary of all ratings for the restaurant. Each Restaurant item has five attributes indicating the number of 1-star, 2-star, 3-star, 4-star, and 5-star ratings the restaurant has received. Whenever a new review is created, your application also increments the relevant counter attribute on the parent Restaurant item. This allows you to quickly show the average rating, the number of ratings, and the distribution of ratings.

    In NoSQL Workbench, the items now look as follows.

    (click to zoom)

    Notice how the two Restaurant items now have attributes for the number of ratings for each number of stars.

    The second strategy helps you to retrieve the Restaurant item and the five most recent Review items in a single request. To handle this, use a secondary index to reshape your data. A secondary index adds an additional primary key structure to your table to allow for additional query patterns.

    In the secondary index, the key structure is as follows.

    (click to zoom)

    Now add two new attributes to the items—GSI1PK and GSI1SK. These attributes are generic key names, just like with the primary key.

    Notice that both our Restaurant and Review items have the same value for GSI1PK. When items have the same partition key value, they are said to be in the same item collection. You can use the Query operation to retrieve multiple items in the same item collection in a single request.

    Whenever a Review is created, the application assigns it a ReviewId that is used in the GSI1SK value. The ReviewId is a K-Sortable Unique IDentifer (KSUID). A KSUID provides some of the uniqueness guarantees of universally unique identifiers (UUIDs) while also including a time-based prefix that allows for chronological sorting. This allows you to fetch the most recent Review items for a specific restaurant.

    Your base table in NoSQL Workbench now looks as follows.

    (click to zoom)

    Notice how the GSI1PK and GSI1SK values have been added for all items (outlined in red). The ratings that were added in the previous step have been removed to make it easier to view.

    If you switch to looking at the GSI1 secondary index, you can see how the data is rearranged for the secondary index.

    (click to zoom)

    Notice how the Thai Time Restaurant item and the two Review items for the restaurant are all in the same item collection (outlined in red).

    In the next module, you learn how to retrieve this data to handle the access pattern.

    Creating the DynamoDB table

    In the scripts/ directory that you downloaded, there is a file called create_table.py that creates the DynamoDB table for your application. The contents of the file are as follows.

    import boto3
    
    dynamodb = boto3.client("dynamodb")
    
    try:
        dynamodb.create_table(
            TableName="Restaurants",
            AttributeDefinitions=[
                {"AttributeName": "PK", "AttributeType": "S"},
                {"AttributeName": "SK", "AttributeType": "S"},
                {"AttributeName": "GSI1PK", "AttributeType": "S"},
                {"AttributeName": "GSI1SK", "AttributeType": "S"},
            ],
            KeySchema=[
                {"AttributeName": "PK", "KeyType": "HASH"},
                {"AttributeName": "SK", "KeyType": "RANGE"},
            ],
            GlobalSecondaryIndexes=[
                {
                    "IndexName": "GSI1",
                    "KeySchema": [
                        {"AttributeName": "GSI1PK", "KeyType": "HASH"},
                        {"AttributeName": "GSI1SK", "KeyType": "RANGE"},
                    ],
                    "Projection": {"ProjectionType": "ALL"},
                    "ProvisionedThroughput": {
                        "ReadCapacityUnits": 5,
                        "WriteCapacityUnits": 5,
                    },
                }
            ],
            ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
        )
        print("Table created successfully.")
    except Exception as e:
        print("Could not create table. Error:")
        print(e)

    The script uses Boto3, the AWS SDK for Python, to interact with DynamoDB. The script creates a table called Restaurants to be used for your restaurant rating application. It specifies the primary key elements using the generic names mentioned previously (PK and SK). The script also creates a global secondary index named GSI1 with the generic names discussed previously. Finally, the script specifies the read capacity units and write capacity units to provision on the table.

    Run the following command in your terminal to execute the script and create your table.

    python scripts/create_table.py

    You should see a message in your terminal that the table was created successfully.

    Next, load some sample data into the table. Run the following command to run the bulk load script.

    python scripts/bulk_load_table.py

    You should see a message in your terminal that the data was loaded successfully.


    In this module, you planned your DynamoDB data model and loaded some sample data. To do this, you learned about some key DynamoDB data modeling principles. You also learned about DynamoDB concepts, such as primary keys and secondary indexes, and you saw how to model your data by using NoSQL Workbench for DynamoDB. Finally, you used Boto3, the AWS SDK for Python, to create your table and load some sample data.

    In the next module, you see how to interact with DynamoDB in your application code.

  • In this module, you learn how to interact with DynamoDB as a database in your application. You use the table that you created and loaded with sample data in the previous module.

    Remember that there are six access patterns in your restaurant application:

    • Fetch Restaurant Summary
    • Create Restaurant
    • Get Restaurant
    • Create Review
    • Get Review
    • Remove Review

    In this module, you learn how to write the code to implement three of these access patterns. Let’s start with a straightforward example and add more complexity as we move on to other access patterns.


    Creating a restaurant

    The first access pattern to handle is the Create Restaurant access pattern. This is the simplest access pattern you have because it involves operating on a single item. When creating the Restaurant item, you need to ensure that there is not an existing Restaurant with the same name.

    In the application/ directory, there is a file called create_restaurant.py. Open this file in your file editor. The contents are as follows.

    import boto3
    
    dynamodb = boto3.client("dynamodb")
    
    
    def create_restaurant(name, cuisine, address):
        try:
            resp = dynamodb.put_item(
                TableName="Restaurants",
                Item={
                    "PK": {"S": "REST#{}".format(name)},
                    "SK": {"S": "REST#{}".format(name)},
                    "GSI1PK": {"S": "REST#{}".format(name)},
                    "GSI1SK": {"S": "REST#{}".format(name)},
                    "name": {"S": name},
                    "cuisine": {"S": cuisine},
                    "address": {"S": address},
                },
                ConditionExpression="attribute_not_exists(PK)",
            )
            return {"name": name, "cuisine": cuisine, "address": address}
        except Exception as e:
            print("Could not create restaurant")
            print(e)
            return False
    
    
    restaurant = create_restaurant(
        "Bev's Bistro", "French", "541 Salazar Ranch, South Kristen, MS 00857"
    )
    
    if restaurant:
        print("Created restaurant: {}".format(restaurant["name"]))

    After importing the Boto3 library and initializing a client, there is a function called create_restaurant. This function is similar to what will be in your application code. It takes in a name, cuisine, and address, and it calls the PutItem operation to insert a Restaurant item into the table.

    Notice that the PutItem operation includes a ConditionExpression parameter. This is used to assert that an item with the same primary key does not already exist. If it does, the PutItem operation is rejected and the item is not written.

    At the bottom of the file, there is an example of how to use the function. It calls the create_restaurant function to create a new restaurant called Bev's Bistro.

    You can execute the script and create the new restaurant by running the following command in your terminal.

    python application/create_restaurant.py

    You should see a message in your terminal indicating that the new restaurant item was created successfully.

    Try executing the script again. You should receive an error indicating that the restaurant could not be created because the conditional check failed. This is because of the ConditionExpression you included in the call to prevent accidental overwrites of existing items.

    Adding a review to a restaurant

    Next, let’s walk through the process to allow a user to review a restaurant. You’ll recall from the previous module that we’ve done some denormalization in our DynamoDB table to allow for a summary of ratings on a Restaurant item. Thus, when handling this access pattern, we need to perform two operations:

    1. Add a Review item to the table if the reviewing user has not already reviewed this restaurant.
    2. Increment the relevant rating attribute on the parent Restaurant item.

    Note that both of these operations must succeed or fail together. If a user has already reviewed this restaurant, you do not want to increment the rating attribute on the parent Restaurant. Additionally, if the write to increment the rating attribute fails, you don't want to store the Review item because it will result in incorrect data.

    To handle this, you can use DynamoDB transactions. Transactions allow you to combine multiple operations in a single request and have the entire request succeed or fail together. This can greatly simplify complex workflows that operate on multiple items.

    In the applications/ directory, there is a create_review.py file. Open the file in your file editor. The contents should look as follows.

    import datetime
    
    import boto3
    from ksuid import ksuid
    
    dynamodb = boto3.client("dynamodb")
    
    RATINGS = {
        1: "one_stars",
        2: "two_stars",
        3: "three_stars",
        4: "four_stars",
        5: "five_stars",
    }
    
    
    def create_review(restaurant, username, rating, review_text):
        review_id = str(ksuid())
        rating_attr = RATINGS[rating]
        try:
            resp = dynamodb.transact_write_items(
                TransactItems=[
                    {
                        "Put": {
                            "TableName": "Restaurants",
                            "Item": {
                                "PK": {"S": "USER#{}".format(username)},
                                "SK": {"S": "REST#{}".format(restaurant)},
                                "GSI1PK": {"S": "REST#{}".format(restaurant)},
                                "GSI1SK": {"S": "#REVIEW#{}".format(review_id)},
                                "username": {"S": username},
                                "restaurant": {"S": restaurant},
                                "id": {"S": review_id},
                                "rating": {"N": str(rating)},
                                "review": {"S": review_text},
                                "created_at": {"S": datetime.datetime.now().isoformat()},
                            },
                            "ConditionExpression": "attribute_not_exists(PK)",
                        },
                    },
                    {
                        "Update": {
                            "TableName": "Restaurants",
                            "Key": {
                                "PK": {"S": "REST#{}".format(restaurant)},
                                "SK": {"S": "REST#{}".format(restaurant)},
                            },
                            "ConditionExpression": "attribute_exists(PK)",
                            "UpdateExpression": "SET #rating = if_not_exists(#rating, :zero) + :inc",
                            "ExpressionAttributeNames": {"#rating": rating_attr},
                            "ExpressionAttributeValues": {
                                ":inc": {"N": "1"},
                                ":zero": {"N": "0"},
                            },
                        }
                    },
                ]
            )
            print("Added review from {} to restaurant {}".format(username, restaurant))
            return True
        except Exception as e:
            print("Could not add review to restaurant")
    
    
    create_review("Bev's Bistro", "hungryhank", 5, "Great food, great price!")

    The structure here is similar to the previous example. After importing needed libraries, there is a create_review function that is similar to what you have in the application code. In the function, it calls the TransactWriteItems operation in DynamoDB. The transaction includes two operations:

    1. A PutItem operation to create the Review item with a condition that an item with the same primary key does not exist.
    2. An UpdateItem operation to increment the relevant rating attribute on the parent Restaurant item.

    At the bottom of the file, there is a statement to test the create_review function by using some sample data. Run the following command in your terminal to execute the create_review.py script and create a review.

    python application/create_review.py

    You should see a message indicating that a review was added to Bev's Bistro from user hungryhank.

    Try executing the script again. This time, you should get an error message that the review could not be added. This is because the user hungryhank has already added a review to Bev's Bistro.

    Fetching the restaurant summary

    The final access pattern we're going to cover is the Fetch Restaurant Summary access pattern.

    When a user navigates to a restaurant's page in the application, we want to show details about the restaurant itself, as well as a summary of all ratings received and the text of the five most recent reviews.

    In the Create Rating access pattern, you saw how we’re denormalizing data and copying the ratings information onto the Restaurant item to quickly calculate a summary. Now let’s see how to retrieve the Restaurant item and the five most recent Review items for the restaurant in a single request.

    The Query operation allows you to read multiple items with the same partition key. Within a single partition key, items are ordered according to the sort-key value.

    Recall that we modeled a Restaurant item with some Review items in NoSQL Workbench. The view for the GSI1 secondary index is as follows.

    (click to zoom)

    Notice that all the Review items come first in the item collection, followed by the Restaurant item. Further, the Review items are ordered according to their ReviewId. Because you’re using a KSUID, the Reviews are ordered chronologically. Accordingly, to get the Restaurant and the five most recent Reviews, you need to start at the end of the item collection and retrieve six items.

    In the applications/ directory, there is a script called fetch_restaurant_summary.py Open the file to view it in your file editor. The contents should look as follows.

    import boto3
    
    from entities import Restaurant, Review
    
    dynamodb = boto3.client("dynamodb")
    
    
    def fetch_restaurant_summary(restaurant_name):
        resp = dynamodb.query(
            TableName="Restaurants",
            IndexName="GSI1",
            KeyConditionExpression="GSI1PK = :gsi1pk",
            ExpressionAttributeValues={
                ":gsi1pk": {"S": "REST#{}".format(restaurant_name)},
            },
            ScanIndexForward=False,
            Limit=6,
        )
    
        restaurant = Restaurant(resp["Items"][0])
        restaurant.reviews = [Review(item) for item in resp["Items"][1:]]
    
        return restaurant
    
    
    restaurant = fetch_restaurant_summary("The Vineyard")
    
    print(restaurant)
    for review in restaurant.reviews:
        print(review)

    The setup is similar to the previous two examples. After importing the required libraries, there is a fetch_restaurant_summary function that is similar to a function in the application. This function takes a restaurant name and returns the Restaurant item and the five most recent Review items for the restaurant.

    The function uses the Query operation to do this. When using a Query with DynamoDB, you must specify a key condition expression that describes the items you want to retrieve. A key condition expression must contain an exact match for the partition key you want and may contain conditions on the sort keys you want to match.

    In this Query operation, you run the operation against the GSI1 secondary index. You specify the exact partition key that holds the data for a specific restaurant. Additionally, you specify a property of ScanIndexForward=False. This indicates that you want to read items from your item collection in reverse order. This starts at the end of the item collection—which is your Restaurant item—and then reads the most recent Review items in the collection. Finally, there is a limit of six items so that you only retrieve the Restaurant item and the five most recent Review items.

    At the bottom of the file is an example of calling the function with a restaurant name of The Vineyard. Run the following command in your terminal to execute the script.

    python application/fetch_restaurant_summary.py

    You should see the following output in your terminal.

    $ python application/fetch_restaurant_summary.py
    Restaurant<The Vineyard -- Fine Dining>
    Review<The Vineyard -- markmartin (2020-05-24T11:59:44)>
    Review<The Vineyard -- kgraham (2020-05-14T15:01:52)>
    Review<The Vineyard -- ewilliams (2020-05-13T02:36:30)>
    Review<The Vineyard -- hannah21 (2020-05-04T03:44:26)>
    Review<The Vineyard -- john97 (2020-04-27T20:45:52)>

    The output printed out the Restaurant item and the five most recent Review items for The Vineyard.


    In this module, you learned how to implement some access patterns in the application code. First, you saw how to use condition expressions to ensure you don't overwrite existing items. Then you used DynamoDB transactions to operate on multiple items in a single request. Finally, you used the Query operation to retrieve multiple, heterogeneous item types in a single request.

    In the next module, you see how to clean up the resources you've created in this lesson.

  • In this lesson, you created an DynamoDB table to use as the primary database for a restaurant ratings service in your application. DynamoDB is a great fit for most OLTP applications, and it is especially popular with high-scale applications and serverless applications. DynamoDB offers fast, consistent performance as it scales as well as a flexible billing model and a hands-off operational model.

    In this module, you clean up the resources you created in this lesson to avoid incurring additional charges.


    First, delete your DynamoDB table. In the scripts/ directory, there is a script called delete_table.py. This contains a Python script that deletes your table by using Boto3.

    To execute the script and delete your table, run the following command in your terminal.

    python scripts/delete_table.py

    You should see output in your terminal indicating that the table was deleted successfully.

    Additionally, you need to delete your AWS Cloud9 development environment. To do so, navigate to the AWS Cloud9 console. Choose the environment you created for this lesson, and choose Delete.

    (click to zoom)

    In this module, you learned how to clean up the DynamoDB table and the AWS Cloud9 environment that you created in this lesson.

In this lesson, you saw how to prepare for data modeling with DynamoDB, how to plan your data model, and how to implement your data model in your application code. You should feel confident in your ability to use DynamoDB in your application.

Was this page helpful?