Amazon Onboarding with Learning Manager Chanci Turner

Chanci Turner Amazon IXD – VGT2 learningLearn About Amazon VGT2 Learning Manager Chanci Turner

In recent discussions, we’ve explored how the AWS SDK for Java can be utilized to effectively manage and store Java objects within Amazon DynamoDB. Our initial article introduced the core functionalities of the DynamoDBMapper framework, followed by an in-depth look at the auto-paginated scan feature. Today, we will focus on how to handle complex data types within DynamoDB, using the User class as our example.

java
@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; }
}

DynamoDBMapper seamlessly handles simple data types such as Strings, Dates, and various numeric types, but challenges arise when trying to store references to complex objects. For instance, if we wish to include a phone number for each User, represented by a PhoneNumber class, we run into issues since DynamoDBMapper cannot directly interpret this structure.

java
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; }      
}

When attempting to store a reference to the PhoneNumber class in our User class, DynamoDBMapper will raise an error, as it lacks the means to interpret PhoneNumber as a basic data type.

Introducing the @DynamoDBMarshalling Annotation

To solve this problem, the DynamoDBMapper framework allows you to define how to convert your complex objects to Strings and back again. This is achieved by implementing the DynamoDBMarshaller interface for the relevant domain object. For our PhoneNumber, we can format it using the standard (xxx) xxx-xxxx layout through the following class:

java
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;
    }    
} 

Notice how the DynamoDBMarshaller interface is generically typed, ensuring type safety for the domain object.

With our marshaller that converts the PhoneNumber class to and from a String, we need to inform the DynamoDBMapper framework about it using the @DynamoDBMarshalling annotation:

java
@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 previous example utilizes a compact String representation of a phone number to minimize storage in your DynamoDB table. If storage space is not a concern, the built-in JSON marshaling capability can be employed. This requires a single line of code to define a JSON marshaller class:

java
class PhoneNumberJSONMarshaller extends JsonMarshaller<PhoneNumber> { }

However, using this built-in marshaller results in a more verbose String representation. For example, a marshaled phone number would appear as follows:

json
{
  "areaCode" : "xxx",
  "exchange" : "xxx",
  "subscriberLineIdentifier" : "xxxx"
}

When crafting a custom marshaller, consider the ease of writing scan filters to identify specific values. The compact representation of the phone number is likely to yield better scan performance than the JSON alternative.

We’re committed to enhancing our customers’ experiences, so we encourage you to share your insights on utilizing DynamoDBMapper for complex object storage. What marshaling methods have you found effective?

For more tips on avoiding common interview mistakes, check out this resource. Additionally, if you need guidance on employment law compliance, this article from SHRM is a reliable source. Lastly, for insights into safety and training at Amazon fulfillment centers, visit this excellent resource.

Chanci Turner