aws-sdk-kotlin
aws-sdk-kotlin copied to clipboard
Configuration via SPI on JVM
Describe the feature
It would be useful to allow auto registration of various SDK configuration on the JVM via SPI (ServiceLoader).
Possible candidates:
- HTTP engine
- Tracing probe (once available)
- Interceptors (once designed and available)
Is your Feature Request related to a problem?
Today customers have to change their code to swap out HTTP engine, insert tracing, or register interceptors (NOTE: some of these are features planned but not yet available today). It would be extremely useful for customers to swap out or install some of these components without requiring code changes (e.g. to test HTTP engine differences, instrument their application, etc).
Proposed Solution
Investigate SPI loading mechanism in Java V2 SDK to see what they allow to be configured this way and determine what (if any) we would want equivalents of. This would be a JVM only feature and require runtime support.
Describe alternative solutions or features you've considered
No response
Acknowledge
- [X] I may be able to implement this feature request
AWS Kotlin SDK version used
N/A
Platform (JVM/JS/Native)
JVM
Operating System and version
N/A
SPI is one possible option, but a bit magical for some cases (random jar on class path changes behaviour). It would be great to not fully internalize all that and provide more flexible options.
For AWS Java SDK have built a Factory object that is exposed as a Spring bean; at configuration time we create various SDK clients via factory.build<S3Client, S3Client.Builder>() - which allows for various customizers to be applied to the builder (those are all registered with the factory).
Working through something similar with Kotlin SDK - its much more challenging as there aren't fixed super-types for the generated builder classes.
Related value from having a Factory concept - consistently providing configuration such as allowed credentials providers; centralized ability to generate/apply scoped-down policies for SaaS applications via StsAssumeRoleCredentialsProvider.
Every client config/builder implements SdkClient/SdkClient.Builder and every client config implements SdkClientConfig/SdkClientConfig.Builder. Everything else is a mixin configuration (e.g. HTTP, identity, observability, etc).
I think what you are looking for may be available already, just a bit different than Java. Take a look at the Example Client Usage from https://github.com/awslabs/aws-sdk-kotlin/pull/814, specifically the vending machine example (c5) and let me know if that's closer to what you are after.
Nice, thanks, that example address the factory issue - creating a builder uniformly (which I can now remove some reflective code for).
It still requires dispatch to each of the mix-ins but that is manageable.
An example in the docs would be helpful ("Centralizing creation / configuration of SDK clients" perhaps)
fyi, end up with this implementation
private fun <
TConfig : SdkClientConfig,
TConfigBuilder : SdkClientConfig.Builder<TConfig>,
TClient : SdkClient,
TClientBuilder : SdkClient.Builder<TConfig, TConfigBuilder, TClient>> internalCreate(
factory: SdkClientFactory<TConfig, TConfigBuilder, TClient, TClientBuilder>,
customizers: List<AwsKotlinSdkClientCustomizer>,
block: TConfigBuilder.() -> Unit
): TClient {
val builder = factory.builder()
val config = builder.config
if (config is AwsSdkClientConfig.Builder) customizers.map { it.awsSdkClientConfigBuilder }
.forEach(config::apply)
if (config is CredentialsProviderConfig.Builder) customizers.map { it.credentialsProviderConfigBuilder }
.forEach(config::apply)
if (config is HttpAuthConfig.Builder) customizers.map { it.httpAuthConfigBuilder }.forEach(config::apply)
if (config is HttpClientConfig.Builder) customizers.map { it.httpClientConfigBuilder }.forEach(config::apply)
if (config is IdempotencyTokenConfig.Builder) customizers.map { it.idempotencyTokenConfigBuilder }
.forEach(config::apply)
if (config is RetryStrategyClientConfig.Builder) customizers.map { it.retryStrategyClientConfigBuilder }
.forEach(config::apply)
if (config is TelemetryConfig.Builder) customizers.map { it.telemetryConfigBuilder }.forEach(config::apply)
builder.config.apply(block)
return builder.build()
}
...combined with creating customizers:
awsKotlinSdkClientCustomizer {
awsSdkClient {
useDualStack = props.useDualStack
useFips = props.useFips
this.region = region
}
credentialsProvider {
credentialsProvider = effectiveCredentialsProvider
}
}
Which all comes together for consumers to use a singleton factory w/ registered customizers to create SDK clients:
val stsClient = factory.create(StsClient) {
// optional at-creation-time client-specific configuration
}