Problem: I'm using an AWS DynamoDb Enhanced Java client V2 in Kotlin (the details of the client might not be important), where I want to make a BatchWriteItemEnhancedRequest
containing a bunch of requests. The standard usage according to the docs goes:
val batchWriteItemEnhancedRequest = BatchWriteItemEnhancedRequest.builder()
.addWriteBatch(
WriteBatch.builder(MyClass::class.java)
.mappedTableResource(myMappedTable)
.addPutItem(putRequest1)
.addPutItem(putRequest2)
.addPutItem(putRequest3)
.build()
)
.build()
Obviously this can't handle an anonymous list of putRequest
items, only some known discrete values like putRequest1
and putRequest2
.
The builder for WriteBatch
doesn't seem to allow adding lists of requests: doc
The only way to do this, as I can see, is making a for-loop that goes:
var writeBatchBuilder = WriteBatch.builder(MyClass::class.java)
.mappedTableResource(myMappedTable)
for (request in putItemRequests){
writeBatchBuilder = writeBatchBuilder.addPutItem(request)
}
val writeBatch = writeBatchBuilder.build()
Which seems horrible to me. There's got to be a more idiomatic way to do this right?
CodePudding user response:
Given an instance of WriteBatch.Builder
, there's no need to explicit need to chain the DSL, so your writeBatchBuilder
can be a val
. Each builder function mutates the internal state of the WriteBatch.Builder
instance, and the returned value is just for convenience - source code.
You can therefore simplify your for-loop and make writeBatchBuilder
a val
:
val writeBatchBuilder = WriteBatch.builder(MyClass::class.java)
.mappedTableResource(myMappedTable)
for (request in putItemRequests){
writeBatchBuilder.addPutItem(request)
}
val writeBatch = writeBatchBuilder.build()
Scope function
Going one step further, scope functions are a nice way of converting Java DSLs into something more Kotlin-esque.
(For a more visual guide to scope functions, and Kotlin in general, check out https://typealias.com/start/kotlin-scopes-and-scope-functions/)
apply {}
will turn the created WriteBatch.Builder
into the receiver, meaning you don't need to have a variable for the builder, you can just use this
.
val writeBatch = WriteBatch.builder(MyClass::class.java).apply {
mappedTableResource(myMappedTable)
putItemRequests.forEach { request ->
addPutItem(request)
}
}.build()
(I also changed the for loop into a forEach {}
- which is more Kotlin-ey.)
Custom helper function
The next improvement is to encapsulate the apply {}
logic inside of a helper function, writeBatchBuilder()
, so the boilerplate is hidden away, which makes re-using the function easier.
Using inline
and reified T
means there's no need to define ::class.java
every time - instead the class can be defined as a type-parameter, and the class can be omitted if Kotlin can infer the type.
Note that the receiver of the builder
arg is WriteBatch.Builder
, just like how the apply {}
scope function changes the receiver to be the builder.
inline fun <reified T> buildWriteBatch(
builder: WriteBatch.Builder<T>.() -> Unit
) : WriteBatch<T> {
return WriteBatch.builder(T::class.java).apply(builder).build()
}
Example usage:
val writeBatch = buildWriteBatch<MyClass> {
mappedTableResource(myMappedTable)
putItemRequests.forEach { request ->
addPutItem(request)
}
}
// no need for explicit typing on buildWriteBatch - Kotlin can infer the type
val writeBatch2: WriteBatch<MyClass> = buildWriteBatch {}
(For examples of this approach in Kotlin stdlib, see the collection builder functions)
CodePudding user response:
In this case extension function function may come handy. Lets assume, that WriteBatch.builder(MyClass::class.java)
has type WriteBatch.Builder
maybe with some generics, which is hard to say from example.
val writeBatch = WriteBatch.builder(MyClass::class.java)
.mappedTableResource(myMappedTable)
.addItems(putItemRequests)
.build()
// I did use Any for items type, change it according to your need
fun WriteBatch.Builder.addItems(items: List<Any>) {
items.forEach { item -> addPutItem(item) }
}