Module 2: DynamoDB Annotations and Load
Module Overview
Explore DynamoDB annotations and learn how to load data from DynamoDB tables using the AWS SDK for Java Enhanced Client.
Learning Objectives
- Implement and annotate a Java POJO that represents any item from a provided DynamoDB table
- Implement and annotate a Java POJO that represents any item from a provided DynamoDB table, where the names of some of its fields differ from the corresponding names of attributes of the DynamoDB table
- Implement functionality that retrieves a unique item by partition key from a provided DynamoDB table
- Implement functionality that retrieves a unique item by partition and sort key from a provided DynamoDB table
- Implement functionality that creates an item in a provided DynamoDB table
- Implement functionality that modifies an existing item in a provided DynamoDB table
- Understand DynamoDB annotations and their purpose in Java applications
- Learn how to use @DynamoDbBean, @DynamoDbPartitionKey, and @DynamoDbSortKey annotations
- Master the techniques for loading data from DynamoDB tables
- Implement queries and scans to retrieve data efficiently
- Apply best practices for modeling data in DynamoDB
DynamoDB Annotations
Working with DynamoDB Annotations
The AWS SDK for Java provides annotations that make it easier to map Java classes to DynamoDB tables. These annotations are part of the DynamoDB Enhanced Client, which provides a high-level, object-oriented interface for DynamoDB operations.
Key Annotations
- @DynamoDbBean - Marks a class as a DynamoDB item. Applied at the class level.
- @DynamoDbPartitionKey - Marks a field as the partition key for the table. Applied to a getter method.
- @DynamoDbSortKey - Marks a field as the sort key for the table. Applied to a getter method.
- @DynamoDbAttribute - Maps a Java field to a differently named DynamoDB attribute. Applied to a getter method.
- @DynamoDbIgnore - Excludes a field from being persisted to DynamoDB. Applied to a getter method.
Here's an example of a Java POJO mapped to a DynamoDB table:
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute;
@DynamoDbBean
public class MusicItem {
private String artist;
private String songTitle;
private String albumTitle;
private int yearReleased;
private Set<String> genres;
@DynamoDbPartitionKey
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
@DynamoDbSortKey
public String getSongTitle() {
return songTitle;
}
public void setSongTitle(String songTitle) {
this.songTitle = songTitle;
}
@DynamoDbAttribute("album_title") // Maps to different attribute name in DynamoDB
public String getAlbumTitle() {
return albumTitle;
}
// Other getters and setters...
}
Notes on the annotations:
- The class must be annotated with
@DynamoDbBean
- The class must have getters and setters for all properties
- The partition key is marked with
@DynamoDbPartitionKey
- If the table has a sort key, it's marked with
@DynamoDbSortKey
- For attribute names that differ between Java and DynamoDB, use
@DynamoDbAttribute
with the DynamoDB name
These annotations simplify working with DynamoDB by automatically handling the conversion between Java objects and DynamoDB items.
Mapping DynamoDB Items to Java Objects
In a previous lesson, you learned about databases, their uses, and one of Amazon's database systems, DynamoDB. In this lesson, you will learn about programmatically retrieving items from a DynamoDB table and mapping them to Java objects.
Mapping DynamoDB items to Java
In the next reading, we will learn how to load a DynamoDB item into Java, but first you need to know how to set up the class that will map the item to a Java object. We will create a POJO (Plain Old Java Object -- a class of just member variables with getters and setter, no complex logic) for each DynamoDB table we want to interact with programmatically. Creating a POJO helps us map items from our table to the Java class, which allows us to use Java to create, delete, and update items in our table. An instance of that POJO will represent a single item in the table and the attributes of the item are the fields of the POJO.
To illustrate this, we're going to use the Songs table from the previous lesson with one additional attribute, isFavorited, a Boolean value that is true if a song has been favorited on your playlist and false if it hasn't been favorited.
artist | song_title | genre | year | isFavorited |
---|---|---|---|---|
Black Eyed Peas | I Gotta Feeling | pop | 2009 | false |
Linkin Park | Numb | rock | 2003 | true |
Black Eyed Peas | Pump It | pop | 2005 | true |
Missy Elliot | Work It | rap | 2002 | true |
Daddy Yankee | Gasolina | latin pop | 2004 | true |
We'll start by creating a class, Song, with fields for each attribute in the table.
public class Song {
private String artist;
private String songTitle;
private String genre;
private Integer year;
private Boolean isFavorited;
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getSongTitle() {
return songTitle;
}
public void setSongTitle(String songTitle) {
this.songTitle = songTitle;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
public Integer getYear() {
return year;
}
public void setYear(Integer year) {
this.year = year;
}
public Boolean isFavorited() {
return isFavorited;
}
public void setFavorited(Boolean isFavorited) {
this.isFavorited = isFavorited;
}
}
The structure of this class should look familiar to you. There's a field for each attribute in the Song table and each field has an appropriate Java data type. Getters and setters exist for each field. You may have noticed something slightly unusual about our Song POJO. Usually, we would use the primitives int and boolean. When we are writing POJOs to be used with DynamoDB, we want to always use the wrapper classes. Since DynamoDB allows us to have items without every attribute specified, wrapper classes can represent that as null for that field. If we use primitive types and an attribute isn't specified for that item, the field will have the primitive's default value. For our Song example above, if isFavorited is a primitive boolean and is false, is it because the song wasn't favorited or because the attribute wasn't specified? By using the wrapper class Boolean, we know the only time the attribute was unspecified is if the value is null.
This is what our Song class looks like when we initially create it in Java. Now we can add in the necessary annotations to map a DynamoDB item to the class.
You can think of the DynamoDB annotations like translators---they translate our DynamoDB items to the Java class and enable us to interact with DynamoDB in Java code. Without annotations, our DynamoDB items wouldn't know how to properly map to the Java class. The annotations specify which Java fields you are expecting to map to each attribute, as well as information about the table being referenced, like the table name and which attributes are the partition and sort keys.
This code will map our Songs table to Java class Song using DynamoDB annotations. The annotations begin with the @ symbol and indicate which attribute maps to which field:
@DynamoDBTable(tableName = "Songs")
public class Song {
private String artist;
private String songTitle;
private String genre;
private Integer year;
private Boolean isFavorited;
@DynamoDBHashKey(attributeName = "artist")
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
@DynamoDBRangeKey(attributeName = "song_title")
public String getSongTitle() {
return songTitle;
}
public void setSongTitle(String songTitle) {
this.songTitle = songTitle;
}
@DynamoDBAttribute(attributeName = "genre")
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
@DynamoDBAttribute(attributeName = "year")
public Integer getYear() {
return year;
}
public void setYear(Integer year) {
this.year = year;
}
@DynamoDBAttribute(attributeName = "isFavorited")
public Boolean isFavorited() {
return isFavorited;
}
public void setFavorited(Boolean isFavorited) {
this.isFavorited = isFavorited;
}
}
Let's look at each annotation separately:
DynamoDBTable
@DynamoDBTable(tableName = "Songs")
public class Song {
The @DynamoDBTable annotation annotates the class declaration that appears directly below (or to the right of) the annotation. When using an annotation, it's important to make sure that the next line of code is the value that you want to annotate. The DynamoDB annotation has a parameter, tableName. The parameter name is specified by DynamoDB (you can read more in the DynamoDBTable Javadoc). The value for tableName should be whatever the name of your table is in DynamoDB. This is how Java knows which table to access. The @DynamoDBTable annotation is required on any class used to represent DynamoDB items.
Our tableName is Songs and our class name is Song. It's a typical naming convention to have the name of your database table be plural (like Songs) and the class be singular (like Song) since the table holds multiple songs, while the class represents an individual instance of the song.
DynamoDBHashKey
@DynamoDBHashKey(attributeName = "artist")
public String getArtist() {
return artist;
}
The @DynamoDBHashKey annotation annotates the variable used as the partition key, so should appear just above the getter for the partition key. Although in DynamoDB we refer to it as a partition key, the Java annotation uses a hash key instead. Both terms are used by DynamoDB interchangeably to refer to the same attribute. We'll typically use the term partition key, as most of the documentation uses this term (the code still uses 'hash' and 'range' in a few places, likely because of the difficulty in changing classes already in use by developers around the world!). Instead of above the getter, this annotation could appear above the declared variable. You may see either on teams, but our practice will be to annotate the getters. This applies not just to the @DynamoDBHashKey annotation, but to all DynamoDB attribute annotations.
Similar to the @DynamoDBTable annotation, this annotation has a parameter, attributeName, which is a name specified by DynamoDB (you can read more in the DynamoDBHashKey Javadoc). The value for attributeName should be whatever the name of the attribute is in DynamoDB. This parameter is optional if the attribute name exactly matches the name of your Java field (including capitalization and any punctuation/special characters). Since all DynamoDB tables have a partition key, the @DyanmoDBHashKey annotation is required.
The annotation above maps the table's attributeName artist to the Java field artist and declares this field as the partition key. Since both the name of the attribute in the DynamoDB table and the Java field are artist we could choose to remove the attributeName parameter and write the annotation like this:
@DynamoDBHashKey
public String getArtist() {
return artist;
}
Even though the attributeName is optional and you will see teams omit it, our practice will be to always include attributeName so that our code is more explicit. This applies not just to the @DynamoDBHashKey annotation, but to all DynamoDB attribute annotations.
DynamoDBRangeKey
@DynamoDBRangeKey(attributeName = "song_title")
public String getSongTitle() {
return songTitle;
}
The @DynamoDBRangeKey annotation annotates the variable used as the sort key, so should appear just above the getter for the sort key. Although in DynamoDB we refer to it as a sort key, the Java annotation uses a range key instead. Both terms are used by DynamoDB interchangeably to refer to the same attribute. We'll typically use the term sort key.
This annotation is very similar to the @DynamoDBHashKey annotation, except it maps the sort key instead of the partition key (you can read more in the DynamoDBRangeKey Javadoc). Like the @DynamoDBHashKey annotation, the parameter attributeName is optional if the attribute name exactly matches the name of your Java field. Since the attribute name from the DynamoDB table in this example is song_title while the name of the Java variable is songTitle, in this case, the parameter is necessary. Without the attributeName parameter, our code would not know to map the song_title attribute to the songTitle field. The @DynamoDBRangeKey annotation is required for tables that have a sort key, but otherwise should not be used.
DynamoDBAttribute
@DynamoDBAttribute(attributeName = "genre")
public String getGenre() {
return genre;
}
To explicitly map other attributes in the table that aren't a key, we use @DynamoDBAttribute with the parameter attributeName. This attribute appears right before the getter for the appropriate Java field, just like we saw with the @DynamoDBHashKey and @DynamoDBRangeKey annotations. The @DynamoDBAttribute annotation is optional and is only required when the attribute names don't exactly match the Java names. In the above example Song class, we explicitly named all of the Java variables so you could see how to use @DynamoDBAttribute. While @DynamoDBAttribute can be optional, it will be our practice to always use it so that our code is more explicit. When you go to other teams, you may not see the optional annotations and parameters. It depends on team standards whether to make the naming explicit.
The following code snippet is our Song class without the optional annotations/parameters:
@DynamoDBTable(tableName = "Songs")
public class Song {
private String artist;
private String songTitle;
private String genre;
private Integer year;
private Boolean isFavorited;
@DynamoDBHashKey
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
@DynamoDBRangeKey(attributeName = "song_title")
public String getSongTitle() {
return songTitle;
}
public void setSongTitle(String songTitle) {
this.songTitle = songTitle;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
public Integer getYear() {
return year;
}
public void setYear(Integer year) {
this.year = year;
}
public Boolean isFavorited() {
return isFavorited;
}
public void setFavorited(Boolean isFavorited) {
this.isFavorited = isFavorited;
}
}
Moving forward, we will always include the annotations and attributeName fields even when they are not required.
DynamoDB Loading
In the previous reading, we went over how to map a DynamoDB item to a Java class using annotations so that we can create, delete, and update items in a DynamoDB table. Now that we know how to set our class up, we'll talk about how to actually get an item from DynamoDB into Java.
DynamoDbClientProvider Class
To start using our DynamoDB items in Java, we first need to create a connection between our Java code and our AWS account. We will make that connection using an AmazonDynamoDBClient class, which implements the interface AmazonDynamoDB. You will often see this client referred to polymorphically using the AmazonDyanmoDB interface. If you think back to the lesson on HTTP and Networking, a client is a software that accesses a service made available by a server. In this case, the DynamoDBClient class sets itself up as the client for accessing DynamoDB, which is the service. Once this connection is set up, our class can retrieve data, such as an item, from our DynamoDB tables. Without this connection, our Java program won't have any way to access DynamoDB or any of the data we have stored there. There is some configuration required to connect to our AWS account and create the client. We have written a class, com.amazon.ata.aws.dynamodb.DynamoDbClientProvider (available from the ATAResources package) that is available to you in your Snippets package and the project to easily provide a configured AmazonDynamoDBClient class.
You can get an AmazonDynamoDB class by adding this line to your code:
AmazonDynamoDB dynamoDBClient = DynamoDbClientProvider.getDynamoDBClient();
While you are not responsible for the DynamoDbClientProvider class, let's take a quick look at it to better understand what is happening for you:
public class DynamoDbClientProvider {
/**
* Returns DynamoDB client using default region.
* @return AmazonDynamoDB
*/
public static AmazonDynamoDB getDynamoDBClient() {
return getDynamoDBClient(Regions.US_WEST_2);
}
/**
* Returns DynamoDB client using provided region.
* @param region will be used as the region for the DynamoDB client
* @return AmazonDynamoDB
*/
public static AmazonDynamoDB getDynamoDBClient(Regions region) {
if (null == region) {
throw new IllegalArgumentException("region cannot be null");
}
return AmazonDynamoDBClientBuilder
.standard()
// this will use multiple providers to look for AWS credentials
.withCredentials(DefaultAWSCredentialsProviderChain.getInstance())
// This should be the same region the CloudFormation stack with tables was deployed in
.withRegion(region)
.build();
}
}
In the DynamoDbClientProvider we are configuring the client with two items: credentials and a region.
The credentials are how we tell our AWS account who we are. Our AWS account then decides if the person with these credentials is allowed to access our account and our DynamoDB tables. How it does that starts to get into the world of Authentication and Authorization, which we will cover at a later point. The DefaultAWSCredentialsProviderChain has a chain of places (a list of places in order of preference) to look to try to get our credentials from.
The region tells the client which data center our tables have been created in. Here US_WEST_2 is a data center located in Oregon. We want our tables to be as physically close to us as possible so we can access them faster. It takes our data much longer to travel all the way across the country from a data center in Virginia to Seattle than from Oregon.
Other than those two items we are using the default configurations, which are provided by calling .standard().
For now, you just need to know that the DynamoDbClientProvider exists, that its purpose is to provide a connection to DynamoDB, and how to use it to get information from DynamoDB (which we'll get to in the next section!)
DynamoDBMapper Class
Another necessary class is the DynamoDBMapper class, which is part of the Amazon AWS library. The DynamoDBMapper class has several methods which are used to map data from a database into our class. We can use DynamoDBMapper methods to load data from a table into Java, save data from Java back into a table, delete an item in our table, query a table for matching items, and more! To instantiate the DynamoDBMapper, we must provide it with an AmazonDynamoDB. This allows our mapper to use our connection to DynamoDB to perform all of these actions. Here's an example of instantiating the DynamoDBMapper:
AmazonDynamoDB dynamoDBClient = DynamoDbClientProvider.getDynamoDBClient();
DynamoDBMapper mapper = new DynamoDBMapper(dynamoDBClient);
If you're interested, more information about the DynamoDBMapper class can be found in the DynamoDBMapper Javadoc.
Loading items with a partition key
Now that we understand a little bit more about the DynamoDbClientProvider and DynamoDBMapper class, we can discuss how to get an item from DynamoDB into Java. We're going to set up an example first, but we'll get back to those two classes soon.
Let's return to our Shoes table from the previous lesson:
shoe_id | cubby_location | color | style | occasion |
---|---|---|---|---|
SN01 | 1 | grey | sneaker | athletic |
BO01 | 2 | grey | boot | work |
SN02 | 3 | black | sneaker | casual |
Given these shoes in our database, we can load an individual item into Java. By loading an item we're taking an item that already exists in our table and mapping it to an instance of our POJO.
Below is our annotated Shoe POJO:
@DynamoDBTable(tableName = "Shoes")
public class Shoe {
private String shoeId;
private Integer cubbyLocation;
private String color;
private String style;
private String occasion;
@DynamoDBHashKey(attributeName = "shoeId")
public String getShoeId() {
return shoeId;
}
public void setShoeId(String shoeId) {
this.shoeId = shoeId;
}
@DynamoDBAttribute(attributeName = "cubbyLocation")
public Integer getCubbyLocation() {
return cubbyLocation;
}
public void setCubbyLocation(Integer cubbyLocation) {
this.cubbyLocation = cubbyLocation;
}
@DynamoDBAttribute(attributeName = "color")
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@DynamoDBAttribute(attributeName = "style")
public String getStyle() {
return style;
}
public void setStyle(String style) {
this.style = style;
}
@DynamoDBAttribute(attributeName = "occasion")
public String getOccasion() {
return occasion;
}
public void setOccasion(String occasion) {
this.occasion = occasion;
}
}
To load an item from our Shoes table to our Shoe class, we use the load() method of the DynamoDBMapper. When we call the load() method, we provide the unique key for an item and then the method searches the table for the item that matches the key we provided.
Let's take a look at the parameters for the load() method:
public T load(Class clazz, Object hashKey)
The first parameter is of type Class, which in our example will be Shoe.class. We give the load() method a class so that it knows which POJO, and therefore which annotations to use. The annotations, specifically the @DynamoDBTable annotation, tells the load() method which table to search. Since we declared in our Shoe class that tableName = "Shoes", the load() method knows to search the Shoes table for the key we specified, the second parameter to the load method. DynamoDBMapper knows to search the shoeId attribute because we specified it was the partition key using the @DynamoDBHashKey annotation. Because there is no @DynamoDBRangeKey annotation, DynamoDBMapper knows the table uses only a partition key. If load() finds an item, it loads that item's data into a new instance of the class we specify in the first argument. In our example, the attributes of the item found by the load() method will be populated into a new instance of the Shoe class. 'DynamoDBMapper' will use the partition key specified by the @DynamoDBHashKey annotation, and map the attributes according to the @DynamoDBAttribute annotations that we specified. This 'mapping' between a DynamoDB item and a Java object is the reason that the class is called DynamoDBMapper. If the method does not find an item, it returns a null value, which we will discuss in more detail in a later lesson.
The second parameter is the primary key for the class, which in this case is a partition key. The key value that we give in this parameter is the item that we are searching our Shoes table for. Our Shoes table only uses a partition key, because shoe_id is always a unique identifier, so we only need to give the partition key in order to load an item. If the table uses a partition + sort key as the primary key, you must specify both keys, but we'll get to that in the next section. Note: In the last reading about annotations we pointed out that the DynamoDBMapper annotations use Hash instead of Partition and Range instead of Sort. Similarly, the arguments to load() are named hashKey and rangeKey.
Loading a Shoe
shoe_id | cubby_location | color | style | occasion |
---|---|---|---|---|
SN01 | 1 | grey | sneaker | athletic |
BO01 | 2 | grey | boot | work |
SN02 | 3 | black | sneaker | casual |
Going back to our Shoes table, let's look at how we would load the first shoe:
DynamoDBMapper mapper = new DynamoDBMapper(DynamoDbClientProvider.getDynamoDBClient());
Shoe shoe01 = mapper.load(Shoe.class, "SN01");
The first line of code creates a new instance of the DynamoDBMapper class by providing an instance of an AmazonDynamoDB, which is defined in the DynamoDbClientProvider class. As we discussed in the last section, the DynamoDbClientProvider class creates the connection between our program and DynamoDB where our table lives. When we pass in the client, we're setting up a connection for mapper, an instance of DynamoDBMapper, to access the correct DynamoDB.
The second line of code creates a new instance of the Shoe class, shoe01, by calling the load() method using the Shoe class and the partition key SN01. As you can see in the Shoes table, SN01 is the partition key for an item in the table. Our load() method searches through the Shoes table to find the item with a matching partition key, which is the first shoe in the table, and then maps that item to shoe01, a new instance of our Shoe class. We have successfully loaded the first shoe!
Loading items with a partition and sort key
As we discussed in the previous lesson, there are situations where it is better to use a composite primary key with both a partition and sort key as the unique identifier. Think back to our Songs table, which used artist as the partition key and song_title as the sort key. For tables with a composite primary key, we need to specify both the partition and sort key in the load() method. This is because loading is about getting a single entry that is uniquely identifiable by key, and for tables with a composite primary key, the partition and sort key pairing is the unique key. Fortunately, there is a load method that can load an item based on both the partition and sort key.
Let's load the first song from our Songs table:
artist | song_title | genre | year |
---|---|---|---|
Black Eyed Peas | I Gotta Feeling | pop | 2009 |
Linkin Park | Numb | rock | 2003 |
Black Eyed Peas | Pump It | pop | 2005 |
Missy Elliot | Work It | rap | 2002 |
Daddy Yankee | Gasolina | latin pop | 2004 |
DynamoDBMapper mapper = new DynamoDBMapper(DynamoDbClientProvider.getDynamoDBClient());
Song song01 = mapper.load(Song.class, "Black Eyed Peas", "I Gotta Feeling");
Looking at this code snippet, we can see that it looks similar to the way we loaded a Shoe in the previous section. First, we create a new instance of DynamoDBMapper, just like we did in the previous example. Then, we call load with the Song class, the artist, and the song_title to get a new instance of the Song class populated with the attribute values for the corresponding item from our Songs table. The only difference from the previous example is that this time we are specifying both the partition and the sort key. As seen in our table above, the partition key for our first song is "Black Eyed Peas" and the sort key is "I Gotta Feeling." Once that item is found by matching the partition and sort keys to an item in the table, it will be loaded into a new Song instance, song01.
It's important to remember that the load() method is used to get a singular item from a table using a unique key, so if a table has a composite primary key, you must use the unique key pairing of partition and sort key. There are ways to retrieve multiple values from a table, but we will discuss that in a later unit!
Practice
Guided Project
Mastery Task 1: Finish What We Started
Milestone 1: Create UML Class Diagrams
For this task, you will be creating class diagrams referenced in the Design Document in your project for the known classes in the service.
Create class diagrams of the following classes and how they relate to each other:
- Activity classes
- DAO classes
- Exception classes
- DynamoDB model classes
Requirements
- Indicate inheritance relationships among classes, including inheriting/implementing superclasses/supertypes that are from outside of your project package (but for those superclasses/types, you don't need to include any of their methods or variables)
- The diagrams do not need to include the App class or any classes in the following packages:
- com.amazon.ata.music.playlist.service.lambda
- com.amazon.ata.music.playlist.service.dependency
- com.amazon.ata.music.playlist.service.converters
- Your DynamoDB model classes must represent the end-state of your models (they start off incomplete). See the "Data Model" section of the Design Document to find the relevant fields.
- The DynamoDB model classes in the diagram must indicate which fields will represent the partition and (if any) sort key.
- Use the @DynamoDBHashKey and @DynamoDBRangeKey annotations to mark the relevant fields
- You do not need to provide any other annotations, but be sure to indicate the Java type for each member variable. Use this same syntax for all of your DynamoDB model classes in your class diagrams.
- With the DynamoDB model classes, show the relationship between the Plain Old Java Objects (POJOs) both as a member variable and as a line between the classes.
Recommendations/tips
- Here is the PlantUML reference for class diagrams
- If your diagram is too large, you may split it into sub-diagrams using the newpage keyword
- IntelliJ can display PlantUML diagrams as you type if you install the PlantUML integration plugin
- For Mac users: If you get an error in the plug-in complaining about GraphViz, then run these on the command line, then close and reopen IntelliJ:
brew install libtool brew install graphviz
Milestone 2: Complete GetPlaylistActivity's implementation, implement CreatePlaylistActivity
Complete GetPlaylist
Update the dynamodb.models.Playlist class with the missing fields indicated in your design from milestone 1. Be sure to also add the correct DynamoDB annotations for the key indicated in your design of the data models.
Update the ModelConverter's toPlaylistModel method to map the new fields from the Playlist object to the PlaylistModel object. Then add or update unit tests as appropriate.
Implement CreatePlaylist
With the updated Playlist class, implement CreatePlaylistActivity's handleRequest method based on the design document's implementation notes and add unit tests to cover your new code. You will have to add a new savePlaylist method in the PlaylistDao class.
Create Lambda Functions
Upon completion, upload your code to your CreatePlaylistActivty Lambda. Try out some requests with different names, with and without prohibited characters and with and without tags. Ensure the creation of your playlists in your dynamoDB table.
Hint
In case you run into "not authorized to perform..." error when running your lambda function:
- Go to lambda function -> Configuration tab -> Edit (top right corner)
- Click on "View the xxx role" (at the very bottom of the page)
- In the IAM Console, go to Add permission (dropdown) -> Attach Policies
- Search for AmazonDynamoDBFullAccess and click on Attach Policies
You may still see a message "The deployment package of your Lambda function xxx is too large to enable inline code editing. However, you can still invoke your function." and that's okay.
Completion Checklist
- You've finished GetPlaylist's and implemented CreatePlaylist's functionality
- You've completed the requested UML class diagrams
- Mastery Task 1 TCTs are passing in your pipeline
- Your Lambda functions successfully get and create playlists