ACTS Blog Selection
We use machine learning technology to do auto-translation. Click "English" on top navigation bar to check Chinese version.
Announcing overridable client configuration in the Amazon Web Services SDK for Kotlin
We’re excited to announce that the
Initializing a client
All service clients require configuration at initialization time. If you don’t provide configuration explicitly, it may be detected from your runtime environment via
Let’s consider an example with Amazon DynamoDB:
DynamoDbClient.fromEnvironment {
// explicit config
logMode = LogMode.LogResponse
region = "us-west-2"
}.use { ddbClient ->
// print all table names
ddbClient.listTables().tableNames?.forEach(::println)
// print first page of items from "dictionary" table
ddbClient.scan { tableName = "dictionary" }.items?.forEach(::println)
}
In the preceding example, I explicitly set the
Every Amazon Web Services service call I execute from ddbClient
(for example, the listTables
and scan
calls) will use the same configuration that was resolved at initialization time. Normally this is fine because usually I want to make calls in the same region, with the same credentials.
Sometimes, though, I may want to execute some service calls with slightly different config. To accomplish this, I will use the withConfig
method on the service client. Let’s look at a few examples:
Changing retry behavior
I want to insert a bunch of items into a DynamoDB table in bulk, so I write a new function for use in my previous example:
suspend fun bulkLoadWords(ddbClient: DynamoDbClient, fromFile: File) {
fromFile
.readLines()
.windowed(25) // 25 is the max number of DynamoDB batch items
.forEach { page ->
ddbClient.batchWriteItem {
// transform file lines to WriteRequest elements
val items = page.map { word ->
WriteRequest {
putRequest {
item = mapOf(
"language" to AttributeValue.S("en"),
"word" to AttributeValue.S(word),
)
}
}
}
requestItems = mapOf("dictionary" to items)
}
}
}
If my table’s bulkLoadWords
:
// using a client with more retries...
ddbClient.withConfig {
retryStrategy { maxAttempts = 10 }
}.use { patientDdbClient ->
// ...bulk insert items from file
bulkLoadWords(patientDdbClient, File("/tmp/words.txt"))
}
Using withConfig
allows me to retain the settings from my existing ddbClient
(specifically, log mode and region) but override specific settings (in this case, the retry attempts). The withConfig
function returns a new client instance and the use function allows me to scope that overridden client’s lifetime to a small block. That is to say, the patientDdbClient
will be closed when the use block ends.
Enabling additional logging
Sometimes, it may be useful to enable
Let’s consider an example using Amazon Web Services Lambda:
LambdaClient.fromEnvironment {
region = "us-west-2"
}.use { lambdaClient ->
// invoke "sum" function
lambdaClient.invoke {
functionName = "sum"
payload = """ { "x": 2, "y": 3 } """.toByteArray()
}
// using a client with more logging...
lambdaClient.withConfig {
logMode = LogMode.LogRequest + LogMode.LogResponse
}.use { loggingLambdaClient ->
// ...invoke "divide" function
loggingLambdaClient.invoke {
functionName = "divide"
payload = """ { "x": 5, "y": 0 } """.toByteArray()
}
}
}
In the preceding example, my first Lambda invoke
call uses the configuration of lambdaClient
. The second invoke
call uses the overridden configuration, which sets the more verbose wire logging mode.
Switching regions
Amazon Web Services service calls are almost always directed to
Let’s consider an example using Amazon Simple Storage Service (Amazon S3):
data class Upload(val fileName: String, val region: String, val bucket: String)
val uploads = listOf(
Upload("q3-report.pdf" , "us-west-2", "quarterly-reports"),
Upload("q4-report.pdf" , "us-west-2", "quarterly-reports"),
Upload("us-financials.zip", "us-east-1", "us-datasets" ),
Upload("eu-financials.zip", "eu-west-1", "eu-datasets" ),
)
S3Client.fromEnvironment {
region = "us-west-2"
}.use { s3Client ->
// upload the files from the list
uploads.forEach { upload ->
s3Client.withConfig { region = upload.region }.use { regionalS3Client ->
val file = File("/tmp/${upload.fileName}")
regionalS3Client.putObject {
bucket = upload.bucket
key = upload.fileName
body = ByteStream.fromFile(file)
}
}
}
}
In the preceding example, I first define a list of uploads which includes information about the local name of the file, the region to which they should be uploaded, and the bucket name. I then initialize an S3 client and iterate over the list of uploads. For each upload, I invoke putObject
using a regionalized S3 client created by withConfig for the desired region.
Best practices
Overriding client config unlocks some powerful use cases. Here are some tips to make the most of them:
- Use
withConfig
instead of creating additional clients from scratch. Creating a new service client with fromEnvironment detects configuration from your local environment. The creation process may read profiles from the filesystem or make HTTP calls to resolve required configuration values. Although creating a service client with a constructor does not detect configuration, as whenfromEnvironment
is used, it still may read from the file system. Neither approach, however, reuses configuration values from already-running client. For that reason, we recommend usingwithConfig
to create additional service clients when you already have a configured one available. Initialization is much faster and all your existing config is copied to the new client—allowing you to specify only what is different. - Scope clients with the
use
function. CallingwithConfig
creates a new client instance. It’s faster than creating a client from scratch with a constructor orfromEnvironment
but it shares references to certain long-running resources such as credentials providers, an HTTP client engine, etc. Always wrap your usage of these overridden clients inuse
to ensure that they’re properly closed at the end, which frees references to shared resources. Alternatively, you can callclose
on the overridden client directly.
More information
Now that you’ve seen how to override client configuration, we hope you’ll try it out in your own code! The following resources can provide more details to get you started:
-
Developer guide entry on overriding client configuration -
Configuration options in the Amazon Web Services SDK for Kotlin
If you have any questions about how to integrate this feature into your code, feel free to leave a comment below or start a
About the author: