Skip to content

Commit 5853883

Browse files
committed
[POC] Optimistic Locking for Delete Operations
1 parent 904caa9 commit 5853883

File tree

10 files changed

+189
-96
lines changed

10 files changed

+189
-96
lines changed

services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncCrudWithResponseIntegrationTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,9 @@ public void deleteItem_versionedRecord_flagTrue_versionMismatch_shouldFail() {
449449

450450

451451

452-
// 6. deleteItem(DeleteItemEnhancedRequest) - Versions match
452+
// 6. deleteItem(DeleteItemEnhancedRequest) with builder method - Versions match
453453
@Test
454-
public void deleteItemWithHelper_versionMatch_shouldSucceed() {
454+
public void deleteItemWithBuilder_versionMatch_shouldSucceed() {
455455
VersionedRecord item = new VersionedRecord().setId("123").setSort(10).setStringAttribute("Test Item");
456456
Key recordKey = Key.builder().partitionValue(item.getId()).sortValue(item.getSort()).build();
457457

@@ -469,9 +469,9 @@ public void deleteItemWithHelper_versionMatch_shouldSucceed() {
469469
assertThat(deletedItem).isNull();
470470
}
471471

472-
// 7. deleteItem(DeleteItemEnhancedRequest) - Versions mismatch
472+
// 7. deleteItem(DeleteItemEnhancedRequest) with builder method - Versions mismatch
473473
@Test
474-
public void deleteItemWithHelper_versionMismatch_shouldFail() {
474+
public void deleteItemWithBuilder_versionMismatch_shouldFail() {
475475
VersionedRecord item = new VersionedRecord().setId("123").setSort(10).setStringAttribute("Test Item");
476476
Key recordKey = Key.builder().partitionValue(item.getId()).sortValue(item.getSort()).build();
477477

@@ -522,9 +522,9 @@ public void transactDeleteItem_versionedRecord_versionMatch_shouldSucceed() {
522522

523523

524524

525-
// 10. TransactWriteItems - versions match
525+
// 10. TransactWriteItems with builder method - versions match
526526
@Test
527-
public void transactDeleteItemWithHelper_versionMatch_shouldSucceed() {
527+
public void transactDeleteItemWithBuilder_versionMatch_shouldSucceed() {
528528
VersionedRecord item = new VersionedRecord().setId("123").setSort(10).setStringAttribute("Test Item");
529529
Key recordKey = Key.builder().partitionValue(item.getId()).sortValue(item.getSort()).build();
530530

@@ -544,9 +544,9 @@ public void transactDeleteItemWithHelper_versionMatch_shouldSucceed() {
544544
assertThat(deletedItem).isNull();
545545
}
546546

547-
// 11. TransactWriteItems with helper - versions mismatch
547+
// 11. TransactWriteItems with builder method - versions mismatch
548548
@Test
549-
public void transactDeleteItemWithHelper_versionMismatch_shouldFail() {
549+
public void transactDeleteItemWithBuilder_versionMismatch_shouldFail() {
550550
VersionedRecord item = new VersionedRecord().setId("123").setSort(10).setStringAttribute("Test Item");
551551
Key recordKey = Key.builder().partitionValue(item.getId()).sortValue(item.getSort()).build();
552552

services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/CrudWithResponseIntegrationTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -432,9 +432,9 @@ public void deleteItem_versionedRecord_flagTrue_versionMismatch_shouldFail() {
432432

433433

434434

435-
// 6. deleteItem(DeleteItemEnhancedRequest) with helper - versions match
435+
// 6. deleteItem(DeleteItemEnhancedRequest) with builder method - versions match
436436
@Test
437-
public void deleteItemWithHelper_versionMatch_shouldSucceed() {
437+
public void deleteItemWithBuilder_versionMatch_shouldSucceed() {
438438
VersionedRecord item = new VersionedRecord().setId("123").setSort(10).setStringAttribute("Test Item");
439439
Key recordKey = Key.builder().partitionValue(item.getId()).sortValue(item.getSort()).build();
440440

@@ -452,9 +452,9 @@ public void deleteItemWithHelper_versionMatch_shouldSucceed() {
452452
assertThat(deletedItem).isNull();
453453
}
454454

455-
// 7. deleteItem(DeleteItemEnhancedRequest) with helper - versions mismatch
455+
// 7. deleteItem(DeleteItemEnhancedRequest) with builder method - versions mismatch
456456
@Test
457-
public void deleteItemWithHelper_versionMismatch_shouldFail() {
457+
public void deleteItemWithBuilder_versionMismatch_shouldFail() {
458458
VersionedRecord item = new VersionedRecord().setId("123").setSort(10).setStringAttribute("Test Item");
459459
Key recordKey = Key.builder().partitionValue(item.getId()).sortValue(item.getSort()).build();
460460

@@ -506,9 +506,9 @@ public void transactDeleteItem_versionedRecord_versionMatch_shouldSucceed() {
506506

507507

508508

509-
// 10. TransactWriteItems with helper - versions match
509+
// 10. TransactWriteItems with builder method - versions match
510510
@Test
511-
public void transactDeleteItemWithHelper_versionMatch_shouldSucceed() {
511+
public void transactDeleteItemWithBuilder_versionMatch_shouldSucceed() {
512512
VersionedRecord item = new VersionedRecord().setId("123").setSort(10).setStringAttribute("Test Item");
513513
Key recordKey = Key.builder().partitionValue(item.getId()).sortValue(item.getSort()).build();
514514

@@ -529,9 +529,9 @@ public void transactDeleteItemWithHelper_versionMatch_shouldSucceed() {
529529
assertThat(deletedItem).isNull();
530530
}
531531

532-
// 11. TransactWriteItems with helper - versions mismatch
532+
// 11. TransactWriteItems with builder method - versions mismatch
533533
@Test
534-
public void transactDeleteItemWithHelper_versionMismatch_shouldFail() {
534+
public void transactDeleteItemWithBuilder_versionMismatch_shouldFail() {
535535
VersionedRecord item = new VersionedRecord().setId("123").setSort(10).setStringAttribute("Test Item");
536536
Key recordKey = Key.builder().partitionValue(item.getId()).sortValue(item.getSort()).build();
537537

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncTable.java

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
package software.amazon.awssdk.enhanced.dynamodb.internal.client;
1717

1818
import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.createKeyFromItem;
19-
import static software.amazon.awssdk.enhanced.dynamodb.model.OptimisticLockingHelper.withOptimisticLocking;
19+
import static software.amazon.awssdk.enhanced.dynamodb.model.OptimisticLockingHelper.applyOptimisticLockingIfApplicable;
2020

2121
import java.util.ArrayList;
2222
import java.util.concurrent.CompletableFuture;
@@ -126,55 +126,53 @@ public CompletableFuture<Void> createTable() {
126126
.build());
127127
}
128128

129+
/**
130+
* Supports optimistic locking via {@link software.amazon.awssdk.enhanced.dynamodb.model.OptimisticLockingHelper}.
131+
*/
129132
@Override
130133
public CompletableFuture<T> deleteItem(DeleteItemEnhancedRequest request) {
131134
TableOperation<T, ?, ?, DeleteItemEnhancedResponse<T>> operation = DeleteItemOperation.create(request);
132135
return operation.executeOnPrimaryIndexAsync(tableSchema, tableName, extension, dynamoDbClient)
133136
.thenApply(DeleteItemEnhancedResponse::attributes);
134137
}
135138

139+
/**
140+
* Supports optimistic locking via {@link software.amazon.awssdk.enhanced.dynamodb.model.OptimisticLockingHelper}.
141+
*/
136142
@Override
137143
public CompletableFuture<T> deleteItem(Consumer<DeleteItemEnhancedRequest.Builder> requestConsumer) {
138144
DeleteItemEnhancedRequest.Builder builder = DeleteItemEnhancedRequest.builder();
139145
requestConsumer.accept(builder);
140146
return deleteItem(builder.build());
141147
}
142148

149+
/**
150+
* Does not support optimistic locking. Use {@link #deleteItem(Object, boolean)} for optimistic locking support.
151+
*/
143152
@Override
144153
public CompletableFuture<T> deleteItem(Key key) {
145154
return deleteItem(r -> r.key(key));
146155
}
147156

148157
/**
149-
* Deletes an item from the table using the provided key item.
150-
* <p>
151-
* <b>Note:</b> This method does not use optimistic locking. For versioned records,
152-
* use {@link #deleteItem(Object, boolean)} with {@code useOptimisticLocking = true}
153-
* to enable optimistic locking protection.
154-
* <p>
155-
* The DynamoDB Enhanced Client provides optimistic locking for the following operations:
156-
* <ul>
157-
* <li>{@link #deleteItem(Object, boolean)} - when {@code useOptimisticLocking = true}</li>
158-
* <li>{@link #deleteItem(DeleteItemEnhancedRequest)} - when using {@code DeleteItemEnhancedRequest.Builder.withOptimisticLocking()}</li>
159-
* <li>Transaction operations - when using {@code TransactDeleteItemEnhancedRequest.Builder.withOptimisticLocking()}</li>
160-
* </ul>
161-
*
162-
* @param keyItem the item containing the key attributes to identify the item to delete
163-
* @return a CompletableFuture containing the deleted item, or null if the item was not found
164-
* @deprecated Use {@link #deleteItem(Object, boolean)} instead to explicitly control optimistic locking behavior
158+
* @deprecated Use {@link #deleteItem(Object, boolean)} instead to explicitly control optimistic locking behavior.
165159
*/
166-
@Deprecated
167160
@Override
161+
@Deprecated
168162
public CompletableFuture<T> deleteItem(T keyItem) {
169163
return deleteItem(keyItem, false);
170164
}
171165

172-
@Override
166+
/**
167+
* Deletes an item from the table with optional optimistic locking.
168+
*
169+
* @param keyItem the item containing the key to delete
170+
* @param useOptimisticLocking if true, applies optimistic locking if the item has version information
171+
* @return a CompletableFuture containing the deleted item, or null if the item was not found
172+
*/
173173
public CompletableFuture<T> deleteItem(T keyItem, boolean useOptimisticLocking) {
174174
DeleteItemEnhancedRequest request = DeleteItemEnhancedRequest.builder().key(keyFrom(keyItem)).build();
175-
if (useOptimisticLocking) {
176-
request = withOptimisticLocking(request, keyItem, tableSchema);
177-
}
175+
request = applyOptimisticLockingIfApplicable(request, keyItem, tableSchema, useOptimisticLocking);
178176
return deleteItem(request);
179177
}
180178

@@ -341,6 +339,11 @@ public CompletableFuture<T> updateItem(T item) {
341339
public Key keyFrom(T item) {
342340
return createKeyFromItem(item, tableSchema, TableMetadata.primaryIndexName());
343341
}
342+
343+
private java.util.Optional<String> getVersionAttributeName() {
344+
return tableSchema.tableMetadata()
345+
.customMetadataObject("VersionedRecordExtension:VersionAttribute", String.class);
346+
}
344347

345348

346349
@Override

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbTable.java

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
package software.amazon.awssdk.enhanced.dynamodb.internal.client;
1717

1818
import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.createKeyFromItem;
19-
import static software.amazon.awssdk.enhanced.dynamodb.model.OptimisticLockingHelper.withOptimisticLocking;
19+
import static software.amazon.awssdk.enhanced.dynamodb.model.OptimisticLockingHelper.applyOptimisticLockingIfApplicable;
2020

2121
import java.util.ArrayList;
2222
import java.util.function.Consumer;
@@ -127,54 +127,52 @@ public void createTable() {
127127
.build());
128128
}
129129

130+
/**
131+
* Supports optimistic locking via {@link software.amazon.awssdk.enhanced.dynamodb.model.OptimisticLockingHelper}.
132+
*/
130133
@Override
131134
public T deleteItem(DeleteItemEnhancedRequest request) {
132135
TableOperation<T, ?, ?, DeleteItemEnhancedResponse<T>> operation = DeleteItemOperation.create(request);
133136
return operation.executeOnPrimaryIndex(tableSchema, tableName, extension, dynamoDbClient).attributes();
134137
}
135138

139+
/**
140+
* Supports optimistic locking via {@link software.amazon.awssdk.enhanced.dynamodb.model.OptimisticLockingHelper}.
141+
*/
136142
@Override
137143
public T deleteItem(Consumer<DeleteItemEnhancedRequest.Builder> requestConsumer) {
138144
DeleteItemEnhancedRequest.Builder builder = DeleteItemEnhancedRequest.builder();
139145
requestConsumer.accept(builder);
140146
return deleteItem(builder.build());
141147
}
142148

149+
/**
150+
* Does not support optimistic locking. Use {@link #deleteItem(Object, boolean)} for optimistic locking support.
151+
*/
143152
@Override
144153
public T deleteItem(Key key) {
145154
return deleteItem(r -> r.key(key));
146155
}
147156

148157
/**
149-
* Deletes an item from the table using the provided key item.
150-
* <p>
151-
* <b>Note:</b> This method does not use optimistic locking. For versioned records,
152-
* use {@link #deleteItem(Object, boolean)} with {@code useOptimisticLocking = true}
153-
* to enable optimistic locking protection.
154-
* <p>
155-
* The DynamoDB Enhanced Client provides optimistic locking for the following operations:
156-
* <ul>
157-
* <li>{@link #deleteItem(Object, boolean)} - when {@code useOptimisticLocking = true}</li>
158-
* <li>{@link #deleteItem(DeleteItemEnhancedRequest)} - when using {@code DeleteItemEnhancedRequest.Builder.withOptimisticLocking()}</li>
159-
* <li>Transaction operations - when using {@code TransactDeleteItemEnhancedRequest.Builder.withOptimisticLocking()}</li>
160-
* </ul>
161-
*
162-
* @param keyItem the item containing the key attributes to identify the item to delete
163-
* @return the deleted item, or null if the item was not found
164-
* @deprecated Use {@link #deleteItem(Object, boolean)} instead to explicitly control optimistic locking behavior
158+
* @deprecated Use {@link #deleteItem(Object, boolean)} instead to explicitly control optimistic locking behavior.
165159
*/
166-
@Deprecated
167160
@Override
161+
@Deprecated
168162
public T deleteItem(T keyItem) {
169163
return deleteItem(keyItem, false);
170164
}
171165

172-
@Override
166+
/**
167+
* Deletes an item from the table with optional optimistic locking.
168+
*
169+
* @param keyItem the item containing the key to delete
170+
* @param useOptimisticLocking if true, applies optimistic locking if the item has version information
171+
* @return the deleted item, or null if the item was not found
172+
*/
173173
public T deleteItem(T keyItem, boolean useOptimisticLocking) {
174174
DeleteItemEnhancedRequest request = DeleteItemEnhancedRequest.builder().key(keyFrom(keyItem)).build();
175-
if (useOptimisticLocking) {
176-
request = withOptimisticLocking(request, keyItem, tableSchema);
177-
}
175+
request = applyOptimisticLockingIfApplicable(request, keyItem, tableSchema, useOptimisticLocking);
178176
return deleteItem(request);
179177
}
180178

@@ -333,6 +331,11 @@ public UpdateItemEnhancedResponse<T> updateItemWithResponse(Consumer<UpdateItemE
333331
public Key keyFrom(T item) {
334332
return createKeyFromItem(item, tableSchema, TableMetadata.primaryIndexName());
335333
}
334+
335+
private java.util.Optional<String> getVersionAttributeName() {
336+
return tableSchema.tableMetadata()
337+
.customMetadataObject("VersionedRecordExtension:VersionAttribute", String.class);
338+
}
336339

337340
@Override
338341
public void deleteTable() {

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/DeleteItemEnhancedRequest.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,19 @@ public Builder returnValuesOnConditionCheckFailure(String returnValuesOnConditio
293293
}
294294

295295
/**
296-
* Adds optimistic locking condition to the delete request.
296+
* Adds optimistic locking to this delete request.
297+
* <p>
298+
* This method applies a condition expression that ensures the delete operation only succeeds
299+
* if the version attribute of the item matches the provided expected value.
297300
*
298-
* @param versionValue the expected version value
299-
* @param versionAttributeName the name of the version attribute
300-
* @return a builder of this type
301+
* @param versionValue the expected version value that must match for the delete to succeed
302+
* @param versionAttributeName the name of the version attribute in the DynamoDB table
303+
* @return a builder of this type with optimistic locking condition applied
304+
* @throws IllegalArgumentException if any parameter is null
301305
*/
302306
public Builder withOptimisticLocking(AttributeValue versionValue, String versionAttributeName) {
303-
return conditionExpression(createVersionCondition(versionValue, versionAttributeName));
307+
Expression optimisticLockingCondition = createVersionCondition(versionValue, versionAttributeName);
308+
return conditionExpression(optimisticLockingCondition);
304309
}
305310

306311
public DeleteItemEnhancedRequest build() {

0 commit comments

Comments
 (0)