Skip to content

Commit 38f54b2

Browse files
committed
DynamoDb Enhanced Client : changed the way extensions are loaded, loads VersionedRecordExtension by default
1 parent b9b429d commit 38f54b2

25 files changed

+445
-119
lines changed

services-custom/dynamodb-enhanced/README.md

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -250,18 +250,32 @@ key differences:
250250

251251
### Using extensions
252252
The mapper supports plugin extensions to provide enhanced functionality
253-
beyond the simple primitive mapped operations. Only one extension can be
254-
loaded into a DynamoDbEnhancedClient. Any number of extensions can be chained
255-
together in a specific order into a single extension using a
256-
ChainExtension. Extensions have two hooks, beforeWrite() and
253+
beyond the simple primitive mapped operations. Extensions have two hooks, beforeWrite() and
257254
afterRead(); the former can modify a write operation before it happens,
258255
and the latter can modify the results of a read operation after it
259256
happens. Some operations such as UpdateItem perform both a write and
260257
then a read, so call both hooks.
261258

259+
Extensions are loaded in the order they are specified in the enhanced client builder. This load order can be important,
260+
as one extension can be acting on values that have been transformed by a previous extension. By default, just the
261+
VersionedRecordExtension will be loaded, however you can override this behavior on the client builder and load any
262+
extensions you like or specify none if you do not want the default bundled VersionedRecordExtension.
263+
264+
In this example, a custom extension named 'verifyChecksumExtension' is being loaded after the VersionedRecordExtension
265+
which is usually loaded by default by itself:
266+
```java
267+
DynamoDbEnhancedClientExtension versionedRecordExtension = VersionedRecordExtension.builder().build();
268+
269+
DynamoDbEnhancedClient enhancedClient =
270+
DynamoDbEnhancedClient.builder()
271+
.dynamoDbClient(dynamoDbClient)
272+
.extensions(versionedRecordExtension, verifyChecksumExtension)
273+
.build();
274+
```
275+
262276
#### VersionedRecordExtension
263277

264-
This extension will increment and track a record version number as
278+
This extension is loaded by default and will increment and track a record version number as
265279
records are written to the database. A condition will be added to every
266280
write that will cause the write to fail if the record version number of
267281
the actual persisted record does not match the value that the
@@ -270,14 +284,6 @@ record updates, if another process updates a record between the time the
270284
first process has read the record and is writing an update to it then
271285
that write will fail.
272286

273-
To load the extension:
274-
```java
275-
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
276-
.dynamoDbClient(dynamoDbClient)
277-
.extendWith(VersionedRecordExtension.builder().build())
278-
.build();
279-
```
280-
281287
To tell the extension which attribute to use to track the record version
282288
number tag a numeric attribute in the TableSchema:
283289
```java

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
* Asynchronous interface for running commands against a DynamoDb database.
3636
*/
3737
@SdkPublicApi
38-
public interface DynamoDbEnhancedAsyncClient {
38+
public interface DynamoDbEnhancedAsyncClient extends DynamoDbEnhancedResource {
3939

4040
/**
4141
* Returns a mapped table that can be used to execute commands that work with mapped items against that table.
@@ -90,11 +90,23 @@ static DynamoDbEnhancedAsyncClient.Builder builder() {
9090
/**
9191
* The builder definition for a {@link DynamoDbEnhancedAsyncClient}.
9292
*/
93-
interface Builder {
94-
Builder dynamoDbClient(DynamoDbAsyncClient dynamoDbAsyncClient);
95-
96-
Builder extendWith(DynamoDbEnhancedClientExtension dynamoDbEnhancedClientExtension);
97-
93+
interface Builder extends DynamoDbEnhancedResource.Builder {
94+
/**
95+
* The regular low-level SDK client to use with the enhanced client.
96+
* @param dynamoDbClient an initialized {@link DynamoDbAsyncClient}
97+
*/
98+
Builder dynamoDbClient(DynamoDbAsyncClient dynamoDbClient);
99+
100+
@Override
101+
Builder extensions(DynamoDbEnhancedClientExtension... dynamoDbEnhancedClientExtensions);
102+
103+
@Override
104+
Builder extensions(List<DynamoDbEnhancedClientExtension> dynamoDbEnhancedClientExtensions);
105+
106+
/**
107+
* Builds an enhanced client based on the settings supplied to this builder
108+
* @return An initialized {@link DynamoDbEnhancedAsyncClient}
109+
*/
98110
DynamoDbEnhancedAsyncClient build();
99111
}
100112
}

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
* Synchronous interface for running commands against a DynamoDb database.
3535
*/
3636
@SdkPublicApi
37-
public interface DynamoDbEnhancedClient {
37+
public interface DynamoDbEnhancedClient extends DynamoDbEnhancedResource {
3838

3939
/**
4040
* Returns a mapped table that can be used to execute commands that work with mapped items against that table.
@@ -88,11 +88,23 @@ static Builder builder() {
8888
/**
8989
* The builder definition for a {@link DynamoDbEnhancedClient}.
9090
*/
91-
interface Builder {
91+
interface Builder extends DynamoDbEnhancedResource.Builder {
92+
/**
93+
* The regular low-level SDK client to use with the enhanced client.
94+
* @param dynamoDbClient an initialized {@link DynamoDbClient}
95+
*/
9296
Builder dynamoDbClient(DynamoDbClient dynamoDbClient);
9397

94-
Builder extendWith(DynamoDbEnhancedClientExtension dynamoDbEnhancedClientExtension);
98+
@Override
99+
Builder extensions(DynamoDbEnhancedClientExtension... dynamoDbEnhancedClientExtensions);
95100

101+
@Override
102+
Builder extensions(List<DynamoDbEnhancedClientExtension> dynamoDbEnhancedClientExtensions);
103+
104+
/**
105+
* Builds an enhanced client based on the settings supplied to this builder
106+
* @return An initialized {@link DynamoDbEnhancedClient}
107+
*/
96108
DynamoDbEnhancedClient build();
97109
}
98110
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import java.util.Map;
1919

2020
import software.amazon.awssdk.annotations.SdkPublicApi;
21-
import software.amazon.awssdk.enhanced.dynamodb.extensions.ChainExtension;
2221
import software.amazon.awssdk.enhanced.dynamodb.extensions.ReadModification;
2322
import software.amazon.awssdk.enhanced.dynamodb.extensions.WriteModification;
2423
import software.amazon.awssdk.enhanced.dynamodb.internal.operations.OperationContext;
@@ -29,9 +28,9 @@
2928
* is written to the database, and one called just after a record is read from the database. This gives the extension the
3029
* opportunity to act as an invisible layer between the application and the database and transform the data accordingly.
3130
* <p>
32-
* Only one extension can be loaded with an enhanced client. In order to combine multiple extensions, the
33-
* {@link ChainExtension} should be used and initialized with all the component extensions to combine together
34-
* into a chain.
31+
* Multiple extensions can be used with the enhanced client, but the order in which they are loaded is important. For
32+
* instance one extension may overwrite the value of an attribute that another extension then includes in a checksum
33+
* calculation.
3534
*/
3635
@SdkPublicApi
3736
public interface DynamoDbEnhancedClientExtension {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb;
17+
18+
import java.util.List;
19+
20+
import software.amazon.awssdk.annotations.SdkPublicApi;
21+
22+
/**
23+
* Shared interface components for {@link DynamoDbEnhancedClient} and {@link DynamoDbEnhancedAsyncClient}. Any common
24+
* methods implemented by both of those classes or their builders are declared here.
25+
*/
26+
@SdkPublicApi
27+
public interface DynamoDbEnhancedResource {
28+
/**
29+
* Shared interface components for the builders of {@link DynamoDbEnhancedClient} and
30+
* {@link DynamoDbEnhancedAsyncClient}
31+
*/
32+
interface Builder {
33+
/**
34+
* Specifies the extensions to load with the enhanced client. The extensions will be loaded in the strict order
35+
* they are supplied here. Calling this method will override any bundled extensions that are loaded by default,
36+
* namely the {@link software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension}, so this
37+
* extension must be included in the supplied list otherwise it will not be loaded. Providing an empty list here
38+
* will cause no extensions to get loaded, effectively dropping the default ones.
39+
*
40+
* @param dynamoDbEnhancedClientExtensions a list of extensions to load with the enhanced client
41+
*/
42+
Builder extensions(DynamoDbEnhancedClientExtension... dynamoDbEnhancedClientExtensions);
43+
44+
/**
45+
* Specifies the extensions to load with the enhanced client. The extensions will be loaded in the strict order
46+
* they are supplied here. Calling this method will override any bundled extensions that are loaded by default,
47+
* namely the {@link software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension}, so this
48+
* extension must be included in the supplied list otherwise it will not be loaded. Providing an empty list here
49+
* will cause no extensions to get loaded, effectively dropping the default ones.
50+
*
51+
* @param dynamoDbEnhancedClientExtensions a list of extensions to load with the enhanced client
52+
*/
53+
Builder extensions(List<DynamoDbEnhancedClientExtension> dynamoDbEnhancedClientExtensions);
54+
}
55+
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbVersionAttribute.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import java.lang.annotation.Target;
2222

2323
import software.amazon.awssdk.annotations.SdkPublicApi;
24-
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.VersionedRecordExtensionAttributeTags;
24+
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.VersionRecordAttributeTags;
2525
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.BeanTableSchemaAttributeTag;
2626

2727
/**
@@ -32,6 +32,6 @@
3232
@SdkPublicApi
3333
@Target({ElementType.METHOD})
3434
@Retention(RetentionPolicy.RUNTIME)
35-
@BeanTableSchemaAttributeTag(VersionedRecordExtensionAttributeTags.class)
35+
@BeanTableSchemaAttributeTag(VersionRecordAttributeTags.class)
3636
public @interface DynamoDbVersionAttribute {
3737
}

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

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package software.amazon.awssdk.enhanced.dynamodb.internal.client;
1717

18+
import java.util.ArrayList;
19+
import java.util.Arrays;
1820
import java.util.List;
1921
import java.util.concurrent.CompletableFuture;
2022
import java.util.function.Consumer;
@@ -36,16 +38,18 @@
3638
import software.amazon.awssdk.enhanced.dynamodb.model.TransactGetResultPage;
3739
import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
3840
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
41+
import software.amazon.awssdk.utils.Validate;
3942

4043
@SdkInternalApi
4144
public final class DefaultDynamoDbEnhancedAsyncClient implements DynamoDbEnhancedAsyncClient {
4245
private final DynamoDbAsyncClient dynamoDbClient;
4346
private final DynamoDbEnhancedClientExtension extension;
4447

45-
private DefaultDynamoDbEnhancedAsyncClient(DynamoDbAsyncClient dynamoDbClient,
46-
DynamoDbEnhancedClientExtension extension) {
47-
this.dynamoDbClient = dynamoDbClient;
48-
this.extension = extension;
48+
private DefaultDynamoDbEnhancedAsyncClient(Builder builder) {
49+
this.dynamoDbClient = Validate.paramNotNull(builder.dynamoDbClient, "You must provide a DynamoDbClient to build " +
50+
"a DefaultDynamoDbEnhancedAsyncClient.");
51+
52+
this.extension = ExtensionResolver.resolveExtensions(builder.dynamoDbEnhancedClientExtensions);
4953
}
5054

5155
public static Builder builder() {
@@ -125,7 +129,7 @@ public DynamoDbEnhancedClientExtension mapperExtension() {
125129
}
126130

127131
public Builder toBuilder() {
128-
return builder().dynamoDbClient(this.dynamoDbClient).extendWith(this.extension);
132+
return builder().dynamoDbClient(this.dynamoDbClient).extensions(this.extension);
129133
}
130134

131135
@Override
@@ -156,33 +160,29 @@ public int hashCode() {
156160

157161
public static final class Builder implements DynamoDbEnhancedAsyncClient.Builder {
158162
private DynamoDbAsyncClient dynamoDbClient;
159-
private DynamoDbEnhancedClientExtension dynamoDbEnhancedClientExtension;
160-
161-
private Builder() {
162-
}
163+
private List<DynamoDbEnhancedClientExtension> dynamoDbEnhancedClientExtensions =
164+
new ArrayList<>(ExtensionResolver.defaultExtensions());
163165

166+
@Override
164167
public DefaultDynamoDbEnhancedAsyncClient build() {
165-
if (dynamoDbClient == null) {
166-
throw new IllegalArgumentException("You must provide a DynamoDbClient to build a "
167-
+ "DefaultDynamoDbEnhancedClient.");
168-
}
169-
170-
return new DefaultDynamoDbEnhancedAsyncClient(dynamoDbClient, dynamoDbEnhancedClientExtension);
168+
return new DefaultDynamoDbEnhancedAsyncClient(this);
171169
}
172170

173-
public Builder dynamoDbClient(DynamoDbAsyncClient dynamoDbAsyncClient) {
174-
this.dynamoDbClient = dynamoDbAsyncClient;
171+
@Override
172+
public Builder dynamoDbClient(DynamoDbAsyncClient dynamoDbClient) {
173+
this.dynamoDbClient = dynamoDbClient;
175174
return this;
176175
}
177176

178-
public Builder extendWith(DynamoDbEnhancedClientExtension dynamoDbEnhancedClientExtension) {
179-
if (dynamoDbEnhancedClientExtension != null && this.dynamoDbEnhancedClientExtension != null) {
180-
throw new IllegalArgumentException("You may only extend a DefaultDynamoDbEnhancedClient with a single "
181-
+ "extension. To combine multiple extensions, use the "
182-
+ "ChainMapperExtension.");
183-
}
177+
@Override
178+
public Builder extensions(DynamoDbEnhancedClientExtension... dynamoDbEnhancedClientExtensions) {
179+
this.dynamoDbEnhancedClientExtensions = Arrays.asList(dynamoDbEnhancedClientExtensions);
180+
return this;
181+
}
184182

185-
this.dynamoDbEnhancedClientExtension = dynamoDbEnhancedClientExtension;
183+
@Override
184+
public Builder extensions(List<DynamoDbEnhancedClientExtension> dynamoDbEnhancedClientExtensions) {
185+
this.dynamoDbEnhancedClientExtensions = new ArrayList<>(dynamoDbEnhancedClientExtensions);
186186
return this;
187187
}
188188
}

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

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package software.amazon.awssdk.enhanced.dynamodb.internal.client;
1717

18+
import java.util.ArrayList;
19+
import java.util.Arrays;
1820
import java.util.List;
1921
import java.util.function.Consumer;
2022

@@ -35,16 +37,18 @@
3537
import software.amazon.awssdk.enhanced.dynamodb.model.TransactGetResultPage;
3638
import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
3739
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
40+
import software.amazon.awssdk.utils.Validate;
3841

3942
@SdkInternalApi
4043
public final class DefaultDynamoDbEnhancedClient implements DynamoDbEnhancedClient {
4144
private final DynamoDbClient dynamoDbClient;
4245
private final DynamoDbEnhancedClientExtension extension;
4346

44-
private DefaultDynamoDbEnhancedClient(DynamoDbClient dynamoDbClient,
45-
DynamoDbEnhancedClientExtension extension) {
46-
this.dynamoDbClient = dynamoDbClient;
47-
this.extension = extension;
47+
private DefaultDynamoDbEnhancedClient(Builder builder) {
48+
this.dynamoDbClient = Validate.paramNotNull(builder.dynamoDbClient, "You must provide a DynamoDbClient to build " +
49+
"a DefaultDynamoDbEnhancedClient.");
50+
51+
this.extension = ExtensionResolver.resolveExtensions(builder.dynamoDbEnhancedClientExtensions);
4852
}
4953

5054
public static Builder builder() {
@@ -119,7 +123,7 @@ public DynamoDbEnhancedClientExtension mapperExtension() {
119123
}
120124

121125
public Builder toBuilder() {
122-
return builder().dynamoDbClient(this.dynamoDbClient).extendWith(this.extension);
126+
return builder().dynamoDbClient(this.dynamoDbClient).extensions(this.extension);
123127
}
124128

125129
@Override
@@ -151,30 +155,29 @@ public int hashCode() {
151155

152156
public static final class Builder implements DynamoDbEnhancedClient.Builder {
153157
private DynamoDbClient dynamoDbClient;
154-
private DynamoDbEnhancedClientExtension dynamoDbEnhancedClientExtension;
158+
private List<DynamoDbEnhancedClientExtension> dynamoDbEnhancedClientExtensions =
159+
new ArrayList<>(ExtensionResolver.defaultExtensions());
155160

161+
@Override
156162
public DefaultDynamoDbEnhancedClient build() {
157-
if (dynamoDbClient == null) {
158-
throw new IllegalArgumentException("You must provide a DynamoDbClient to build a "
159-
+ "DefaultDynamoDbEnhancedClient.");
160-
}
161-
162-
return new DefaultDynamoDbEnhancedClient(dynamoDbClient, dynamoDbEnhancedClientExtension);
163+
return new DefaultDynamoDbEnhancedClient(this);
163164
}
164165

166+
@Override
165167
public Builder dynamoDbClient(DynamoDbClient dynamoDbClient) {
166168
this.dynamoDbClient = dynamoDbClient;
167169
return this;
168170
}
169171

170-
public Builder extendWith(DynamoDbEnhancedClientExtension dynamoDbEnhancedClientExtension) {
171-
if (dynamoDbEnhancedClientExtension != null && this.dynamoDbEnhancedClientExtension != null) {
172-
throw new IllegalArgumentException("You may only extend a DefaultDynamoDbEnhancedClient with a single "
173-
+ "extension. To combine multiple extensions, use the "
174-
+ "ChainMapperExtension.");
175-
}
172+
@Override
173+
public Builder extensions(DynamoDbEnhancedClientExtension... dynamoDbEnhancedClientExtensions) {
174+
this.dynamoDbEnhancedClientExtensions = Arrays.asList(dynamoDbEnhancedClientExtensions);
175+
return this;
176+
}
176177

177-
this.dynamoDbEnhancedClientExtension = dynamoDbEnhancedClientExtension;
178+
@Override
179+
public Builder extensions(List<DynamoDbEnhancedClientExtension> dynamoDbEnhancedClientExtensions) {
180+
this.dynamoDbEnhancedClientExtensions = new ArrayList<>(dynamoDbEnhancedClientExtensions);
178181
return this;
179182
}
180183
}

0 commit comments

Comments
 (0)