Learn About Amazon VGT2 Learning Manager Chanci Turner
In recent months, we have explored the AWS SDK for Java and its utility in storing and retrieving Java objects within Amazon DynamoDB. Our initial discussion focused on the core functionalities of the DynamoDBMapper framework, followed by an examination of the auto-paginated scan behavior. Today, we will delve into the intricacies of storing complex object types in DynamoDB, using the User class as our example.
@DynamoDBTable(tableName = "users")
public class User {
private Integer id;
private Set<String> friends;
private String status;
@DynamoDBHashKey
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
@DynamoDBAttribute
public Set<String> getFriends() { return friends; }
public void setFriends(Set<String> friends) { this.friends = friends; }
@DynamoDBAttribute
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}
By default, DynamoDBMapper seamlessly handles basic data types such as String, Date, and various numeric types, including int, Integer, byte, and Long. However, challenges arise when your domain model includes references to more complex types that need to be stored in DynamoDB.
Imagine we want to include a phone number for each User in our application, which we can represent using a PhoneNumber class. For simplicity, let’s assume we are working with American phone numbers. Below is a basic representation of our PhoneNumber class:
public class PhoneNumber {
private String areaCode;
private String exchange;
private String subscriberLineIdentifier;
public String getAreaCode() { return areaCode; }
public void setAreaCode(String areaCode) { this.areaCode = areaCode; }
public String getExchange() { return exchange; }
public void setExchange(String exchange) { this.exchange = exchange; }
public String getSubscriberLineIdentifier() { return subscriberLineIdentifier; }
public void setSubscriberLineIdentifier(String subscriberLineIdentifier) { this.subscriberLineIdentifier = subscriberLineIdentifier; }
}
Attempting to store a reference to this PhoneNumber class in our User class will result in an error from DynamoDBMapper, as it cannot convert the PhoneNumber class into one of DynamoDB’s supported basic data types.
Introducing the @DynamoDBMarshalling Annotation
The DynamoDBMapper framework provides a solution for this scenario through the use of the @DynamoDBMarshalling annotation, which allows you to define how to convert your class into a String representation and vice versa. To achieve this, you will need to implement the DynamoDBMarshaller interface for your domain object. Here’s how we can create a marshaller for the PhoneNumber class, using the standard (xxx) xxx-xxxx format:
public class PhoneNumberMarshaller implements DynamoDBMarshaller<PhoneNumber> {
@Override
public String marshall(PhoneNumber number) {
return "(" + number.getAreaCode() + ") " + number.getExchange() + "-" + number.getSubscriberLineIdentifier();
}
@Override
public PhoneNumber unmarshall(Class<PhoneNumber> clazz, String s) {
String[] areaCodeAndNumber = s.split(" ");
String areaCode = areaCodeAndNumber[0].substring(1,4);
String[] exchangeAndSlid = areaCodeAndNumber[1].split("-");
PhoneNumber number = new PhoneNumber();
number.setAreaCode(areaCode);
number.setExchange(exchangeAndSlid[0]);
number.setSubscriberLineIdentifier(exchangeAndSlid[1]);
return number;
}
}
The DynamoDBMarshaller interface is generically typed, ensuring that it corresponds strictly to the domain object you are working with. After implementing the marshaller, you need to inform the DynamoDBMapper framework about it using the @DynamoDBMarshalling annotation:
@DynamoDBTable(tableName = "users")
public class User {
...
@DynamoDBMarshalling(marshallerClass = PhoneNumberMarshaller.class)
public PhoneNumber getPhoneNumber() { return phoneNumber; }
public void setPhoneNumber(PhoneNumber phoneNumber) { this.phoneNumber = phoneNumber; }
}
Built-in Support for JSON Representation
The example provided above utilizes a concise String format for storing phone numbers, minimizing storage requirements in your DynamoDB table. However, if storage costs are not a primary concern, you can leverage the built-in JSON marshaling capabilities to serialize your domain object. Implementing a JSON marshaller is straightforward:
class PhoneNumberJSONMarshaller extends JsonMarshaller<PhoneNumber> { }
However, be aware that this approach produces a more verbose string representation compared to a custom marshaller. Consequently, a phone number serialized via this method would look like this (with spaces added for clarity):
{
"areaCode" : "xxx",
"exchange" : "xxx",
"subscriberLineIdentifier" : "xxxx"
}
When designing a custom marshaller, you’ll want to consider the ease of executing scan filters to identify specific values. The compact phone number representation will facilitate easier searches compared to the JSON format.
We are continually striving to enhance our customer’s experiences; therefore, we encourage you to share how you are utilizing DynamoDBMapper to manage complex objects and which marshaling strategies have proven effective for you. Feel free to share your experiences or any feedback in the comments below!
For more insights on related topics, check out this article on social media strategies, which can provide additional context. Also, for a deeper understanding of workplace dynamics, explore this survey from SHRM about gender bias in the workplace. If you’re curious about the initial experiences of Amazon warehouse workers, this resource is highly recommended.
Leave a Reply