Description
DynamoDB supports preventing an overwrite of an existing value in an update operation through the use of the function if_not_exists(path, value)
DynamoDB Update Expression Docs. The DynamoDB Enhanced client does not have a mechanism to achieve the same behavior. You must do a read from dynamodb, then omit the values you do not want to overwrite in a subsequent write operation.
Describe the Feature
Allow a user to specify attributes in their table schema that should not be overwritten in an update operation.
This is the basic use case, I have a class that has attributes similar to:
public class Customer {
private String accountId;
private String name;
private Instant createdDate; // Once this is written, never overwrite
private Instant updatedDate;
static final TableSchema<Customer> CUSTOMER_TABLE_SCHEMA =
TableSchema.builder(Customer.class)
.newItemSupplier(Customer::new)
.addAttribute(String.class, a -> a.name("account_id")
.getter(Customer::getAccountId)
.setter(Customer::setAccountId)
.tags(primaryPartitionKey()))
.addAttribute(String.class, a -> a.name("name")
.getter(Customer::getName)
.setter(Customer::setName))
.addAttribute(Instant.class, a -> a.name("created_date")
.getter(Customer::getCreatedDate)
.setter(Customer::setCreatedDate))
.addAttribute(Instant.class, a -> a.name("updated_date")
.getter(Customer::getUpdatedDate)
.setter(Customer::setUpdatedDate))
.build();
}
If the name field is updated, I would like to update both "name" and "updated_date" but not "created_date" in a single operation. This is possible with the low level client, but not the enhanced client.
The sequence of events is as follows:
Customer "xyz" specifies their name for the first time
{
"accountId": "xyz",
"name" : "foo",
"createdDate" : "2020-05-01T00:00:00Z",
"updatedDate" : "2020-05-01T00:00:00Z"
}
At some point later, account "xyz" updates their name to "bar". At this point the application could do a read in DDB to check if the customer already exists and then only add a createdDate value if the customer does not exist. However, DDB allows you to do it in a single UpdateItem request, so I would like to do one update with this:
{
"accountId": "xyz",
"name" : "bar",
"createdDate" : "2020-05-05T05:05:05Z",
"updatedDate" : "2020-05-05T05:05:05Z"
}
The final expected state in DDB is:
{
"accountId": "xyz",
"name" : "bar",
"createdDate" : "2020-05-01T00:00:00Z",
"updatedDate" : "2020-05-05T05:05:05Z"
}
Proposed Solution
One way I could see this implemented is by adding an attribute tag for attributes that should not be overwritten on an update.
Something like
TableSchema.builder(Customer.class)
.addAttribute(Instant.class,
a -> a.name("created_date")
.getter(Customer::getCreatedDate)
.setter(Customer::setCreatedDate)
.tags(updateBehavior(UpdateBehavior.DO_NOT_OVERWRITE)))
There could also be a corresponding annotation for use with @DynamoDbBean
When UpdateItemOperation.java
sees an attribute with UpdateBehavior.DO_NOT_OVERWRITE
, it uses "SET A = if_not_exists(A, :a)"
instead of the default "SET A = :a"
in the update expression.