Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:28:30 +08:00
commit 171acedaa4
220 changed files with 85967 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
# Advanced Operations Reference
This document covers advanced DynamoDB operations and patterns.
## Query Operations
### Key Conditions
#### Key.equalTo()
```java
QueryConditional equalTo = QueryConditional
.keyEqualTo(Key.builder()
.partitionValue("customer123")
.build());
```
#### Key.between()
```java
QueryConditional between = QueryConditional
.sortBetween(
Key.builder().partitionValue("customer123").sortValue("2023-01-01").build(),
Key.builder().partitionValue("customer123").sortValue("2023-12-31").build());
```
#### Key.beginsWith()
```java
QueryConditional beginsWith = QueryConditional
.sortKeyBeginsWith(Key.builder()
.partitionValue("customer123")
.sortValue("2023-")
.build());
```
### Filter Expressions
```java
Expression filter = Expression.builder()
.expression("points >= :minPoints AND status = :status")
.putExpressionName("#p", "points")
.putExpressionName("#s", "status")
.putExpressionValue(":minPoints", AttributeValue.builder().n("1000").build())
.putExpressionValue(":status", AttributeValue.builder().s("ACTIVE").build())
.build();
```
### Projection Expressions
```java
Expression projection = Expression.builder()
.expression("customerId, name, email")
.putExpressionName("#c", "customerId")
.putExpressionName("#n", "name")
.putExpressionName("#e", "email")
.build();
```
## Scan Operations
### Pagination
```java
ScanEnhancedRequest request = ScanEnhancedRequest.builder()
.limit(100)
.build();
PaginatedScanIterable<Customer> results = table.scan(request);
results.stream().forEach(page -> {
// Process each page of results
});
```
### Conditional Scan
```java
Expression filter = Expression.builder()
.expression("active = :active")
.putExpressionValue(":active", AttributeValue.builder().bool(true).build())
.build();
return table.scan(r -> r
.filterExpression(filter)
.limit(50))
.items().stream()
.collect(Collectors.toList());
```
## Batch Operations
### Batch Get with Unprocessed Keys
```java
List<Key> keys = customerIds.stream()
.map(id -> Key.builder().partitionValue(id).build())
.collect(Collectors.toList());
ReadBatch.Builder<Customer> batchBuilder = ReadBatch.builder(Customer.class)
.mappedTableResource(table);
keys.forEach(batchBuilder::addGetItem);
BatchGetResultPageIterable result = enhancedClient.batchGetItem(r ->
r.addReadBatch(batchBuilder.build()));
// Handle unprocessed keys
result.stream()
.flatMap(page -> page.unprocessedKeys().entrySet().stream())
.forEach(entry -> {
// Retry logic for unprocessed keys
});
```
### Batch Write with Different Operations
```java
WriteBatch.Builder<Customer> batchBuilder = WriteBatch.builder(Customer.class)
.mappedTableResource(table);
batchBuilder.addPutItem(customer1);
batchBuilder.addDeleteItem(customer2);
batchBuilder.addPutItem(customer3);
enhancedClient.batchWriteItem(r -> r.addWriteBatch(batchBuilder.build()));
```
## Transactions
### Conditional Writes
```java
PutItemEnhancedRequest putRequest = PutItemEnhancedRequest.builder(table)
.item(customer)
.conditionExpression("attribute_not_exists(customerId)")
.build();
table.putItemWithRequestBuilder(putRequest);
```
### Multiple Table Operations
```java
TransactWriteItemsEnhancedRequest request = TransactWriteItemsEnhancedRequest.builder()
.addPutItem(customerTable, customer)
.addPutItem(orderTable, order)
.addUpdateItem(productTable, product)
.addDeleteItem(cartTable, cartKey)
.build();
enhancedClient.transactWriteItems(request);
```
## Conditional Operations
### Condition Expressions
```java
// Check if attribute exists
.setAttribute("conditionExpression", "attribute_not_exists(customerId)")
// Check attribute values
.setAttribute("conditionExpression", "points > :currentPoints")
.setAttribute("expressionAttributeValues", Map.of(
":currentPoints", AttributeValue.builder().n("500").build()))
// Multiple conditions
.setAttribute("conditionExpression", "points > :min AND active = :active")
.setAttribute("expressionAttributeValues", Map.of(
":min", AttributeValue.builder().n("100").build(),
":active", AttributeValue.builder().bool(true).build()))
```
## Error Handling
### Provisioned Throughput Exceeded
```java
try {
table.putItem(customer);
} catch (TransactionCanceledException e) {
// Handle transaction cancellation
} catch (ConditionalCheckFailedException e) {
// Handle conditional check failure
} catch (ResourceNotFoundException e) {
// Handle table not found
} catch (DynamoDbException e) {
// Handle other DynamoDB exceptions
}
```
### Exponential Backoff for Retry
```java
int maxRetries = 3;
long baseDelay = 1000; // 1 second
for (int attempt = 0; attempt < maxRetries; attempt++) {
try {
operation();
break;
} catch (ProvisionedThroughputExceededException e) {
long delay = baseDelay * (1 << attempt);
Thread.sleep(delay);
}
}
```

View File

@@ -0,0 +1,120 @@
# Entity Mapping Reference
This document provides detailed information about entity mapping in DynamoDB Enhanced Client.
## @DynamoDbBean Annotation
The `@DynamoDbBean` annotation marks a class as a DynamoDB entity:
```java
@DynamoDbBean
public class Customer {
// Class implementation
}
```
## Field Annotations
### @DynamoDbPartitionKey
Marks a field as the partition key:
```java
@DynamoDbPartitionKey
public String getCustomerId() {
return customerId;
}
```
### @DynamoDbSortKey
Marks a field as the sort key (used with composite keys):
```java
@DynamoDbSortKey
@DynamoDbAttribute("order_id")
public String getOrderId() {
return orderId;
}
```
### @DynamoDbAttribute
Maps a field to a DynamoDB attribute with custom name:
```java
@DynamoDbAttribute("customer_name")
public String getName() {
return name;
}
```
### @DynamoDbSecondaryPartitionKey
Marks a field as a partition key for a Global Secondary Index:
```java
@DynamoDbSecondaryPartitionKey(indexNames = "category-index")
public String getCategory() {
return category;
}
```
### @DynamoDbSecondarySortKey
Marks a field as a sort key for a Global Secondary Index:
```java
@DynamoDbSecondarySortKey(indexNames = "category-index")
public BigDecimal getPrice() {
return price;
}
```
### @DynamoDbConvertedBy
Custom attribute conversion:
```java
@DynamoDbConvertedBy(LocalDateTimeConverter.class)
public LocalDateTime getCreatedAt() {
return createdAt;
}
```
## Supported Data Types
The enhanced client automatically handles the following data types:
- String → S (String)
- Integer, Long → N (Number)
- BigDecimal → N (Number)
- Boolean → BOOL
- LocalDateTime → S (ISO-8601 format)
- LocalDate → S (ISO-8601 format)
- UUID → S (String)
- Enum → S (String representation)
- Custom types with converters
## Custom Converters
Create custom converters for complex data types:
```java
public class LocalDateTimeConverter extends AttributeConverter<LocalDateTime, String> {
@Override
public String transformFrom(LocalDateTime input) {
return input.toString();
}
@Override
public LocalDateTime transformTo(String input) {
return LocalDateTime.parse(input);
}
@Override
public AttributeValue transformToAttributeValue(String input) {
return AttributeValue.builder().s(input).build();
}
@Override
public String transformFromAttributeValue(AttributeValue attributeValue) {
return attributeValue.s();
}
}
```

View File

@@ -0,0 +1,377 @@
# Spring Boot Integration Reference
This document provides detailed information about integrating DynamoDB with Spring Boot applications.
## Configuration
### Basic Configuration
```java
@Configuration
public class DynamoDbConfiguration {
@Bean
@Profile("local")
public DynamoDbClient dynamoDbClient() {
return DynamoDbClient.builder()
.region(Region.US_EAST_1)
.build();
}
@Bean
@Profile("prod")
public DynamoDbClient dynamoDbClientProd(
@Value("${aws.region}") String region,
@Value("${aws.accessKeyId}") String accessKeyId,
@Value("${aws.secretAccessKey}") String secretAccessKey) {
return DynamoDbClient.builder()
.region(Region.of(region))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(accessKeyId, secretAccessKey)))
.build();
}
@Bean
public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClient) {
return DynamoDbEnhancedClient.builder()
.dynamoDbClient(dynamoDbClient)
.build();
}
}
```
### Properties Configuration
`application-local.properties`:
```properties
aws.region=us-east-1
```
`application-prod.properties`:
```properties
aws.region=us-east-1
aws.accessKeyId=${AWS_ACCESS_KEY_ID}
aws.secretAccessKey=${AWS_SECRET_ACCESS_KEY}
```
## Repository Pattern Implementation
### Base Repository Interface
```java
public interface DynamoDbRepository<T> {
void save(T entity);
Optional<T> findById(Object partitionKey);
Optional<T> findById(Object partitionKey, Object sortKey);
void delete(Object partitionKey);
void delete(Object partitionKey, Object sortKey);
List<T> findAll();
List<T> findAll(int limit);
boolean existsById(Object partitionKey);
boolean existsById(Object partitionKey, Object sortKey);
}
public interface CustomerRepository extends DynamoDbRepository<Customer> {
List<Customer> findByEmail(String email);
List<Customer> findByPointsGreaterThan(Integer minPoints);
}
```
### Generic Repository Implementation
```java
@Repository
public class GenericDynamoDbRepository<T> implements DynamoDbRepository<T> {
private final DynamoDbTable<T> table;
@SuppressWarnings("unchecked")
public GenericDynamoDbRepository(DynamoDbEnhancedClient enhancedClient,
Class<T> entityClass,
String tableName) {
this.table = enhancedClient.table(tableName, TableSchema.fromBean(entityClass));
}
@Override
public void save(T entity) {
table.putItem(entity);
}
@Override
public Optional<T> findById(Object partitionKey) {
Key key = Key.builder().partitionValue(partitionKey).build();
return Optional.ofNullable(table.getItem(key));
}
@Override
public Optional<T> findById(Object partitionKey, Object sortKey) {
Key key = Key.builder()
.partitionValue(partitionKey)
.sortValue(sortKey)
.build();
return Optional.ofNullable(table.getItem(key));
}
@Override
public void delete(Object partitionKey) {
Key key = Key.builder().partitionValue(partitionKey).build();
table.deleteItem(key);
}
@Override
public List<T> findAll() {
return table.scan().items().stream()
.collect(Collectors.toList());
}
@Override
public List<T> findAll(int limit) {
return table.scan(ScanEnhancedRequest.builder().limit(limit).build())
.items().stream()
.collect(Collectors.toList());
}
}
```
### Specific Repository Implementation
```java
@Repository
public class CustomerRepositoryImpl implements CustomerRepository {
private final DynamoDbTable<Customer> customerTable;
public CustomerRepositoryImpl(DynamoDbEnhancedClient enhancedClient) {
this.customerTable = enhancedClient.table(
"Customers",
TableSchema.fromBean(Customer.class));
}
@Override
public List<Customer> findByEmail(String email) {
Expression filter = Expression.builder()
.expression("email = :email")
.putExpressionValue(":email", AttributeValue.builder().s(email).build())
.build();
return customerTable.scan(r -> r.filterExpression(filter))
.items().stream()
.collect(Collectors.toList());
}
@Override
public List<Customer> findByPointsGreaterThan(Integer minPoints) {
Expression filter = Expression.builder()
.expression("points >= :minPoints")
.putExpressionValue(":minPoints", AttributeValue.builder().n(minPoints.toString()).build())
.build();
return customerTable.scan(r -> r.filterExpression(filter))
.items().stream()
.collect(Collectors.toList());
}
}
```
## Service Layer Implementation
### Service with Transaction Management
```java
@Service
@Transactional
public class CustomerService {
private final CustomerRepository customerRepository;
private final OrderRepository orderRepository;
private final DynamoDbEnhancedClient enhancedClient;
public CustomerService(CustomerRepository customerRepository,
OrderRepository orderRepository,
DynamoDbEnhancedClient enhancedClient) {
this.customerRepository = customerRepository;
this.orderRepository = orderRepository;
this.enhancedClient = enhancedClient;
}
public void createCustomerWithOrder(Customer customer, Order order) {
// Use transaction for atomic operation
enhancedClient.transactWriteItems(r -> r
.addPutItem(getCustomerTable(), customer)
.addPutItem(getOrderTable(), order));
}
private DynamoDbTable<Customer> getCustomerTable() {
return enhancedClient.table("Customers", TableSchema.fromBean(Customer.class));
}
private DynamoDbTable<Order> getOrderTable() {
return enhancedClient.table("Orders", TableSchema.fromBean(Order.class));
}
}
```
### Async Operations
```java
@Service
public class AsyncCustomerService {
private final DynamoDbEnhancedClient enhancedClient;
public CompletableFuture<Void> saveCustomerAsync(Customer customer) {
return CompletableFuture.runAsync(() -> {
DynamoDbTable<Customer> table = enhancedClient.table(
"Customers",
TableSchema.fromBean(Customer.class));
table.putItem(customer);
});
}
public CompletableFuture<List<Customer>> findCustomersByPointsAsync(Integer minPoints) {
return CompletableFuture.supplyAsync(() -> {
Expression filter = Expression.builder()
.expression("points >= :minPoints")
.putExpressionValue(":minPoints", AttributeValue.builder().n(minPoints.toString()).build())
.build();
DynamoDbTable<Customer> table = enhancedClient.table(
"Customers",
TableSchema.fromBean(Customer.class));
return table.scan(r -> r.filterExpression(filter))
.items().stream()
.collect(Collectors.toList());
});
}
}
```
## Testing with LocalStack
### Test Configuration
```java
@TestConfiguration
@ContextConfiguration(classes = {LocalStackDynamoDbConfig.class})
public class DynamoDbTestConfig {
@Bean
public DynamoDbClient dynamoDbClient() {
return LocalStackDynamoDbConfig.dynamoDbClient();
}
@Bean
public DynamoDbEnhancedClient dynamoDbEnhancedClient() {
return DynamoDbEnhancedClient.builder()
.dynamoDbClient(dynamoDbClient())
.build();
}
}
@SpringBootTest(classes = {DynamoDbTestConfig.class})
@Import(DynamoDbTestConfig.class)
public class CustomerRepositoryIntegrationTest {
@Autowired
private DynamoDbEnhancedClient enhancedClient;
@BeforeEach
void setUp() {
// Clean up test data
clearTestData();
}
@Test
void testCustomerOperations() {
// Test implementation
}
}
```
### LocalStack Container Setup
```java
public class LocalStackDynamoDbConfig {
@Container
static LocalStackContainer localstack = new LocalStackContainer(
DockerImageName.parse("localstack/localstack:3.0"))
.withServices(LocalStackContainer.Service.DYNAMODB);
@Bean
@DynamicPropertySource
public static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("aws.region", () -> Region.US_EAST_1.toString());
registry.add("aws.accessKeyId", () -> localstack.getAccessKey());
registry.add("aws.secretAccessKey", () -> localstack.getSecretKey());
registry.add("aws.endpoint",
() -> localstack.getEndpointOverride(LocalStackContainer.Service.DYNAMODB).toString());
}
@Bean
public DynamoDbClient dynamoDbClient(
@Value("${aws.region}") String region,
@Value("${aws.accessKeyId}") String accessKeyId,
@Value("${aws.secretAccessKey}") String secretAccessKey,
@Value("${aws.endpoint}") String endpoint) {
return DynamoDbClient.builder()
.region(Region.of(region))
.endpointOverride(URI.create(endpoint))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(accessKeyId, secretAccessKey)))
.build();
}
}
```
## Health Check Integration
### Custom Health Indicator
```java
@Component
public class DynamoDbHealthIndicator implements HealthIndicator {
private final DynamoDbClient dynamoDbClient;
public DynamoDbHealthIndicator(DynamoDbClient dynamoDbClient) {
this.dynamoDbClient = dynamoDbClient;
}
@Override
public Health health() {
try {
dynamoDbClient.listTables();
return Health.up()
.withDetail("region", dynamoDbClient.serviceClientConfiguration().region())
.build();
} catch (Exception e) {
return Health.down()
.withException(e)
.build();
}
}
}
```
## Metrics Collection
### Micrometer Integration
```java
@Component
public class DynamoDbMetricsCollector {
private final DynamoDbClient dynamoDbClient;
private final MeterRegistry meterRegistry;
@EventListener
public void handleDynamoDbOperation(DynamoDbOperationEvent event) {
Timer.Sample sample = Timer.start();
sample.stop(Timer.builder("dynamodb.operation")
.tag("operation", event.getOperation())
.tag("table", event.getTable())
.register(meterRegistry));
}
}
public class DynamoDbOperationEvent {
private String operation;
private String table;
private long duration;
// Getters and setters
}
```

View File

@@ -0,0 +1,407 @@
# Testing Strategies for DynamoDB
This document provides comprehensive testing strategies for DynamoDB applications using the AWS SDK for Java 2.x.
## Unit Testing with Mocks
### Mocking DynamoDbClient
```java
@ExtendWith(MockitoExtension.class)
class CustomerServiceTest {
@Mock
private DynamoDbClient dynamoDbClient;
@Mock
private DynamoDbEnhancedClient enhancedClient;
@Mock
private DynamoDbTable<Customer> customerTable;
@InjectMocks
private CustomerService customerService;
@Test
void saveCustomer_ShouldReturnSavedCustomer() {
// Arrange
Customer customer = new Customer("123", "John Doe", "john@example.com");
when(enhancedClient.table(anyString(), any(TableSchema.class)))
.thenReturn(customerTable);
when(customerTable.putItem(customer))
.thenReturn(null);
// Act
Customer result = customerService.saveCustomer(customer);
// Assert
assertNotNull(result);
assertEquals("123", result.getCustomerId());
verify(customerTable).putItem(customer);
}
@Test
void getCustomer_NotFound_ShouldReturnEmpty() {
// Arrange
when(enhancedClient.table(anyString(), any(TableSchema.class)))
.thenReturn(customerTable);
when(customerTable.getItem(any(Key.class)))
.thenReturn(null);
// Act
Optional<Customer> result = customerService.getCustomer("123");
// Assert
assertFalse(result.isPresent());
verify(customerTable).getItem(any(Key.class));
}
}
```
### Testing Query Operations
```java
@Test
void queryCustomersByStatus_ShouldReturnMatchingCustomers() {
// Arrange
List<Customer> mockCustomers = List.of(
new Customer("1", "Alice", "alice@example.com"),
new Customer("2", "Bob", "bob@example.com")
);
DynamoDbTable<Customer> mockTable = mock(DynamoDbTable.class);
DynamoDbIndex<Customer> mockIndex = mock(DynamoDbIndex.class);
QueryEnhancedRequest queryRequest = QueryEnhancedRequest.builder()
.queryConditional(QueryConditional.keyEqualTo(Key.builder()
.partitionValue("ACTIVE")
.build()))
.build();
when(enhancedClient.table("Customers", TableSchema.fromBean(Customer.class)))
.thenReturn(mockTable);
when(mockTable.index("status-index"))
.thenReturn(mockIndex);
when(mockIndex.query(queryRequest))
.thenReturn(PaginatedQueryIterable.from(mock(Customer.class), mock(QueryResponseEnhanced.class)));
QueryResponseEnhanced mockResponse = mock(QueryResponseEnhanced.class);
when(mockResponse.items())
.thenReturn(mockCustomers.stream());
when(mockIndex.query(any(QueryEnhancedRequest.class)))
.thenReturn(PaginatedQueryIterable.from(mock(Customer.class), mockResponse));
// Act
List<Customer> result = customerService.findByStatus("ACTIVE");
// Assert
assertEquals(2, result.size());
verify(mockIndex).query(any(QueryEnhancedRequest.class));
}
```
## Integration Testing with Testcontainers
### LocalStack Setup
```java
@Testcontainers
@SpringBootTest
@AutoConfigureMockMvc
class DynamoDbIntegrationTest {
@Container
static LocalStackContainer localstack = new LocalStackContainer(
DockerImageName.parse("localstack/localstack:3.0"))
.withServices(LocalStackContainer.Service.DYNAMODB);
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("aws.region", () -> Region.US_EAST_1.toString());
registry.add("aws.accessKeyId", () -> localstack.getAccessKey());
registry.add("aws.secretAccessKey", () -> localstack.getSecretKey());
registry.add("aws.endpoint",
() -> localstack.getEndpointOverride(LocalStackContainer.Service.DYNAMODB).toString());
}
@Autowired
private DynamoDbEnhancedClient enhancedClient;
@BeforeEach
void setup() {
createTestTable();
}
@Test
void testCustomerCRUDOperations() {
// Test create
Customer customer = new Customer("test-123", "Test User", "test@example.com");
enhancedClient.table("Customers", TableSchema.fromBean(Customer.class))
.putItem(customer);
// Test read
Customer retrieved = enhancedClient.table("Customers", TableSchema.fromBean(Customer.class))
.getItem(Key.builder().partitionValue("test-123").build());
assertNotNull(retrieved);
assertEquals("Test User", retrieved.getName());
// Test update
customer.setPoints(1000);
enhancedClient.table("Customers", TableSchema.fromBean(Customer.class))
.putItem(customer);
// Test delete
enhancedClient.table("Customers", TableSchema.fromBean(Customer.class))
.deleteItem(Key.builder().partitionValue("test-123").build());
}
private void createTestTable() {
DynamoDbClient client = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.DYNAMODB))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())))
.build();
CreateTableRequest request = CreateTableRequest.builder()
.tableName("Customers")
.keySchema(KeySchemaElement.builder()
.attributeName("customerId")
.keyType(KeyType.HASH)
.build())
.attributeDefinitions(AttributeDefinition.builder()
.attributeName("customerId")
.attributeType(ScalarAttributeType.S)
.build())
.provisionedThroughput(ProvisionedThroughput.builder()
.readCapacityUnits(5L)
.writeCapacityUnits(5L)
.build())
.build();
client.createTable(request);
waiterForTableActive(client, "Customers");
}
private void waiterForTableActive(DynamoDbClient client, String tableName) {
Waiter waiter = client.waiter();
CreateTableResponse response = client.createTable(request);
waiter.waitUntilTableExists(r -> r
.tableName(tableName)
.maxWait(Duration.ofSeconds(30)));
try {
waiter.waitUntilTableExists(r -> r.tableName(tableName));
} catch (WaiterTimeoutException e) {
throw new RuntimeException("Table creation timed out", e);
}
}
}
```
### Testcontainers with H2 Migration
```java
@SpringBootTest
@Testcontainers
@AutoConfigureDataJpa
class CustomerRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void postgresProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private CustomerRepository customerRepository;
@Autowired
private DynamoDbEnhancedClient dynamoDbClient;
@Test
void testRepositoryWithRealDatabase() {
// Test with real database
Customer customer = new Customer("123", "Test User", "test@example.com");
customerRepository.save(customer);
Customer retrieved = customerRepository.findById("123").orElse(null);
assertNotNull(retrieved);
assertEquals("Test User", retrieved.getName());
}
}
```
## Performance Testing
### Load Testing with Gatling
```java
class CustomerSimulation extends Simulation {
HttpProtocolBuilder httpProtocolBuilder = http
.baseUrl("http://localhost:8080")
.acceptHeader("application/json");
ScenarioBuilder scn = scenario("Customer Operations")
.exec(http("create_customer")
.post("/api/customers")
.body(StringBody(
"""{
"customerId": "test-123",
"name": "Test User",
"email": "test@example.com"
}"""))
.asJson()
.check(status().is(201)))
.exec(http("get_customer")
.get("/api/customers/test-123")
.check(status().is(200)));
{
setUp(
scn.injectOpen(
rampUsersPerSec(10).to(100).during(60),
constantUsersPerSec(100).during(120)
)
).protocols(httpProtocolBuilder);
}
}
```
### Microbenchmark Testing
```java
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Benchmark)
public class DynamoDbPerformanceBenchmark {
private DynamoDbEnhancedClient enhancedClient;
private DynamoDbTable<Customer> customerTable;
private Customer testCustomer;
@Setup
public void setup() {
enhancedClient = DynamoDbEnhancedClient.builder()
.dynamoDbClient(DynamoDbClient.builder().build())
.build();
customerTable = enhancedClient.table("Customers", TableSchema.fromBean(Customer.class));
testCustomer = new Customer("benchmark-123", "Benchmark User", "benchmark@example.com");
}
@Benchmark
public void testPutItem() {
customerTable.putItem(testCustomer);
}
@Benchmark
public void testGetItem() {
customerTable.getItem(Key.builder().partitionValue("benchmark-123").build());
}
@Benchmark
public void testQuery() {
customerTable.scan().items().stream().collect(Collectors.toList());
}
}
```
## Property-Based Testing
### Using jqwik
```java
@Property
@Report(Reporting.GENERATED)
void customerSerializationShouldBeConsistent(
@ForAll("customers") Customer customer
) {
// When
String serialized = serializeCustomer(customer);
Customer deserialized = deserializeCustomer(serialized);
// Then
assertEquals(customer.getCustomerId(), deserialized.getCustomerId());
assertEquals(customer.getName(), deserialized.getName());
assertEquals(customer.getEmail(), deserialized.getEmail());
}
@Provide
Arbitrary<Customer> customers() {
return Arbitraries.one(
Arbitraries.of("customer-", "user-", "client-").string()
).map(id -> new Customer(
id + Arbitraries.integers().between(1000, 9999).sample(),
Arbitraries.strings().ofLength(10).sample(),
Arbitraries.strings().email().sample()
));
}
```
## Test Data Management
### Test Data Factory
```java
@Component
public class TestDataFactory {
private final DynamoDbEnhancedClient enhancedClient;
@Autowired
public TestDataFactory(DynamoDbEnhancedClient enhancedClient) {
this.enhancedClient = enhancedClient;
}
public Customer createTestCustomer(String id) {
Customer customer = new Customer(
id != null ? id : UUID.randomUUID().toString(),
"Test User",
"test@example.com"
);
customer.setPoints(1000);
customer.setCreatedAt(LocalDateTime.now());
enhancedClient.table("Customers", TableSchema.fromBean(Customer.class))
.putItem(customer);
return customer;
}
public void cleanupTestData() {
// Implementation to clean up test data
}
}
```
### Test Database Configuration
```java
@TestConfiguration
public class TestDataConfig {
@Bean
public TestDataCleaner testDataCleaner() {
return new TestDataCleaner();
}
}
@Component
public class TestDataCleaner {
private final DynamoDbClient dynamoDbClient;
@EventListener(ApplicationReadyEvent.class)
public void cleanup() {
// Clean up test data before each test run
}
}
```