AWS Developer Tools Blog
Consumer Builders in the AWS SDK for Java v2
The AWS SDK for Java v2 introduced immutable models which in turn necessitated using a builder
to create request/response objects. Builders are a common pattern for working with immutable objects, they allow building up the state of an object over time and then calling build
to create an immutable representation of the object. They also have the benefit of enabling an API to evolve (e.g. new properties added) without breaking existing customers as would be the case with an all-arguments constructor. However there is a downside – a loss of brevity; using builders can become verbose, especially when trying to create nested structures. Let’s take a look at an example using the AWS SDK for Java v2’s builder pattern to create a Simple Email Service request:
Classic Builder Pattern
sesClient.sendEmail(
SendEmailRequest.builder()
.destination(Destination.builder()
.toAddresses("to-email@domain.com")
.bccAddresses("bcc-email@domain.com")
.build())
.replyToAddresses("reply-to@domain.com")
.message(Message.builder()
.subject(Content.builder()
.charset("UTF-8")
.data("Subject Line")
.build())
.body(Body.builder()
.text(Content.builder()
.data("The body of the email")
.charset("UTF-8")
.build())
.build())
.build())
.build());
Here each sub-structure in the hierarchy needs to have its own builder
created (unless it’s a simple scalar type like a String
), then at the end a chain of repeated build
methods need to be called in order to actually create desired object.
The above snippet of code uses the standard sendEmail
method on the SesClient
which has the following signature:
SendEmailResponse sendEmail(SendEmailRequest sendEmailRequest)
Consumer Builder Pattern
The AWS SDK for Java v2 includes overloaded helper methods, nicknamed ‘consumer builder’ methods, that accept a java.util.function.Consumer
of the corresponding complex-type Builder
class for the method in question. The above sendEmail
method has an overload that looks like:
SendEmailResponse sendEmail(Consumer<SendEmailRequest.Builder> sendEmailRequest)
Using the power of Java 8’s short-hand lambda syntax we can re-write the above example as follows:
sesClient.sendEmail(
email -> email.destination(d -> d.toAddresses("to-email@domain.com")
.bccAddresses("bcc-email@domain.com"))
.replyToAddresses("reply-to@domain.com")
.message(m -> m.subject(s -> s.charset("UTF-8")
.data("Subject Line"))
.body(b -> b.text(t -> t.data("The body of the email")
.charset("UTF-8")))));
Here there’s no need to find the Builder
class for each type that needs to be built, and no chain of ugly build
methods. The SDK takes care of instantiating the builder object, and then calling build on it at the end. This pattern also plays nicely with JVM languages that have implicit parameter patterns like Kotlin and Groovy:
sesClient.sendEmail {
it.destination {
it.toAddresses("to-email@domain.com")
.bccAddresses("bcc-email@domain.com")
}.replyToAddresses("reply-to@domain.com")
.message {
it.subject {
it.charset("UTF-8")
.data("Subject Line")
}.body {
it.text {
it.data("The body of the email")
.charset("UTF-8")
}
}
}
}
Conclusion
As always check out the code on GitHub, chat to the SDK team on Gitter or provide any feedback/comments/issues via the GitHub issues page.