# 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 { void save(T entity); Optional findById(Object partitionKey); Optional findById(Object partitionKey, Object sortKey); void delete(Object partitionKey); void delete(Object partitionKey, Object sortKey); List findAll(); List findAll(int limit); boolean existsById(Object partitionKey); boolean existsById(Object partitionKey, Object sortKey); } public interface CustomerRepository extends DynamoDbRepository { List findByEmail(String email); List findByPointsGreaterThan(Integer minPoints); } ``` ### Generic Repository Implementation ```java @Repository public class GenericDynamoDbRepository implements DynamoDbRepository { private final DynamoDbTable table; @SuppressWarnings("unchecked") public GenericDynamoDbRepository(DynamoDbEnhancedClient enhancedClient, Class entityClass, String tableName) { this.table = enhancedClient.table(tableName, TableSchema.fromBean(entityClass)); } @Override public void save(T entity) { table.putItem(entity); } @Override public Optional findById(Object partitionKey) { Key key = Key.builder().partitionValue(partitionKey).build(); return Optional.ofNullable(table.getItem(key)); } @Override public Optional 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 findAll() { return table.scan().items().stream() .collect(Collectors.toList()); } @Override public List 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 customerTable; public CustomerRepositoryImpl(DynamoDbEnhancedClient enhancedClient) { this.customerTable = enhancedClient.table( "Customers", TableSchema.fromBean(Customer.class)); } @Override public List 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 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 getCustomerTable() { return enhancedClient.table("Customers", TableSchema.fromBean(Customer.class)); } private DynamoDbTable getOrderTable() { return enhancedClient.table("Orders", TableSchema.fromBean(Order.class)); } } ``` ### Async Operations ```java @Service public class AsyncCustomerService { private final DynamoDbEnhancedClient enhancedClient; public CompletableFuture saveCustomerAsync(Customer customer) { return CompletableFuture.runAsync(() -> { DynamoDbTable table = enhancedClient.table( "Customers", TableSchema.fromBean(Customer.class)); table.putItem(customer); }); } public CompletableFuture> findCustomersByPointsAsync(Integer minPoints) { return CompletableFuture.supplyAsync(() -> { Expression filter = Expression.builder() .expression("points >= :minPoints") .putExpressionValue(":minPoints", AttributeValue.builder().n(minPoints.toString()).build()) .build(); DynamoDbTable 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 } ```