Search

Dark theme | Light theme
Showing posts with label MicronautMastery:Controller. Show all posts
Showing posts with label MicronautMastery:Controller. Show all posts

March 27, 2019

Micronaut Mastery: Parse String Value With Kb/Mb/Gb To Number

Micronaut can convert String values defined as a number followed by (case-insensitive) KB/MB/GB to a number value in certain cases. The conversion service in Micronaut supports the @ReadableBytes annotation that we can apply to a method parameter. Micronaut will then parse the String value and convert it to a number. The value 1Kb is converted to 1024. We can use this for example in a configuration class or path variable in a controller.

In the following example we have a configuration class annotated with @ConfigurationProperties with the property maxFileSize. We use the @ReadableBytes annotation to support setting the value with a String value:

package mrhaki;

import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.convert.format.ReadableBytes;

@ConfigurationProperties(SampleConfig.SAMPLE_CONFIG_PREFIX)
public class SampleConfig {
    
    public static final String SAMPLE_CONFIG_PREFIX = "sample.config";
    
    private long maxFileSize;

    /**
     * Use @ReadableBytes for parameter {@code maxFileSize}
     * to convert a String value formatted as number followed
     * by "KB", "MB" or "GB" (case-insensitive).
     * 
     * @param maxFileSize Maximum file size
     */
    public void setMaxFileSize(@ReadableBytes long maxFileSize) {
        this.maxFileSize = maxFileSize;
    }

    public long getMaxFileSize() {
        return maxFileSize;
    }
    
}

Let's write a Spock specification to test the conversion of String values to numbers:

package mrhaki

import io.micronaut.context.ApplicationContext
import spock.lang.Specification;

class SampleConfigSpec extends Specification {

    void "set maxFileSize configuration property with KB/MB/GB format"() {
        given:
        final ApplicationContext context =
                ApplicationContext.run("sample.config.maxFileSize": maxFileSize)

        when:
        final SampleConfig sampleConfig = context.getBean(SampleConfig)

        then:
        sampleConfig.maxFileSize == result

        where:
        maxFileSize || result
        "20KB"      || 20_480
        "20kb"      || 20 * 1024
        "1MB"       || 1_048_576
        "1Mb"       || 1 * 1024 * 1024
        "3GB"       || 3L * 1024 * 1024 * 1024
        113         || 113
    }
}

In another example we use the conversion on a path variable parameter in a controller method:

package mrhaki;

import io.micronaut.core.convert.format.ReadableBytes;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller
public class SampleController {
    
    @Get("/{size}")
    public long size(@ReadableBytes final long size) {
        return size;
    }
    
}

And with the following test we can see if the conversion is done correctly:

package mrhaki

import io.micronaut.context.ApplicationContext
import io.micronaut.http.client.HttpClient
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class SampleControllerSpec extends Specification {

    @Shared
    @AutoCleanup
    private static EmbeddedServer server = ApplicationContext.run(EmbeddedServer)

    @Shared
    @AutoCleanup
    private static HttpClient httpClient = HttpClient.create(server.URL)

    void "return size converted from String value with unit KB/MB/GB"() {
        expect:
        httpClient.toBlocking().retrieve("/$size") == result

        where:
        size   || result
        "20KB" || "20480"
        "20kb" || (20 * 1024).toString()
        "1MB"  || 1_048_576.toString()
        "3GB"  || (3L * 1024 * 1024 * 1024).toString()
        113    || "113"

    }
}

Written with Micronaut 1.0.4.

March 22, 2019

Micronaut Mastery: Binding Request Parameters To POJO

Micronaut 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 method argument sorting of type Sorting. We want to map 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.

August 28, 2018

Micronaut Mastery: Using Specific Configuration Properties For HTTP Client

One of the (many) great features of Micronaut is the HTTP client. We use the @Client annotation to inject a low-level HTTP client. Or we define a declarative HTTP client based on an interface, for which Micronaut will generate an implementation. The @Client annotation supports the configuration parameter to reference a configuration class with configuration properties for the HTTP client. The configuration class extends HttpClientConfiguration to support for example the configuration of timeouts and connection pooling. We can add our own configuration properties as well and use them in our application.

In the following example we want to access the OpenWeatherMap API using a declarative HTTP client. First we write a class that extends HttpClientConfiguration. This gives us HTTP client configuration properties and we also add some properties to define the OpenWeatherMap URI, path and access key we need to invoke the REST API. Finally we add configuration properties for a @Retryable annotation we want to use for our HTTP client.

// File: src/main/java/mrhaki/micronaut/WeatherClientConfiguration.java
package weather;

import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.http.client.HttpClientConfiguration;
import io.micronaut.runtime.ApplicationConfiguration;

import java.net.URI;
import java.time.Duration;

import static weather.WeatherClientConfiguration.PREFIX;

/**
 * Custom HTTP client configuration set via application
 * properties prefixed with "weather.client".
 */
@ConfigurationProperties(PREFIX)
public class WeatherClientConfiguration extends HttpClientConfiguration {

    public static final String PREFIX = "weather.client";

    /**
     * HTTP client connection pool configuration.
     */
    private final WeatherClientConnectionPoolConfiguration connectionPoolConfiguration;

    /**
     * OpenWeatherMap URI.
     */
    private URI url;

    /**
     * Path for requests sent to OpenWeatherMap.
     */
    private String path;
    
    /** 
     * Key needed to access OpenWeatherMap API.
     */
    private String apiKey;

    public WeatherClientConfiguration(
            final ApplicationConfiguration applicationConfiguration,
            final WeatherClientConnectionPoolConfiguration connectionPoolConfiguration) {
        super(applicationConfiguration);
        this.connectionPoolConfiguration = connectionPoolConfiguration;
    }

    public URI getUrl() {
        return url;
    }

    public void setUrl(final URI url) {
        this.url = url;
    }

    public String getPath() {
        return path;
    }

    public void setPath(final String path) {
        this.path = path;
    }

    public String getApiKey() {
        return apiKey;
    }

    public void setApiKey(final String apiKey) {
        this.apiKey = apiKey;
    }

    @Override
    public ConnectionPoolConfiguration getConnectionPoolConfiguration() {
        return connectionPoolConfiguration;
    }
    
    @ConfigurationProperties(ConnectionPoolConfiguration.PREFIX)
    public static class WeatherClientConnectionPoolConfiguration extends ConnectionPoolConfiguration {
    }

    /**
     * Extra configuration propertie to set the values
     * for the @Retryable annotation on the WeatherClient.
     */
    @ConfigurationProperties(WeatherClientRetryConfiguration.PREFIX)
    public static class WeatherClientRetryConfiguration {
        
        public static final String PREFIX = "retry";
        
        private Duration delay;
        
        private int attempts;

        public Duration getDelay() {
            return delay;
        }

        public void setDelay(final Duration delay) {
            this.delay = delay;
        }

        public int getAttempts() {
            return attempts;
        }

        public void setAttempts(final int attempts) {
            this.attempts = attempts;
        }
    }
}

Next we write the declarative HTTP client as Java interface with the @Client annotation. We refer to our custom configuration and use the configuration properties to set the URI and path for accessing the OpenWeatherMap API.

// File: src/main/java/mrhaki/micronaut/WeatherClient.java
package weather;

import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.Client;
import io.micronaut.retry.annotation.Retryable;
import io.reactivex.Single;

import java.util.Map;

// Declarative HTTP client with URL and path
// fetched from the application configuration.
// HTTP client configuration like pooled connections,
// timeouts are defined using WeatherClientConfiguration.
@Client(
        value = "${weather.client.url}",
        path = "${weather.client.path}",
        configuration = WeatherClientConfiguration.class)
// Retry accessing OpenWeatherMap REST API if error occurs.
@Retryable(
        attempts = "${weather.client.retry.attempts}",
        delay = "${weather.client.retry.delay}")
interface WeatherClient {

    /**
     * Get weather description for the town of Tilburg, NL. 
     * The APPID query parameter is filled in with the apiKey
     * argument value.
     *
     * @param apikey OpenWeatherMap API key to access REST API.
     * @return Response data from REST API.
     */
    @Get("weather?q=Tilburg,nl&APPID={apikey}")
    Single<Map<String, Object>> tilburg(String apikey);

}

Finally we write a controller that uses the declarative HTTP client WeatherClient to get a weather description for the town of Tilburg in The Netherlands:

// File: src/main/java/mrhaki/micronaut/WeatherController.java
package weather;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.reactivex.Single;

import java.util.List;
import java.util.Map;

/**
 * Controller to expose data from the 
 * OpenWeatherMap REST API.
 */
@Controller("/weather")
public class WeatherController {
    
    private final WeatherClient client;
    private final WeatherClientConfiguration configuration;

    public WeatherController(
            final WeatherClient client, 
            final WeatherClientConfiguration configuration) {
        this.client = client;
        this.configuration = configuration;
    }

    /**
     * Get weather data for town Tilburg, NL and get the
     * weather description to return.
     * 
     * @return Weather description as text.
     */
    @Get(value = "/tilburg", produces = MediaType.TEXT_PLAIN)
    public Single<String> weatherInTilburg() {
        return client.tilburg(configuration.getApiKey())
                     .map(response -> getWeatherDescription(response));
    }

    /**
     * Get weather description from response data.
     * 
     * @param data Response data from OpenWeatherMap API.
     * @return Textual description of weather.
     */
    private String getWeatherDescription(final Map<String, Object> data) {
        final List<Object> weatherList = (List<Object>) data.get("weather");
        final Map<String, Object> weather = (Map<String, Object>) weatherList.get(0);
        final String description = (String) weather.get("description");
        return description;
    }
    
}

In the application.yml configuration file we can set the values for the configuration properties:

# File: src/main/resources/application.yml
...
weather:
  client:
    url: http://api.openweathermap.org/
    path: /data/2.5/
    api-key: 39caa...
    read-timeout: 500ms
    retry:
      attempts: 2
      delay: 5s

When we run our application and access the URL http://localhost:8080/weather/tilburg using HTTPie we get the weather description:

$ http :8080/weather/tilburg
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain;charset=UTF-8

moderate rain

Written with Micronaut 1.0.0.M4.

August 22, 2018

Micronaut Mastery: Documenting Our API Using Spring REST Docs

Spring REST Docs is a project to document a RESTful API using tests. The tests are used to invoke real REST calls on the application and to generate Asciidoctor markup snippets. We can use the generated snippets in an Asciidoctor document with documentation about our API. We can use Spring REST Docs to document a REST API we create using Micronaut.

First we must change our build file and include the Asciidoctor plugin and add dependencies to Spring REST Docs. The following example Gradle build file adds the Gradle Asciidoctor plugin, Spring REST Docs dependencies and configures the test and asciidoctor tasks. Spring REST Docs supports three different web clients to invoke the REST API of our application: Spring MockMVC, Spring Webflux WebTestClient and REST Assured. We use REST Assured 3, because it has little dependencies on other frameworks (like Spring).

// File: build.gradle
...

plugins {
    id "org.asciidoctor.convert" version "1.5.8.1"
}

...

ext {
    snippetsDir = file('build/generated-snippets')
    springRestDocsVersion = '2.0.2.RELEASE'
}

dependencies {
    asciidoctor "org.springframework.restdocs:spring-restdocs-asciidoctor:$springRestDocsVersion"
    testCompile "org.springframework.restdocs:spring-restdocs-restassured:$springRestDocsVersion"
}

test {
    outputs.dir snippetsDir
}

asciidoctor {
    inputs.dir snippetsDir
    dependsOn test
}

Let's add a controller to our application that has two methods to return one or more Conference objects. We want to document both REST API resource methods. First we look at the Conference class that is used:

// File: src/main/java/mrhaki/micronaut/Conference.java
package mrhaki.micronaut;

public class Conference {
    private final String name;
    private final String location;

    public Conference(final String name, final String location) {
        this.name = name;
        this.location = location;
    }

    public String getName() {
        return name;
    }

    public String getLocation() {
        return location;
    }
}

Next we write the following controller to implement /conference to return multiple conferences and /conference/{name} to return a specific conference. The controller is dependent on the class ConferenceService that contains the real logic to get the data, but the implementation is not important for our example to document the controller:

// File: src/main/java/mrhaki/micronaut/ConferenceController.java
package mrhaki.micronaut;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Controller("/conference")
public class ConferenceController {
    
    private final ConferenceService conferenceService;

    public ConferenceController(final ConferenceService conferenceService) {
        this.conferenceService = conferenceService;
    }

    @Get("/")
    public Flux<Conference> all() {
        return conferenceService.all();
    }
    
    @Get("/{name}")
    public Mono<Conference> findByName(final String name) {
        return conferenceService.findByName(name);
    }
}

Now it is time to write our test that will invoke our controller and generate Asciidoctor markup snippets. We use Spock for writing the test in our example:

// File: src/test/groovy/mrhaki/micronaut/ConferenceApiSpec.groovy
package mrhaki.micronaut

import io.micronaut.context.ApplicationContext
import io.micronaut.http.HttpStatus
import io.micronaut.runtime.server.EmbeddedServer
import io.restassured.builder.RequestSpecBuilder
import io.restassured.specification.RequestSpecification
import org.junit.Rule
import org.springframework.restdocs.JUnitRestDocumentation
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

import static io.restassured.RestAssured.given
import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.documentationConfiguration

class ConferenceApiSpec extends Specification {

    @Shared
    @AutoCleanup
    private EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)

    @Rule
    private JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation()

    private RequestSpecification spec

    void setup() {
        // Create a REST Assured request specification
        // with some defaults. All URI's
        // will not have localhost as server name,
        // but api.example.com and the port is removed.
        // All JSON responses are prettyfied.
        this.spec = new RequestSpecBuilder()
                .addFilter(
                    documentationConfiguration(restDocumentation)
                        .operationPreprocessors()
                        .withRequestDefaults(
                            modifyUris().host('api.example.com')
                                        .removePort())
                        .withResponseDefaults(prettyPrint()))
                .build()
    }

    void "get all conferences"() {
        given:
        final request =
                given(this.spec)
                    // The server port is set and the value is
                    // used from embeddedServer.
                    .port(embeddedServer.URL.port)
                    .accept("application/json")
                    .filter(
                        document(
                            "all", 
                            responseFields(
                                fieldWithPath("[].name").description("Name of conference."),
                                fieldWithPath("[].location").description("Location of conference.")
                        )))

        when:
        final response = request.get("/conference")

        then:
        response.statusCode() == HttpStatus.OK.code
    }

    void "get conference with given name"() {
        given:
        final request = 
                given(this.spec)
                    .port(embeddedServer.URL.port)
                    .accept("application/json")
                    .filter(
                        document(
                            "getByName", 
                            responseFields(
                                fieldWithPath("name").description("Name of conference."),
                                fieldWithPath("location").description("Location of conference.")
                )))

        when:
        final response = request.get("/conference/Gr8Conf EU")

        then:
        response.statusCode() == HttpStatus.OK.code
    }

}

Finally we create a Asciidoctor document to describe our API and use the generated Asciidoctor markup snippets from Spring REST Docs in our document. We rely in our example document on the operation macro that is part of Spring REST Docs to include some generated snippets:

// File: src/docs/asciidoc/api.adoc
= Conference API

== Get all conferences

operation::all[snippets="curl-request,httpie-request,response-body,response-fields"]

== Get conference using name

operation::getByName[snippets="curl-request,httpie-request,response-body,response-fields"]

We run the Gradle asciidoctor task to create the documentation. When we open the generated HTML we see the following result:

Written with Micronaut 1.0.0.M4 and Spring REST Docs 2.0.2.RELEASE.

August 21, 2018

Micronaut Mastery: Return Response Based On HTTP Accept Header

Suppose we want our controller methods to return a JSON response when the HTTP Accept header is set to application/json and XML when the Accept header is set to application/xml. We can access the values of HTTP headers in our controller methods by adding an argument of type HttpHeaders to our method definition and Micronaut will add all HTTP headers with their values as HttpHeaders object when we run the application. In our method we can check the value of the Accept header and return a different value based on the header value.

In the following example controller we have a sample method with an argument of type HttpHeaders. We check the value of the Accept header using the method accept and return either XML or JSON as response.

package mrhaki.micronaut;

import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/message")
public class MessageController {
    
    @Get("/")
    public HttpResponse<?> sample(final HttpHeaders headers) {
        // Simple object to be returned from this method either
        // as XML or JSON, based on the HTTP Accept header.
        final Message message = new Message("Micronaut is awesome");

        // Check if HTTP Accept header is "application/xml".
        if (headerAcceptXml(headers)) {
            // Encode messages as XML.
            final String xml = encodeAsXml(message);
            
            // Return response and set content type 
            // to "application/xml".
            return HttpResponse.ok(xml)
                               .contentType(MediaType.APPLICATION_XML_TYPE);
        }
        
        // Default response as JSON.
        return HttpResponse.ok(message);
    }

    /**
     * Check HTTP Accept header with value "application/xml"
     * is sent by the client.
     * 
     * @param headers Http headers sent by the client.
     * @return True if the Accept header contains "application/xml".
     */
    private boolean headerAcceptXml(final HttpHeaders headers) {
        return headers.accept().contains(MediaType.APPLICATION_XML_TYPE);
    }

    /**
     * Very simple way to create XML String with message content.
     * 
     * @param message Message to be encoded as XML String.
     * @return XML String with message content.
     */
    private String encodeAsXml(final Message message) {
        return String.format("<content>%s</content>", message.getContent());
    }

}

The Message class that is converted to XML or JSON is simple:

package mrhaki.micronaut;

public class Message {

    final String content;

    public Message(final String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

}

When we run the application and GET /message with HTTP Accept header value application/xml we get the following response:

<content>Micronaut is awesome</content>

And with HTTP Accept header value application/json we get the following response:

{
    "content": "Micronaut is awesome"
}

We can test our controller with the following Spock specification:

package mrhaki.micronaut

import io.micronaut.context.ApplicationContext
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
import io.micronaut.http.client.RxHttpClient
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class MessageControllerSpec extends Specification {

    @Shared
    @AutoCleanup
    private static EmbeddedServer embeddedServer =
            ApplicationContext.run(EmbeddedServer)

    @Shared
    @AutoCleanup
    private static RxHttpClient client =
            embeddedServer.applicationContext
                          .createBean(RxHttpClient, embeddedServer.getURL())

    void "get message as XML"() {
        given:
        final request = HttpRequest.GET("/message").accept(MediaType.APPLICATION_XML_TYPE)
        HttpResponse response = client.toBlocking().exchange(request, String)

        expect:
        response.status == HttpStatus.OK
        response.body() == 'Micronaut is awesome'
    }

    void "get message as JSON"() {
        given:
        HttpResponse response = client.toBlocking().exchange("/message", Message)

        expect:
        response.status == HttpStatus.OK
        response.body().getContent() == 'Micronaut is awesome'
    }
    
}

Written with Micronaut 1.0.0.M4.

August 16, 2018

Micronaut Mastery: Decode JSON Using Custom Constructor Without Jackson Annotations

Micronaut uses Jackson to encode objects to JSON and decode JSON to objects. Micronaut adds a Jackson ObjectMapper bean to the application context with all configuration to work properly. Jackson can by default populate an object with values from JSON as the class has a no argument constructor and the properties can be accessed. But if our class doesn't have a no argument constructor we need to use the @JsonCreator and @JsonProperty annotations to help Jackson. We can use these annotation on the constructor with arguments that is used to create an object.

But we can even make it work without the extra annotations, so our classes are easier to read and better reusable. We need to add the Jackson ParameterNamesModule as module to the ObjectMapper instance in our application. And we need to compile our sources with the -parameters argument, so the argument names are preserved in the compiled code. Luckily the -parameters option is already added to our Gradle build when we create a Micronaut application. All we have to do is to add the ParameterNamesModule in our application.

We need to add a dependency on com.fasterxml.jackson.module:jackson-module-parameter-names to our compile class path. Micronaut will automatically find the ParameterNamesModule and add it to the ObjectMapper using the findAndRegisterModules method.

So we change our build.gradle file first:

// File: build.gradle
...
dependencies {
    ...
    compile "com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.0"
    ...
}
...

In our application we have the following class that has an argument constructor to create a immutable object:

package mrhaki;

import java.util.Objects;

public class Language {
    private final String name;

    private final String platform;

    public Language(final String name, final String platform) {
        this.name = name;
        this.platform = platform;
    }

    public String getName() {
        return name;
    }

    public String getPlatform() {
        return platform;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) { return true; }
        if (o == null || getClass() != o.getClass()) { return false; }
        final Language language = (Language) o;
        return Objects.equals(name, language.name) &&
                Objects.equals(platform, language.platform);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, platform);
    }
}

We write the following sample controller to use the Language class as a return type (we wrap it in a Mono object so the method is reactive):

package mrhaki;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import reactor.core.publisher.Mono;

@Controller("/languages")
public class LanguagesController {

    @Get("/groovy")
    public Mono<Language> getGroovy() {
        return Mono.just(new Language("Groovy", "JVM"));
    }

}

And in our test we use HttpClient to invoke the controller method. The exchange method will trigger a decode of the JSON output of the controller to a Language object:

package mrhaki

import io.micronaut.context.ApplicationContext
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.HttpClient
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class LanguagesControllerSpec extends Specification {

    @AutoCleanup
    @Shared
    private static EmbeddedServer server = ApplicationContext.run(EmbeddedServer)

    @AutoCleanup
    @Shared
    private static HttpClient client = server.applicationContext.createBean(HttpClient, server.URL)

    void '/languages/groovy should find language Groovy'() {
        given:
        final request = HttpRequest.GET('/languages/groovy')

        when:
        final response = client.toBlocking().exchange(request, Language)

        then:
        response.status() == HttpStatus.OK

        and:
        response.body() == new Language('Groovy', 'JVM')
    }


}

When we wouldn't have the dependency on jackson-module-parameter-names and not use the -parameter compiler option we get the following error message:

[nioEventLoopGroup-1-6] WARN  i.n.channel.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.micronaut.http.codec.CodecException: Error decoding JSON stream for type [class mrhaki.Language]: Cannot construct instance of `hello.conf.Language` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (byte[])"{"name":"Groovy","platform":"JVM"}"; line: 1, column: 2]

But with the dependency and -parameter compiler option we have a valid test without any errors. Jackson knows how to use the argument constructor to create a new Language object.

Written with Micronaut 1.0.0.M4.

Micronaut Mastery: Using Reactor Mono And Flux

Micronaut is reactive by nature and uses RxJava2 as implementation for the Reactive Streams API by default. RxJava2 is on the compile classpath by default, but we can easily use Project Reactor as implementation of the Reactive Streams API. This allows us to use the Reactor types Mono and Flux. These types are also used by Spring's Webflux framework and makes a transition from Webflux to Micronaut very easy.

How we do use Project Reactor in our Micronaut application? We only have to add the dependency the Project Reactory core library to our project. In the following example we add it to our build.gradle file as:

// File: build.gradle
...
dependencies {
    ...
    // The version of Reactor is resolved
    // via the BOM of Micronaut, so we know
    // the version is valid for Micronaut.
    compile 'io.projectreactor:reactor-core'
    ...
}
...

Now we can use Mono and Flux as return types for methods. If we use them in our controller methods Micronaut will make sure the code is handled on the Netty event loop. This means we must handle blocking calls (like accessing a database using JDBC) with care and make sure a blocking call invoked from the controller methods is handled on a different thread.

In the following example we have a simple controller. Some of the methods use a repository implementation with code that access a databases using JDBC. The methods of the repository implementation are not reactive, therefore we must use Mono.fromCallable with Reactor's elastic scheduler to make sure the code is called on separate threads and will not block our Netty event loop.

package mrhaki;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.util.concurrent.Callable;

@Controller("/languages")
public class LanguagesController {

    // Repository reads data from database
    // using JDBC and uses simple return types.
    private final LanguagesRepository repository;

    public LanguagesController(final LanguagesRepository repository) {
        this.repository = repository;
    }

    @Get("/{name}")
    public Mono<Language> findByName(final String name) {
        return blockingGet(() -> repository.findByName(name));
    }

    @Get("/")
    public Flux<Language> findAll() {
        return blockingGet(() -> repository.findAll()).flatMapMany(Flux::fromIterable);
    }

    // Run callable code on other thread pool than Netty event loop,
    // so blocking call will not block the event loop.
    private <T> Mono<T> blockingGet(final Callable<T> callable) {
        return Mono.fromCallable(callable)
                   .subscribeOn(Schedulers.elastic());
    }
}

The repository interface looks like this:

package mrhaki;

interface LanguagesRepository {
    List<Language> findAll();
    Language findByName(String name);
}

Let's write a Spock specification to test if our controller works correctly:

package mrhaki

import io.micronaut.context.ApplicationContext
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class LanguagesControllerSpec extends Specification {

    @AutoCleanup
    @Shared
    private static EmbeddedServer server = ApplicationContext.run(EmbeddedServer)

    @AutoCleanup
    @Shared
    private static HttpClient client = server.applicationContext.createBean(HttpClient, server.URL)

    void '/languages should return all languages'() {
        given:
        final request = HttpRequest.GET('/languages')

        when:
        final response = client.toBlocking().exchange(request, Argument.of(List, Language))

        then:
        response.status() == HttpStatus.OK

        and:
        response.body()*.name == ['Java', 'Groovy', 'Kotlin']

        and:
        response.body().every { language -> language.platform == 'JVM' }
    }

    void '/languages/groovy should find language Groovy'() {
        given:
        final request = HttpRequest.GET('/languages/Groovy')

        when:
        final response = client.toBlocking().exchange(request, Language)

        then:
        response.status() == HttpStatus.OK

        and:
        response.body() == new Language('Groovy', 'JVM')
    }

    void '/languages/dotnet should return 404'() {
        given:
        final request = HttpRequest.GET('/languages/dotnet')

        when:
        client.toBlocking().exchange(request)

        then:
        HttpClientResponseException notFoundException = thrown(HttpClientResponseException)
        notFoundException.status == HttpStatus.NOT_FOUND
    }

}

Written with Micronaut 1.0.0.M4.