As a simple example, I have a class that looks like this:
public class DbData {
@DynamoDBAutoGeneratedKey
@DynamoDBHashKey(attributeName = "id")
private UUID id;
private UUID parent;
// other fields ...
}
This class is stored in a DynamoDB table. When a new entry is added to the table, I want to make sure that the parent already exists (if the parent is set).
Can this be done using a DynamoDBSaveExpression?
For example, I can make sure that the current id does not exist:
new DynamoDBSaveExpression()
.withExpectedEntry("id", new ExpectedAttributeValue().withExists(false));
I thought this may work, but it does not:
ExpectedAttributeValue expectedAttributeValue = new ExpectedAttributeValue(new AttributeValue(parent.toString()));
dynamoDBSaveExpression = new DynamoDBSaveExpression()
.withExpectedEntry("id", new ExpectedAttributeValue().withExists(false));
.withExpectedEntry("id", expectedAttributeValue.withExists(true))
.withConditionalOperator(ConditionalOperator.AND);
Edit
There is a RESTful API that defines a POST to create a resource, and the resource is stored as an entry in a DynamoDB table.
The resource allows a hierarchy, which is specified with a parent identifier.
If a parent resource is not found, a 404 is returned:
a. if parent identifier is set
b. get parent resource from DynamoDB
c. if parent not found
d. return 404
e. persist resource in DynamoDB
The pseudo-code is not atomic. 'b' may return the parent, but another thread/process may delete it before 'e'.
I was hoping that the condition expression would help here. Is the transaction library the only way to accomplish this? Or, should the data be modeled differently?
The below answer is basically obsolete, but I'm leaving it for posterity. DynamoDB now supports transactions which allow you to provide a condition expression for a different key value in the same or another table.
No, a ConditionExpression cannot look at any items other that the current item which is being saved/updated/deleted.
Update
If possible, can you design your data in such a way that things cannot be deleted? (Maybe you can mark it as obsolete instead.) That eliminates the problem entirely because once something exists, it will always exist.
If that’s not possible, you might want to look at DynamoDBLockClient. It provides a general-purpose distributed locking API that’s backed by DynamoDB.
Your new pseudo code would be
a. if parent identifier is set
b. Acquire lock for parent identifier
c. get parent resource from DynamoDB
d. if parent not found
e. Release lock
f. return 404
g. persist resource in DynamoDB
h. Release lock
Also, in any operation where you are deleting something that is a parent identifier, you should acquire a lock on that parent identifier before deleting it. If you do this, you can ensure that the parent does not get deleted in the midst of a child being created.