Micronaut Mastery: Binding Request Parameters to POJO
We take a quick look at how Java developers can work with POJOs in their microservices via the Micronaut framework.
Join the DZone community and get the full member experience.
Join For FreeMicronaut supports the RFC-6570 URI template specification to define URI variables in a path definition. The path definition can be a value of the @Controller
annotation or any of the routing annotations for example @Get
or @Post
. We can define a path variable as {?binding*}
to support binding of request parameters to all properties of an object type that is defined as method argument with the name binding
. We can even use the Bean Validation API (JSR380) to validate the values of the request parameters if we add an implementation of this API to our class path.
In the following example controller, we have the method items
with the method argument sorting
of type Sorting
. We want to map the request parameters ascending
and field
to the properties of the Sorting
object. We only have the use the path variable {?sorting*}
to make this happen. We also add the dependency io.micronaut.configuration:micronaut-hibernate-validator
to our class path. If we use Gradle we can add compile("io.micronaut.configuration:micronaut-hibernate-validator")
to our build file.
package mrhaki;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.validation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.Pattern;
import java.util.List;
@Controller("/sample")
@Validated // Enable validation of Sorting properties.
public class SampleController {
private final SampleComponent sampleRepository;
public SampleController(final SampleComponent sampleRepository) {
this.sampleRepository = sampleRepository;
}
// Using the syntax {?sorting*} we can assign request parameters
// to a POJO, where the request parameter name matches a property
// name in the POJO. The name 'must match the argument
// name of our method, which is 'sorting' in our example.
// The properties of the POJO can use the Validation API to
// define constraints and those will be validated if we use
// @Valid for the method argument and @Validated at the class level.
@Get("/{?sorting*}")
public List<Item> items(@Valid final Sorting sorting) {
return sampleRepository.allItems(sorting.getField(), sorting.getDirection());
}
private static class Sorting {
private boolean ascending = true;
@Pattern(regexp = "name|city", message = "Field must have value 'name' or 'city'.")
private String field = "name";
private String getDirection() {
return ascending ? "ASC" : "DESC";
}
public boolean isAscending() {
return ascending;
}
public void setAscending(final boolean ascending) {
this.ascending = ascending;
}
public String getField() {
return field;
}
public void setField(final String field) {
this.field = field;
}
}
}
Let's write a test to check that the binding of the request parameters happens correctly. We use the Micronaut test support for Spock so we can use the @Micronaut
and @MockBean
annotations. We add a dependency on io.micronaut:micronaut-test-spock
to our build, which is testCompile("io.micronaut.test:micronaut-test-spock:1.0.2")
if we use a Gradle build.
package mrhaki
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.http.uri.UriTemplate
import io.micronaut.test.annotation.MicronautTest
import io.micronaut.test.annotation.MockBean
import spock.lang.Specification
import javax.inject.Inject
@MicronautTest
class SampleControllerSpec extends Specification {
// Client to test the /sample endpoint.
@Inject
@Client("/sample")
RxHttpClient httpClient
// Will inject mock created by sampleRepository method.
@Inject
SampleComponent sampleRepository
// Mock for SampleRepository to check method is
// invoked with correct arguments.
@MockBean(SampleRepository)
SampleComponent sampleRepository() {
return Mock(SampleComponent)
}
void "sorting request parameters are bound to Sorting object"() {
given:
// UriTemplate to expand field and ascending request parameters with values.
// E.g. ?field=name&expanding=false.
final requestURI = new UriTemplate("/{?field,ascending}").expand(field: paramField, ascending: paramAscending)
when:
httpClient.toBlocking().exchange(requestURI)
then:
1 * sampleRepository.allItems(sortField, sortDirection) >> []
where:
paramField | paramAscending | sortField | sortDirection
null | null | "name" | "ASC"
null | false | "name" | "DESC"
null | true | "name" | "ASC"
"city" | false | "city" | "DESC"
"city" | true | "city" | "ASC"
"name" | false | "name" | "DESC"
"name" | true | "name" | "ASC"
}
void "invalid sorting field should give error response"() {
given:
final requestURI = new UriTemplate("/{?field,ascending}").expand(field: "invalid")
when:
httpClient.toBlocking().exchange(requestURI)
then:
final HttpClientResponseException clientResponseException = thrown()
clientResponseException.response.status == HttpStatus.BAD_REQUEST
clientResponseException.message == "sorting.field: Field must have value 'name' or 'city'."
}
}
Written with Micronaut 1.0.4.
Published at DZone with permission of Hubert Klein Ikkink, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments