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

Fixes #768 #897

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@
*/
package io.awspring.cloud.autoconfigure.dynamodb;

import java.io.IOException;
import java.util.Optional;

import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer;
import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer;
import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration;
import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration;
import io.awspring.cloud.dynamodb.*;
import java.io.IOException;
import java.util.Optional;
import io.awspring.cloud.dynamodb.DefaultDynamoDbTableNameResolver;
import io.awspring.cloud.dynamodb.DefaultDynamoDbTableSchemaResolver;
import io.awspring.cloud.dynamodb.DynamoDbOperations;
import io.awspring.cloud.dynamodb.DynamoDbTableNameResolver;
import io.awspring.cloud.dynamodb.DynamoDbTableSchemaResolver;
import io.awspring.cloud.dynamodb.DynamoDbTemplate;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
Expand All @@ -36,10 +42,14 @@
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClientBuilder;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;
import software.amazon.dax.ClusterDaxAsyncClient;
import software.amazon.dax.ClusterDaxClient;

/**
Expand All @@ -51,7 +61,7 @@
*/
@AutoConfiguration
@EnableConfigurationProperties(DynamoDbProperties.class)
@ConditionalOnClass({ DynamoDbClient.class, DynamoDbEnhancedClient.class, DynamoDbTemplate.class })
@ConditionalOnClass({ DynamoDbClient.class, DynamoDbAsyncClient.class })
@AutoConfigureAfter({ CredentialsProviderAutoConfiguration.class, RegionProviderAutoConfiguration.class })
@ConditionalOnProperty(name = "spring.cloud.aws.dynamodb.enabled", havingValue = "true", matchIfMissing = true)
public class DynamoDbAutoConfiguration {
Expand All @@ -60,39 +70,59 @@ public class DynamoDbAutoConfiguration {
@ConditionalOnClass(name = "software.amazon.dax.ClusterDaxClient")
static class DaxDynamoDbClient {

@ConditionalOnMissingBean
@Bean
public DynamoDbClient dynamoDbClient(DynamoDbProperties properties, AwsCredentialsProvider credentialsProvider,
AwsRegionProvider regionProvider) throws IOException {
DaxProperties daxProperties = properties.getDax();

private software.amazon.dax.Configuration.Builder toAwsDaxConfiguration(DaxProperties daxProperties) {
PropertyMapper propertyMapper = PropertyMapper.get();
software.amazon.dax.Configuration.Builder configuration = software.amazon.dax.Configuration.builder();
propertyMapper.from(daxProperties.getIdleTimeoutMillis()).whenNonNull()
.to(configuration::idleTimeoutMillis);
.to(configuration::idleTimeoutMillis);
propertyMapper.from(daxProperties.getConnectionTtlMillis()).whenNonNull()
.to(configuration::connectionTtlMillis);
.to(configuration::connectionTtlMillis);
propertyMapper.from(daxProperties.getConnectTimeoutMillis()).whenNonNull()
.to(configuration::connectTimeoutMillis);
.to(configuration::connectTimeoutMillis);
propertyMapper.from(daxProperties.getRequestTimeoutMillis()).whenNonNull()
.to(configuration::requestTimeoutMillis);
.to(configuration::requestTimeoutMillis);
propertyMapper.from(daxProperties.getWriteRetries()).whenNonNull().to(configuration::writeRetries);
propertyMapper.from(daxProperties.getReadRetries()).whenNonNull().to(configuration::readRetries);
propertyMapper.from(daxProperties.getClusterUpdateIntervalMillis()).whenNonNull()
.to(configuration::clusterUpdateIntervalMillis);
.to(configuration::clusterUpdateIntervalMillis);
propertyMapper.from(daxProperties.getEndpointRefreshTimeoutMillis()).whenNonNull()
.to(configuration::endpointRefreshTimeoutMillis);
.to(configuration::endpointRefreshTimeoutMillis);
propertyMapper.from(daxProperties.getMaxConcurrency()).whenNonNull().to(configuration::maxConcurrency);
propertyMapper.from(daxProperties.getMaxPendingConnectionAcquires()).whenNonNull()
.to(configuration::maxPendingConnectionAcquires);
.to(configuration::maxPendingConnectionAcquires);
propertyMapper.from(daxProperties.getSkipHostNameVerification()).whenNonNull()
.to(configuration::skipHostNameVerification);
.to(configuration::skipHostNameVerification);

return configuration;
}

@ConditionalOnMissingBean
@Bean
public DynamoDbClient daxDynamoDbClient(DynamoDbProperties properties,
AwsCredentialsProvider credentialsProvider,
AwsRegionProvider regionProvider) throws IOException {
DaxProperties daxProperties = properties.getDax();

software.amazon.dax.Configuration.Builder configuration = toAwsDaxConfiguration(daxProperties);

configuration.region(AwsClientBuilderConfigurer.resolveRegion(properties, regionProvider))
.credentialsProvider(credentialsProvider).url(properties.getDax().getUrl());
return ClusterDaxClient.builder().overrideConfiguration(configuration.build()).build();
}

@ConditionalOnMissingBean
@Bean
public DynamoDbAsyncClient daxDynamoDbAsyncClient(DynamoDbProperties properties,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for PR @driverpt!

You sneaked in async client ;-) Is there a particular reason for that?

As much as we do want to autoconfigure async clients we need to think how to do it consistently in every module so that we don't create beans that consume resources without users knowing about it or needing it.

One of the principles of 3.0 was to not create more than needed in contract to 2.x, so we need to be careful.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because in the project we just upgraded to Spring Boot 3, we use WebFlux + Async Clients :)

I think it's harmless because it has @ConditionalOnBean

AwsCredentialsProvider credentialsProvider,
AwsRegionProvider regionProvider) throws IOException {
DaxProperties daxProperties = properties.getDax();

software.amazon.dax.Configuration.Builder configuration = toAwsDaxConfiguration(daxProperties);

configuration.region(AwsClientBuilderConfigurer.resolveRegion(properties, regionProvider))
.credentialsProvider(credentialsProvider).url(properties.getDax().getUrl());
return ClusterDaxAsyncClient.builder().overrideConfiguration(configuration.build()).build();
}
}

@Conditional(MissingDaxUrlCondition.class)
Expand All @@ -101,31 +131,61 @@ static class StandardDynamoDbClient {

@ConditionalOnMissingBean
@Bean
public DynamoDbClient dynamoDbClient(AwsClientBuilderConfigurer awsClientBuilderConfigurer,
public DynamoDbClient standardDynamoDbClient(AwsClientBuilderConfigurer awsClientBuilderConfigurer,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changing bean names makes it a breaking change, in such case it would have to be included in 3.1.0. Perhaps we can leave bean names as they were?

ObjectProvider<AwsClientCustomizer<DynamoDbClientBuilder>> configurer, DynamoDbProperties properties) {
return awsClientBuilderConfigurer
.configure(DynamoDbClient.builder(), properties, configurer.getIfAvailable()).build();
}
}

@Conditional(MissingDaxUrlCondition.class)
@Configuration(proxyBeanMethods = false)
static class StandardDynamoDbAsyncClient {

@ConditionalOnMissingBean
@Bean
public DynamoDbAsyncClient standardDynamoDbAsyncClient(AwsClientBuilderConfigurer awsClientBuilderConfigurer,
ObjectProvider<AwsClientCustomizer<DynamoDbAsyncClientBuilder>> configurer,
DynamoDbProperties properties) {
return awsClientBuilderConfigurer
.configure(DynamoDbAsyncClient.builder(), properties, configurer.getIfAvailable()).build();
}

}

@ConditionalOnMissingBean
@Bean
public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClient) {
return DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build();
@ConditionalOnClass(name = "software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient")
@Configuration(proxyBeanMethods = false)
static class DynamoDbEnhancedClientConfiguration {
@ConditionalOnMissingBean
@ConditionalOnClass(DynamoDbEnhancedClient.class)
@Bean
public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClient) {
return DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build();
}

@ConditionalOnMissingBean
@ConditionalOnClass(DynamoDbEnhancedAsyncClient.class)
@Bean
public DynamoDbEnhancedAsyncClient dynamoDbEnhancedAsyncClient(DynamoDbAsyncClient dynamoDbClient) {
return DynamoDbEnhancedAsyncClient.builder().dynamoDbClient(dynamoDbClient).build();
}
}

@ConditionalOnMissingBean(DynamoDbOperations.class)
@Bean
public DynamoDbTemplate dynamoDBTemplate(DynamoDbProperties properties,
DynamoDbEnhancedClient dynamoDbEnhancedClient, Optional<DynamoDbTableSchemaResolver> tableSchemaResolver,
Optional<DynamoDbTableNameResolver> tableNameResolver) {
DynamoDbTableSchemaResolver tableSchemaRes = tableSchemaResolver
@ConditionalOnClass(name = "io.awspring.cloud.dynamodb.DynamoDbOperations")
@Configuration(proxyBeanMethods = false)
static class DynamoDbTemplateConfiguration {
@ConditionalOnMissingBean(DynamoDbOperations.class)
@Bean
public DynamoDbTemplate dynamoDBTemplate(DynamoDbProperties properties,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should think about creating DynamoDbAsyncTemplate
This is just a note, not required for this PR.

DynamoDbEnhancedClient dynamoDbEnhancedClient, Optional<DynamoDbTableSchemaResolver> tableSchemaResolver,
Optional<DynamoDbTableNameResolver> tableNameResolver) {
DynamoDbTableSchemaResolver tableSchemaRes = tableSchemaResolver
.orElseGet(DefaultDynamoDbTableSchemaResolver::new);

DynamoDbTableNameResolver tableNameRes = tableNameResolver
DynamoDbTableNameResolver tableNameRes = tableNameResolver
.orElseGet(() -> new DefaultDynamoDbTableNameResolver(properties.getTablePrefix()));
return new DynamoDbTemplate(dynamoDbEnhancedClient, tableSchemaRes, tableNameRes);
return new DynamoDbTemplate(dynamoDbEnhancedClient, tableSchemaRes, tableNameRes);
}
}

static class MissingDaxUrlCondition extends NoneNestedConditions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;
import software.amazon.dax.ClusterDaxAsyncClient;
import software.amazon.dax.ClusterDaxClient;

/**
Expand Down Expand Up @@ -96,6 +98,7 @@ void withDynamoDbClientCustomEndpoint() {
contextRunner.withPropertyValues("spring.cloud.aws.dynamodb.endpoint:http://localhost:8090")
.run(context -> {
assertThat(context).hasSingleBean(DynamoDbClient.class);
assertThat(context).hasSingleBean(DynamoDbAsyncClient.class);
assertThat(context).hasSingleBean(DynamoDbTemplate.class);
assertThat(context).hasSingleBean(DynamoDbEnhancedClient.class);

Expand All @@ -109,6 +112,7 @@ void withDynamoDbClientCustomEndpoint() {
void dynamoDbClientConfiguredSinceNoUrl() {
contextRunner.run(context -> {
assertThat(context).hasSingleBean(DynamoDbClient.class);
assertThat(context).hasSingleBean(DynamoDbAsyncClient.class);
assertThat(context).hasSingleBean(DynamoDbTemplate.class);
assertThat(context).hasSingleBean(DynamoDbEnhancedClient.class);
assertThat(context).doesNotHaveBean(ClusterDaxClient.class);
Expand Down Expand Up @@ -170,6 +174,7 @@ void defaultsAreUsedWhenPropertiesAreNotSet() {
.run(context -> {
ConfiguredDaxClient daxClient = new ConfiguredDaxClient(
context.getBean(ClusterDaxClient.class));
assertThat(context).hasSingleBean(ClusterDaxAsyncClient.class);
assertThat(daxClient.getUrl())
.isEqualTo("dax://something.dax-clusters.us-east-1.amazonaws.com");
assertThat(daxClient.getWriteRetries()).isEqualTo(2);
Expand Down Expand Up @@ -211,6 +216,7 @@ void clusterDaxClient_CustomUrl_DefaultValues() {
.run(context -> {
ConfiguredDaxClient daxClient = new ConfiguredDaxClient(
context.getBean(ClusterDaxClient.class));
assertThat(context).hasSingleBean(ClusterDaxAsyncClient.class);
assertThat(daxClient.getUrl())
.isEqualTo("dax://something.dax-clusters.us-east-1.amazonaws.com");
assertThat(daxClient.getWriteRetries()).isEqualTo(2);
Expand All @@ -237,6 +243,7 @@ void customTableResolverResolverCanBeConfigured() {
assertThat(dynamoDBDynamoDbTableNameResolver)
.isInstanceOf(CustomDynamoDBDynamoDbTableNameResolver.class);

assertThat(context).hasSingleBean(ClusterDaxAsyncClient.class);
});
}

Expand Down
Loading