Recently, when working with AWS DynamoDB, I made another discovery. The Partition Key and Sort Key or any other Index Key, for that matter are always immutable.
What do I mean by that? Let us digress a little and talk about the write operations that are available. They are PutItem and UpdateItem, analogous of Insert and Update. However, due to the very nature of DynamoDB, both of these operations are effectively Upsert.
Consider the below POJO that models a normal DynamoDB table:
import lombok.Data;
import lombok.Getter;
import java.time.LocalDateTime;
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;
@DynamoDbBean
@Data
public class Book {
@Getter(onMethod_ = { @DynamoDbPartitionKey })
private String id;
@Getter(onMethod_ = { @DynamoDbSortKey })
private LocalDateTime publishedOn;
private String title;
private String description;
private String authorId;
}
Now, we will setup the Enhanced DynamoDB Client and TableSchema as shown below:
@Configuration
public class DynamoDBConfig {
@Bean
public DynamoDbEnhancedClient dynamoDbEnhancedClient() {
return DynamoDbEnhancedClient.create();
}
@Bean
public DynamoDbTable<Book> bookTable(DynamoDbEnhancedClient dynamoDbEnhancedClient) {
return dynamoDbEnhancedClient.table("book", TableSchema.fromBean(Book.class));
}
}
With the enhanced dynamo-db client, let us perform an insert operation:
public void insertBook(DynamoDbTable<Book> bookTable) {
Book newBook = new Book();
newBook.setId("1111");
newBook.setPublishedOn(LocalDateTime.of(2025, 7, 8, 13, 10));
newBook.setTitle("AAAA");
newBook.setDescription("BBBB");
newBook.setAuthorId("ccc");
bookTable.putItem(newBook);
}
Now, we will update the same book with id 111, as below:
public void updateBookPublishedOn(DynamoDbTable<Book> bookTable, Book existingBook, LocalDateTime newDate) {
existingBook.setPublishedOn(newDate);
bookTable.updateItem(existingBook);
}
Note that I am updating the publishedOn attribute. My expectation would be to see that the Book with id 111 has been updated with the new publishedOn date. However, what happens is really unexpected. We find that there are now 2 rows with the same bookId 111, but different Sort Keys.
Though unexpected, this is a conscious design decision that was taken by DynamoDB, and that is how most NoSQL DBs work. Here Partition Keys and Sort keys are immutable. Once written, they cannot be modified. Again, the HashMap analogy comes handy. Can you modify the HashKey of a record inside of a HashMap? Obviously, no.
The solution really is to use the @DynamoDbImmutable annotation to have an immutable schema using builder pattern. The example from the official site uses a class, but to my pleasant surprise, we can use even records along with Lombok Builder.
@DynamoDbImmutable(builder = ImmutableBook.ImmutableBookBuilder.class)
@Builder
public record ImmutableBook(@DynamoDbPartitionKey String id, @DynamoDbSortKey LocalDateTime publishedOn, String title,
String description, String authorId) {
}
And the below configuration for creating a table schema:
@Bean
public DynamoDbTable<ImmutableBook> immutableBookTable(DynamoDbEnhancedClient dynamoDbEnhancedClient) {
return dynamoDbEnhancedClient.table("book", TableSchema.fromImmutableClass(ImmutableBook.class));
}
Now, why is this significant?
The reason is: once your Table Schema is a record, its immutable and you can only make the rookie mistake of updating an existing record would be with difficulty, and would mostly be called out in peer reviews.
Can be found here.