If you use the @DynamoDbVersionAttribute in conjunction with @SortKey, the initial PutItem request will work, and correctly store the item with 1 for Version, but subsequent PutItems will not auto increment to Version 2 (Note, it does work with UpdateItem if this is used on a normal attribute that's not a @SortKey/@PrimaryKey, like the SomeLatestItem table below)
Put Item Fails on a condition Check from this VersionedRecordExtension whether or not the version passed exists(and is == current version) or does not exist (and is == current version + 1 )
@Value
@Builder
@AllArgsConstructor
@DynamoDbImmutable(builder = SomeVersionsItem.SomeVersionsItemBuilder.class)
public class SomeVersionsItem {
@Getter(onMethod_ = {@DynamoDbPartitionKey, @DynamoDbAttribute("Id")})
private String id;
// DDbVersionAttribute must be type (int, long)
@Getter(onMethod_ = {@DynamoDbSortKey, @DynamoDbVersionAttribute, @DynamoDbAttribute("Version")})
private Long version;
// Non PK/SK Additional fields
@Getter(onMethod_ = {@DynamoDbAttribute("SomeAttribute")})
private String someAttribute;
}
@Value
@Builder
@AllArgsConstructor
@DynamoDbImmutable(builder = SomeLatestItem.SomeLatestItemBuilder.class)
public class SomeLatestItem {
@Getter(onMethod_ = {@DynamoDbPartitionKey, @DynamoDbAttribute("Id")})
private String id;
// DDbVersionAttribute must be type (int, long)
@With(onMethod_ = {@DynamoDbIgnore})
@Getter(onMethod_ = {@DynamoDbVersionAttribute, @DynamoDbAttribute("Version")})
private Long version;
}
Expected Behavior
To bump the version to N+1, and write the new item.
For an update item request when using the @DynamoDbVersionAttribute on a table where Version is not the sort key, it expects you to pass the current version and it auto increments for you.
With the current implementation, and using the ddb enhanced client, you can not write subsequent items to the table. This forces the user to now handle their own versioning logic, and remove the annotation. This is not called out in any docs, and if anything, many of the AWS articles on versioning lead you to make this mistake.
Current Behavior
ERROR TransmutingContinuationHandler:157 - Internal Failure
software.amazon.awssdk.services.dynamodb.model.TransactionCanceledException: Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None] (Service: DynamoDb, Status Code: 400, Request ID: KNKSB2C6NH1T4U57EF26P6C2MNVV4KQNSO5AEMVJF66Q9ASUAAJG)
at software.amazon.awssdk.core.internal.http.CombinedResponseHandler.handleErrorResponse(CombinedResponseHandler.java:125) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.CombinedResponseHandler.handleResponse(CombinedResponseHandler.java:82) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.CombinedResponseHandler.handle(CombinedResponseHandler.java:60) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.CombinedResponseHandler.handle(CombinedResponseHandler.java:41) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.HandleResponseStage.execute(HandleResponseStage.java:50) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.HandleResponseStage.execute(HandleResponseStage.java:38) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptTimeoutTrackingStage.execute(ApiCallAttemptTimeoutTrackingStage.java:72) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptTimeoutTrackingStage.execute(ApiCallAttemptTimeoutTrackingStage.java:42) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.TimeoutExceptionHandlingStage.execute(TimeoutExceptionHandlingStage.java:78) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.TimeoutExceptionHandlingStage.execute(TimeoutExceptionHandlingStage.java:40) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptMetricCollectionStage.execute(ApiCallAttemptMetricCollectionStage.java:55) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptMetricCollectionStage.execute(ApiCallAttemptMetricCollectionStage.java:39) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage.execute(RetryableStage.java:81) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage.execute(RetryableStage.java:36) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.StreamManagingStage.execute(StreamManagingStage.java:56) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.StreamManagingStage.execute(StreamManagingStage.java:36) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.executeWithTimer(ApiCallTimeoutTrackingStage.java:80) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.execute(ApiCallTimeoutTrackingStage.java:60) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.execute(ApiCallTimeoutTrackingStage.java:42) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallMetricCollectionStage.execute(ApiCallMetricCollectionStage.java:50) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallMetricCollectionStage.execute(ApiCallMetricCollectionStage.java:32) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ExecutionFailureExceptionReportingStage.execute(ExecutionFailureExceptionReportingStage.java:37) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ExecutionFailureExceptionReportingStage.execute(ExecutionFailureExceptionReportingStage.java:26) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient$RequestExecutionBuilderImpl.execute(AmazonSyncHttpClient.java:224) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.invoke(BaseSyncClientHandler.java:103) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.doExecute(BaseSyncClientHandler.java:173) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.lambda$execute$1(BaseSyncClientHandler.java:80) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.measureApiCallSuccess(BaseSyncClientHandler.java:182) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.execute(BaseSyncClientHandler.java:74) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.client.handler.SdkSyncClientHandler.execute(SdkSyncClientHandler.java:45) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler.execute(AwsSyncClientHandler.java:53) ~[AwsJavaSdk-Core-AwsCore-2.0.jar:?]
at software.amazon.awssdk.services.dynamodb.DefaultDynamoDbClient.transactWriteItems(DefaultDynamoDbClient.java:6042) ~[AwsJavaSdk-DynamoDb-2.0.jar:?]
at software.amazon.awssdk.enhanced.dynamodb.internal.operations.DatabaseOperation.execute(DatabaseOperation.java:82) ~[AwsJavaSdk-DynamoDb-Enhanced-2.0.jar:?]
at software.amazon.awssdk.enhanced.dynamodb.internal.client.DefaultDynamoDbEnhancedClient.transactWriteItems(DefaultDynamoDbEnhancedClient.java:103) ~[AwsJavaSdk-DynamoDb-Enhanced-2.0.jar:?]
Reproduction Steps
public class DynamoDbDAO {
private final DynamoDbTable<SomeLatestItem> enhancedSomeLatestTable;
private final DynamoDbTable<SomeVersionsItem> enhancedSomeVersionsTable;
private final DynamoDbEnhancedClient dynamoDbEnhancedClient;
...
public SomeLatestItem edit() {
// before edit is called, currentVersion = 1 and item "1234" exists.
final SomeLatestItem item = SomeLatestItem.builder().id("1234").version(currentVersion).build();
// SomeVersionsItem write fails for both currentVersion passed (Version = 1)
// also fails for current version + 1 passed (Version = 2)
// since i want a new item, I can't call addUpdateItem for this table.
final SomeVersionsItem item = SomeVersionsItem.builder().id("1234").version(currentVersion).build();
final TransactPutItemEnhancedRequest<SomeVersionsItem> putRequestVersionItem =
TransactPutItemEnhancedRequest.builder(SomeVersionsItem.class)
.item(updatedVersionItemToWrite)
.build();
final TransactUpdateItemEnhancedRequest<SomeLatestItem> updateRequestLatestItem =
TransactUpdateItemEnhancedRequest.builder(SomeLatestItem.class)
.item(updatedLatestItem)
.ignoreNulls(true)
.build();
final TransactWriteItemsEnhancedRequest transactReq = TransactWriteItemsEnhancedRequest.builder()
.addPutItem(enhancedSomeVersionsTable, putRequestVersionItem)
.addUpdateItem(enhancedSomeLatestTable, updateRequestLatestItem)
.build();
dynamoDbEnhancedClient.transactWriteItems(transactReq);
return SomeLatestItem.builder().id("1234").build();
}
Describe the bug
If you use the
@DynamoDbVersionAttribute
in conjunction with@SortKey
, the initialPutItem
request will work, and correctly store the item with1
for Version, but subsequentPutItems
will not auto increment to Version2
(Note, it does work withUpdateItem
if this is used on a normal attribute that's not a@SortKey/@PrimaryKey
, like theSomeLatestItem
table below)Put Item Fails on a condition Check from this VersionedRecordExtension whether or not the version passed exists(and is
== current version
) or does not exist (and is== current version + 1
)Expected Behavior
To bump the version to N+1, and write the new item.
For an update item request when using the @DynamoDbVersionAttribute on a table where Version is not the sort key, it expects you to pass the current version and it auto increments for you.
With the current implementation, and using the ddb enhanced client, you can not write subsequent items to the table. This forces the user to now handle their own versioning logic, and remove the annotation. This is not called out in any docs, and if anything, many of the AWS articles on versioning lead you to make this mistake.
Current Behavior
Reproduction Steps
Possible Solution
No response
Additional Information/Context
No response
AWS Java SDK version used
aws-sdk-java-v2
JDK version used
JDK21
Operating System and version
Amazon Linux 2023 (Lambda JDK 21)