Skip to content

Commit

Permalink
Migrated carts service to AWS SDK v2
Browse files Browse the repository at this point in the history
  • Loading branch information
niallthomson committed Dec 13, 2023
1 parent af06ac2 commit 1706018
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 105 deletions.
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.20.125</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

0 comments on commit 1706018

Please sign in to comment.