AWS DevOps & Developer Productivity Blog
Introducing the Enhanced Document API for DynamoDB in the AWS SDK for Java 2.x
We are excited to announce that the AWS SDK for Java 2.x now offers the Enhanced Document API for DynamoDB, providing an enhanced way of working with Amazon DynamoDb items.
This post covers using the Enhanced Document API for DynamoDB with the DynamoDB Enhanced Client. By using the Enhanced Document API, you can create an EnhancedDocument
instance to represent an item with no fixed schema, and then use the DynamoDB Enhanced Client to read and write to DynamoDB.
Furthermore, unlike the Document APIs of aws-sdk-java 1.x, which provided arguments and return types that were not type-safe, the EnhancedDocument provides strongly-typed APIs for working with documents. This interface simplifies the development process and ensures that the data is correctly typed.
Prerequisites:
Before getting started, ensure you are using an up-to-date version of the AWS Java SDK dependency with all the latest released bug-fixes and features. For Enhanced Document API support, you must use version 2.20.33 or later. See our “Set up an Apache Maven project” guide for details on how to manage the AWS Java SDK dependency in your project.
Add dependency for dynamodb-enhanced in pom.xml.
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb-enhanced</artifactId>
<version>2.20.33</version>
</dependency>
Quick walk-through for using Enhanced Document API to interact with DDB
Step 1 : Create a DynamoDB Enhanced Client
Create an instance of the DynamoDbEnhancedClient class, which provides a high-level interface for Amazon DynamoDB that simplifies working with DynamoDB tables.
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
.dynamoDbClient(DynamoDbClient.create())
.build();
Step 2 : Create a DynamoDbTable resource object with Document table schema
To execute commands against a DynamoDB table using the Enhanced Document API, you must associate the table with your Document table schema to create a DynamoDbTable
resource object. The Document table schema builder requires the primary index key and attribute converter providers. Use AttributeConverterProvider.defaultProvider()
to convert document attributes of default types. An optional secondary index key can be added to the builder.
DynamoDbTable<EnhancedDocument> documentTable = enhancedClient.table("my_table",
TableSchema.documentSchemaBuilder()
.addIndexPartitionKey(TableMetadata.primaryIndexName(),"hashKey", AttributeValueType.S)
.addIndexSortKey(TableMetadata.primaryIndexName(), "sortKey", AttributeValueType.N)
.attributeConverterProviders(AttributeConverterProvider.defaultProvider())
.build());
// call documentTable.createTable() if "my_table" does not exist in DynamoDB
Step 3 : Write a DynamoDB item using an EnhancedDocument
The EnhancedDocument
class has static factory methods along with a builder
method to add attributes to a document. The following snippet demonstrates the type safety provided by EnhancedDocument
when you construct a document item.
EnhancedDocument simpleDoc = EnhancedDocument.builder()
.attributeConverterProviders(defaultProvider())
.putString("hashKey", "sampleHash")
.putNull("nullKey")
.putNumber("sortKey", 1.0)
.putBytes("byte", SdkBytes.fromUtf8String("a"))
.putBoolean("booleanKey", true)
.build();
documentTable.putItem(simpleDoc);
Step 4 : Read a Dynamo DB item as an EnhancedDocument
Attributes of the Documents retrieved from a DynamoDB table can be accessed with getter methods
EnhancedDocument docGetItem = documentTable.getItem(r -> r.key(k -> k.partitionValue("samppleHash").sortValue(1)));
docGetItem.getString("hashKey");
docGetItem.isNull("nullKey")
docGetItem.getNumber("sortKey").floatValue();
docGetItem.getBytes("byte");
docGetItem.getBoolean("booleanKey");
AttributeConverterProviders for accessing document attributes as custom objects
You can provide a custom AttributeConverterProvider
instance to an EnhancedDocument
to convert document attributes to a specific object type.
These providers can be set on either DocumentTableSchema
or EnhancedDocument
to read or write attributes as custom objects.
TableSchema.documentSchemaBuilder()
.attributeConverterProviders(CustomClassConverterProvider.create(), defaultProvider())
.build();
// Insert a custom class instance into an EnhancedDocument as attribute 'customMapOfAttribute'.
EnhancedDocument customAttributeDocument =
EnhancedDocument.builder().put("customMapOfAttribute", customClassInstance, CustomClass.class).build();
// Retrieve attribute 'customMapOfAttribute' as CustomClass object.
CustomClass customClassObject = customAttributeDocument.get("customMapOfAttribute", CustomClass.class);
Convert Documents to JSON and vice-versa
The Enhanced Document API allows you to convert a JSON string to an EnhancedDocument
and vice-versa.
// Enhanced document created from JSON string using defaultConverterProviders.
EnhancedDocument documentFromJson = EnhancedDocument.fromJson("{\"key\": \"Value\"}")
// Converting an EnhancedDocument to JSON string "{\"key\": \"Value\"}"
String jsonFromDocument = documentFromJson.toJson();
Define a Custom Attribute Converter Provider
Custom attribute converter providers are implementations of AttributeConverterProvider
that provide converters for custom classes.
Below is an example for a CustomClassForDocumentAPI
which has as a single field stringAttribute
of type String and its corresponding AttributeConverterProvider implementation.
public class CustomClassForDocumentAPI {
private final String stringAttribute;
public CustomClassForDocumentAPI(Builder builder) {
this.stringAttribute = builder.stringAttribute;
}
public static Builder builder() {
return new Builder();
}
public String stringAttribute() {
return stringAttribute;
}
public static final class Builder {
private String stringAttribute;
private Builder() {
}
public Builder stringAttribute(String stringAttribute) {
this.stringAttribute = string;
return this;
}
public CustomClassForDocumentAPI build() {
return new CustomClassForDocumentAPI(this);
}
}
}
import java.util.Map;
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.utils.ImmutableMap;
public class CustomAttributeForDocumentConverterProvider implements AttributeConverterProvider {
private final Map<EnhancedType<?>, AttributeConverter<?>> converterCache = ImmutableMap.of(
EnhancedType.of(CustomClassForDocumentAPI.class), new CustomClassForDocumentAttributeConverter());
// Different types of converters can be added to this map.
public static CustomAttributeForDocumentConverterProvider create() {
return new CustomAttributeForDocumentConverterProvider();
}
@Override
public <T> AttributeConverter<T> converterFor(EnhancedType<T> enhancedType) {
return (AttributeConverter<T>) converterCache.get(enhancedType);
}
}
A custom attribute converter is an implementation of AttributeConverter
that converts a custom classes to and from a map of attribute values, as shown below.
import java.util.LinkedHashMap;
import java.util.Map;
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnhancedAttributeValue;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.StringAttributeConverter;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
public class CustomClassForDocumentAttributeConverter implements AttributeConverter<CustomClassForDocumentAPI> {
public static CustomClassForDocumentAttributeConverter create() {
return new CustomClassForDocumentAttributeConverter();
}
@Override
public AttributeValue transformFrom(CustomClassForDocumentAPI input) {
Map<String, AttributeValue> attributeValueMap = new LinkedHashMap<>();
if(input.string() != null){
attributeValueMap.put("stringAttribute", AttributeValue.fromS(input.string()));
}
return EnhancedAttributeValue.fromMap(attributeValueMap).toAttributeValue();
}
@Override
public CustomClassForDocumentAPI transformTo(AttributeValue input) {
Map<String, AttributeValue> customAttr = input.m();
CustomClassForDocumentAPI.Builder builder = CustomClassForDocumentAPI.builder();
if (customAttr.get("stringAttribute") != null) {
builder.stringAttribute(StringAttributeConverter.create().transformTo(customAttr.get("stringAttribute")));
}
return builder.build();
}
@Override
public EnhancedType<CustomClassForDocumentAPI> type() {
return EnhancedType.of(CustomClassForDocumentAPI.class);
}
@Override
public AttributeValueType attributeValueType() {
return AttributeValueType.M;
}
}
Attribute Converter Provider for EnhancedDocument Builder
When working outside of a DynamoDB table context, make sure to set the attribute converter providers explicitly on the EnhancedDocument
builder. When used within a DynamoDB table context, the table schema’s converter provider will be used automatically for the EnhancedDocument
.
The code snippet below shows how to set an AttributeConverterProvider
using the EnhancedDocument
builder method.
// Enhanced document created from JSON string using custom AttributeConverterProvider.
EnhancedDocument documentFromJson = EnhancedDocument.builder()
.attributeConverterProviders(CustomClassConverterProvider.create())
.json("{\"key\": \"Values\"}")
.build();
CustomClassForDocumentAPI customClass = documentFromJson.get("key", CustomClassForDocumentAPI.class)
Conclusion
In this blog post we showed you how to set up and begin using the Enhanced Document API with the DynamoDB Enhanced Client and standalone with the EnhancedDocument
class. The enhanced client is open-source and resides in the same repository as the AWS SDK for Java 2.0. For more examples related to this visit to our Developer Guide. We hope you’ll find this new feature useful. You can always share your feedback on our GitHub issues page.
About the author: