diff --git a/src/cart/pom.xml b/src/cart/pom.xml index eda2f0ab1..b746d2d9c 100644 --- a/src/cart/pom.xml +++ b/src/cart/pom.xml @@ -25,6 +25,18 @@ 1.5.5.Final + + + + software.amazon.awssdk + bom + 2.21.45 + pom + import + + + + org.springframework.boot @@ -69,15 +81,17 @@ spring-boot-starter-data-mongodb - com.amazonaws - aws-java-sdk-dynamodb - 1.12.603 - + software.amazon.awssdk + dynamodb + - com.amazonaws - aws-java-sdk-sts - 1.12.578 + software.amazon.awssdk + dynamodb-enhanced + + software.amazon.awssdk + sts + org.projectlombok lombok diff --git a/src/cart/src/main/java/com/amazon/sample/carts/configuration/DynamoDBConfiguration.java b/src/cart/src/main/java/com/amazon/sample/carts/configuration/DynamoDBConfiguration.java index e7cdf3109..22afb3867 100644 --- a/src/cart/src/main/java/com/amazon/sample/carts/configuration/DynamoDBConfiguration.java +++ b/src/cart/src/main/java/com/amazon/sample/carts/configuration/DynamoDBConfiguration.java @@ -18,54 +18,46 @@ package com.amazon.sample.carts.configuration; -import com.amazonaws.client.builder.AwsClientBuilder; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverterFactory; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder; + import com.amazon.sample.carts.services.DynamoDBCartService; + +import java.net.URI; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.util.StringUtils; + import com.amazon.sample.carts.services.CartService; @Configuration @Profile("dynamodb") public class DynamoDBConfiguration { - @Bean - public AmazonDynamoDB amazonDynamoDB(DynamoDBProperties properties) { + @Bean DynamoDbClient dynamoDbClient(DynamoDBProperties properties) { + DynamoDbClientBuilder builder = DynamoDbClient.builder(); + if (!StringUtils.isEmpty(properties.getEndpoint())) { - return AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration( - new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), "us-west-2") - ).build(); + builder.region(Region.US_WEST_2); + builder.endpointOverride(URI.create(properties.getEndpoint())); } - return AmazonDynamoDBClientBuilder.standard().build(); - } - - @Bean - public DynamoDBMapperConfig dynamoDBMapperConfig(DynamoDBProperties properties) { - // Create empty DynamoDBMapperConfig builder - DynamoDBMapperConfig.Builder builder = new DynamoDBMapperConfig.Builder(); - // Inject missing defaults from the deprecated method - builder.withTypeConverterFactory(DynamoDBTypeConverterFactory.standard()); - builder.withTableNameResolver((aClass, dynamoDBMapperConfig) -> { - return properties.getTableName(); - }); - return builder.build(); } @Bean - public DynamoDBMapper dynamoDBMapper(AmazonDynamoDB amazonDynamoDB, DynamoDBMapperConfig config) { - return new DynamoDBMapper(amazonDynamoDB, config); + public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClient) { + return DynamoDbEnhancedClient.builder() + .dynamoDbClient(dynamoDbClient) + .build(); } @Bean - public CartService dynamoCartService(DynamoDBMapper mapper, AmazonDynamoDB amazonDynamoDB, DynamoDBProperties properties) { - return new DynamoDBCartService(mapper, amazonDynamoDB, properties.isCreateTable()); + public CartService dynamoCartService(DynamoDbClient dynamoDbClient, DynamoDbEnhancedClient dynamoDbEnhancedClient, DynamoDBProperties properties) { + return new DynamoDBCartService(dynamoDbClient, dynamoDbEnhancedClient, properties.isCreateTable()); } } diff --git a/src/cart/src/main/java/com/amazon/sample/carts/configuration/DynamoDBProperties.java b/src/cart/src/main/java/com/amazon/sample/carts/configuration/DynamoDBProperties.java index 80f90bac6..6b77efdd4 100644 --- a/src/cart/src/main/java/com/amazon/sample/carts/configuration/DynamoDBProperties.java +++ b/src/cart/src/main/java/com/amazon/sample/carts/configuration/DynamoDBProperties.java @@ -19,7 +19,6 @@ package com.amazon.sample.carts.configuration; import lombok.Data; -import lombok.Getter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; diff --git a/src/cart/src/main/java/com/amazon/sample/carts/repositories/dynamo/entities/DynamoItemEntity.java b/src/cart/src/main/java/com/amazon/sample/carts/repositories/dynamo/entities/DynamoItemEntity.java index dce73bbfd..af892fd90 100644 --- a/src/cart/src/main/java/com/amazon/sample/carts/repositories/dynamo/entities/DynamoItemEntity.java +++ b/src/cart/src/main/java/com/amazon/sample/carts/repositories/dynamo/entities/DynamoItemEntity.java @@ -18,13 +18,13 @@ package com.amazon.sample.carts.repositories.dynamo.entities; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey; + import com.amazon.sample.carts.repositories.ItemEntity; -@DynamoDBTable(tableName="Items") +@DynamoDbBean() public class DynamoItemEntity implements ItemEntity { private String id; @@ -48,28 +48,24 @@ public DynamoItemEntity() { } - @DynamoDBHashKey + @DynamoDbPartitionKey public String getId() { return id; } - @DynamoDBAttribute - @DynamoDBIndexHashKey(globalSecondaryIndexName = "idx_global_customerId") + @DynamoDbSecondaryPartitionKey(indexNames = {"idx_global_customerId"}) public String getCustomerId() { return customerId; } - @DynamoDBAttribute public String getItemId() { return itemId; } - @DynamoDBAttribute public int getQuantity() { return quantity; } - @DynamoDBAttribute public int getUnitPrice() { return unitPrice; } diff --git a/src/cart/src/main/java/com/amazon/sample/carts/services/DynamoDBCartService.java b/src/cart/src/main/java/com/amazon/sample/carts/services/DynamoDBCartService.java index ec87f99c0..5ed9a1f22 100644 --- a/src/cart/src/main/java/com/amazon/sample/carts/services/DynamoDBCartService.java +++ b/src/cart/src/main/java/com/amazon/sample/carts/services/DynamoDBCartService.java @@ -18,66 +18,70 @@ package com.amazon.sample.carts.services; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression; -import com.amazonaws.services.dynamodbv2.datamodeling.PaginatedQueryList; -import com.amazonaws.services.dynamodbv2.model.CreateTableRequest; -import com.amazonaws.services.dynamodbv2.model.DeleteTableRequest; -import com.amazonaws.services.dynamodbv2.model.Projection; -import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput; import com.amazon.sample.carts.repositories.CartEntity; import com.amazon.sample.carts.repositories.ItemEntity; import com.amazon.sample.carts.repositories.dynamo.entities.DynamoItemEntity; import lombok.extern.slf4j.Slf4j; - +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbIndex; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.Page; +import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest; +import software.amazon.awssdk.services.dynamodb.model.ProjectionType; +import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException; import jakarta.annotation.PostConstruct; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Optional; @Slf4j public class DynamoDBCartService implements CartService { - private final DynamoDBMapper mapper; - private final AmazonDynamoDB dynamoDB; + private final DynamoDbClient dynamoDBClient; private final boolean createTable; + private final DynamoDbTable table; + + static final TableSchema CART_TABLE_SCHEMA = TableSchema.fromClass(DynamoItemEntity.class); - public DynamoDBCartService(DynamoDBMapper mapper, AmazonDynamoDB dynamoDB, boolean createTable) { - this.mapper = mapper; - this.dynamoDB = dynamoDB; + public DynamoDBCartService(DynamoDbClient dynamoDBClient,DynamoDbEnhancedClient dynamoDbEnhancedClient, boolean createTable) { + this.dynamoDBClient = dynamoDBClient; this.createTable = createTable; + + this.table = dynamoDbEnhancedClient.table("Items", CART_TABLE_SCHEMA); } @PostConstruct public void init() { if(createTable) { - DeleteTableRequest deleteTableRequest = mapper.generateDeleteTableRequest(DynamoItemEntity.class); - try { - dynamoDB.describeTable(deleteTableRequest.getTableName()); - - log.warn("Dynamo table found, deleting to recreate...."); - dynamoDB.deleteTable(deleteTableRequest); + dynamoDBClient.deleteTable( + DeleteTableRequest.builder().tableName("Items").build() + ); } - catch (com.amazonaws.services.dynamodbv2.model.ResourceNotFoundException rnfe) { - log.warn("Dynamo "+deleteTableRequest.getTableName()+" table not found"); + catch (ResourceNotFoundException rnfe) { + log.warn("Dynamo table not found"); } - ProvisionedThroughput pt = new ProvisionedThroughput(1L, 1L); - - CreateTableRequest tableRequest = mapper - .generateCreateTableRequest(DynamoItemEntity.class); - tableRequest.setProvisionedThroughput(pt); - tableRequest.getGlobalSecondaryIndexes().get(0).setProvisionedThroughput(pt); - tableRequest.getGlobalSecondaryIndexes().get(0).setProjection(new Projection().withProjectionType("ALL")); - dynamoDB.createTable(tableRequest); - - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + this.table.createTable(builder -> builder + .globalSecondaryIndices(builder3 -> builder3 + .indexName("idx_global_customerId") + + .projection(builder2 -> builder2 + .projectionType(ProjectionType.ALL)) + .provisionedThroughput(builder4 -> builder4 + .writeCapacityUnits(1L) + .readCapacityUnits(1L))) + .provisionedThroughput(b -> b + .readCapacityUnits(1L) + .writeCapacityUnits(1L) + .build())); + + this.dynamoDBClient.waiter().waitUntilTableExists(b -> b.tableName("Items")); } } @@ -102,7 +106,9 @@ public List getItems() { public void delete(String customerId) { List items = items(customerId); - mapper.batchDelete(items); + items.forEach(item -> { + this.table.deleteItem(item); + }); } @Override @@ -114,7 +120,7 @@ public CartEntity merge(String sessionId, String customerId) { public ItemEntity add(String customerId, String itemId, int quantity, int unitPrice) { String hashKey = hashKey(customerId, itemId); - DynamoItemEntity item = this.mapper.load(DynamoItemEntity.class, hashKey); + DynamoItemEntity item = this.table.getItem(Key.builder().partitionValue(hashKey(customerId, itemId)).build()); if(item != null) { item.setQuantity(item.getQuantity() + quantity); @@ -123,34 +129,34 @@ public ItemEntity add(String customerId, String itemId, int quantity, int unitPr item = new DynamoItemEntity(hashKey, customerId, itemId, 1, unitPrice); } - this.mapper.save(item); + this.table.putItem(item); return item; } @Override public List items(String customerId) { - final DynamoItemEntity gsiKeyObj = new DynamoItemEntity(); - gsiKeyObj.setCustomerId(customerId); - final DynamoDBQueryExpression queryExpression = - new DynamoDBQueryExpression<>(); - queryExpression.setHashKeyValues(gsiKeyObj); - queryExpression.setIndexName("idx_global_customerId"); - queryExpression.setConsistentRead(false); // cannot use consistent read on GSI - final PaginatedQueryList results = - mapper.query(DynamoItemEntity.class, queryExpression); - - return new ArrayList<>(results); + DynamoDbIndex index = this.table.index("idx_global_customerId"); + QueryConditional q = QueryConditional.keyEqualTo(Key.builder().partitionValue(customerId).build()); + Iterator> result = index.query(q).iterator(); + List users = new ArrayList<>(); + + while (result.hasNext()) { + Page userPage = result.next(); + users.addAll(userPage.items()); + } + + return users; } @Override public Optional item(String customerId, String itemId) { - return Optional.of(mapper.load(DynamoItemEntity.class, hashKey(customerId, itemId))); + return Optional.of(this.table.getItem(Key.builder().partitionValue(hashKey(customerId, itemId)).build())); } @Override public void deleteItem(String customerId, String itemId) { - item(customerId, itemId).ifPresentOrElse(this.mapper::delete, + item(customerId, itemId).ifPresentOrElse(this.table::deleteItem, () -> log.warn("Item missing for delete {} -- {}", customerId, itemId)); } @@ -162,7 +168,7 @@ public Optional update(String customerId, String itemId, int q item.setQuantity(quantity); item.setUnitPrice(unitPrice); - this.mapper.save(item); + this.table.updateItem(item); return item; } diff --git a/src/cart/src/test/java/com/amazon/sample/carts/services/DynamoDBCartServiceTests.java b/src/cart/src/test/java/com/amazon/sample/carts/services/DynamoDBCartServiceTests.java index 6bb27e3e2..3cb2622dd 100644 --- a/src/cart/src/test/java/com/amazon/sample/carts/services/DynamoDBCartServiceTests.java +++ b/src/cart/src/test/java/com/amazon/sample/carts/services/DynamoDBCartServiceTests.java @@ -18,8 +18,9 @@ package com.amazon.sample.carts.services; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + import com.amazon.sample.carts.configuration.DynamoDBConfiguration; import com.amazon.sample.carts.configuration.DynamoDBProperties; import org.junit.jupiter.api.Tag; @@ -54,7 +55,7 @@ public class DynamoDBCartServiceTests extends AbstractServiceTests { private DynamoDBCartService service; @Autowired - private AmazonDynamoDB dynamodb; + private DynamoDbClient dynamoDbClient; @Container public static GenericContainer dynamodbContainer = @@ -72,10 +73,10 @@ public CartService getService() { static class TestConfiguration { @Autowired - private AmazonDynamoDB amazonDynamoDB; + private DynamoDbClient dynamoDbClient; @Autowired - private DynamoDBMapper mapper; + private DynamoDbEnhancedClient dynamoDbEnhancedClient; } public static class Initializer implements