Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Migrated carts service to AWS SDK for Java v2 #401

Merged
merged 2 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions src/cart/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@
<mapstruct.version>1.5.5.Final</mapstruct.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.21.45</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -69,15 +81,17 @@
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-dynamodb</artifactId>
<version>1.12.603</version>
</dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-sts</artifactId>
<version>1.12.578</version>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb-enhanced</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<DynamoItemEntity> table;

static final TableSchema<DynamoItemEntity> 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"));
}
}

Expand All @@ -102,7 +106,9 @@ public List<? extends ItemEntity> getItems() {
public void delete(String customerId) {
List<DynamoItemEntity> items = items(customerId);

mapper.batchDelete(items);
items.forEach(item -> {
this.table.deleteItem(item);
});
}

@Override
Expand All @@ -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);
Expand All @@ -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<DynamoItemEntity> items(String customerId) {
final DynamoItemEntity gsiKeyObj = new DynamoItemEntity();
gsiKeyObj.setCustomerId(customerId);
final DynamoDBQueryExpression<DynamoItemEntity> queryExpression =
new DynamoDBQueryExpression<>();
queryExpression.setHashKeyValues(gsiKeyObj);
queryExpression.setIndexName("idx_global_customerId");
queryExpression.setConsistentRead(false); // cannot use consistent read on GSI
final PaginatedQueryList<DynamoItemEntity> results =
mapper.query(DynamoItemEntity.class, queryExpression);

return new ArrayList<>(results);
DynamoDbIndex<DynamoItemEntity> index = this.table.index("idx_global_customerId");
QueryConditional q = QueryConditional.keyEqualTo(Key.builder().partitionValue(customerId).build());
Iterator<Page<DynamoItemEntity>> result = index.query(q).iterator();
List<DynamoItemEntity> users = new ArrayList<>();

while (result.hasNext()) {
Page<DynamoItemEntity> userPage = result.next();
users.addAll(userPage.items());
}

return users;
}

@Override
public Optional<DynamoItemEntity> 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));
}
Expand All @@ -162,7 +168,7 @@ public Optional<DynamoItemEntity> update(String customerId, String itemId, int q
item.setQuantity(quantity);
item.setUnitPrice(unitPrice);

this.mapper.save(item);
this.table.updateItem(item);

return item;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -54,7 +55,7 @@ public class DynamoDBCartServiceTests extends AbstractServiceTests {
private DynamoDBCartService service;

@Autowired
private AmazonDynamoDB dynamodb;
private DynamoDbClient dynamoDbClient;

@Container
public static GenericContainer dynamodbContainer =
Expand All @@ -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
Expand Down