September 4, 2018

Spring Sweets: Dockerize Spring Boot Application With Jib

Jib is an open-source Java library from Google for creating Docker images for Java applications. Jib can be used as Maven or Gradle plugin in our Spring Boot project. One of the nice feature of Jib is that it adds layers with our classes, resources and dependency libraries for the Docker image. This means that when only class files have changed, the classes layer is rebuild, but the others remain the same. Therefore the creation of a Docker image with our Spring Boot application is also very fast (after the first creation). Also the Maven and Gradle plugins have sensible defaults, like using the project name and version as image name, so we don't have to configure anything in our build tool. Although Jib provides options to configure other values for the defaults, for example to change the JVM options passed on to the application.

Let's see Jib in action for a simple Spring Boot application. In our example we use Gradle as build tool with the following Spring Boot application:

// File: src/main/java/mrhaki/spring/sample/SampleApplication.java
package mrhaki.spring.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@SpringBootApplication
public class SampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }

    @GetMapping("/message")
    Mono<String> message() {
        return Mono.just("Spring Boot sample application.");
    }
    
}

Next we add the Jib plugin to our Gradle build file:

// File: build.gradle
...
plugins {
    id 'com.google.cloud.tools.jib' version '0.9.10'
}
...

With the Gradle task jibDockerBuild we can create a Docker image for our local Docker. Our project is called springboot-sample with version 1.0.0-SNAPSHOT, so we get the Docker image springboot-sample:1.0.0-SNAPSHOT:

$ ./gradlew jibDockerBuild
Tagging image with generated image reference springboot-sample:1.0.0-SNAPSHOT. If you'd like to specify a different tag, you can set the jib.to.image parameter in your build.gradle, or use the --image=<MY IMAGE> commandline flag.
warning: Base image 'gcr.io/distroless/java' does not use a specific image digest - build may not be reproducible

Containerizing application to Docker daemon as springboot-sample:1.0.0-SNAPSHOT...

Getting base image gcr.io/distroless/java...
Building dependencies layer...
Building resources layer...
Building classes layer...
Finalizing...
Loading to Docker daemon...

Container entrypoint set to [java, -cp, /app/resources/:/app/classes/:/app/libs/*, mrhaki.spring.sample.SampleApplication]

Built image to Docker daemon as springboot-sample:1.0.0-SNAPSHOT

BUILD SUCCESSFUL in 3s
3 actionable tasks: 1 executed, 2 up-to-date
$

Notice that the default image our Docker image is build on is gcr.io/distroless/java, but we can change that in our Gradle build file via the jib configuration block.

Our image is available so we can run a Docker container based on our image and check the URL of our application:

$ docker run -d --name springboot-sample -p 8080:8080 springboot-sample:1.0.0-SNAPSHOT
5d288cbe4ed606760a51157734349135d4d4562072e1024f4585dff370ac6f99
$ curl -X GET http://localhost:8080/message
Spring Boot sample application.
$

In the following example we add some configuration for Jib in our Gradle build file:

// File: build.gradle
...
jib {
    to {
        image = "springboot-mrhaki:${project.version}"
    }

    container {
        // Set JVM options.
        jvmFlags = ['-XX:+UnlockExperimentalVMOptions', '-XX:+UseCGroupMemoryLimitForHeap', '-Dserver.port=9000']
        // Expose different port.
        ports = ['9000']
        // Add labels.
        labels = [maintainer: 'mrhaki']
    }

}
...

When we run the jibDockerBuild task a new Docker image is build:

$ warning: Base image 'gcr.io/distroless/java' does not use a specific image digest - build may not be reproducible

Containerizing application to Docker daemon as springboot-mrhaki:1.0.0-SNAPSHOT...

Getting base image gcr.io/distroless/java...
Building dependencies layer...
Building resources layer...
Building classes layer...
Finalizing...
Loading to Docker daemon...

Container entrypoint set to [java, -XX:+UnlockExperimentalVMOptions, -XX:+UseCGroupMemoryLimitForHeap, -Dserver.port=9000, -cp, /app/resources/:/app/classes/:/app/libs/*, mrhaki.spring.sample.SampleApplication]

Built image to Docker daemon as springboot-mrhaki:1.0.0-SNAPSHOT

BUILD SUCCESSFUL in 3s
3 actionable tasks: 1 executed, 2 up-to-date
$ docker run -d --name springboot-mrhaki -p 9000:9000 springboot-mrhaki:1.0.0-SNAPSHOT
6966ff3b5ca8dae658d59e39c0f26c11d22af7e04e02ad423516572eb5e0e0bd
$ curl -X GET http://localhost:9000/message
Spring Boot sample application.
$

Jib also adds the jib task to deploy a Docker image to a registry instead of the local Docker daemon. Jib can use several command-line applications to authenticate with registries or we can set authentication information in the Gradle build file. Check Jib on Github for more documentation of the options and features.

Written with Spring Boot 2.0.4.RELEASE and Jib Gradle plugin 0.9.10.

September 2, 2018

Awesome Asciidoctor: Document Attributes With Styling

Document attributes in Asciidoctor are very powerful. We can assign values to a document attributes and reference the attribute name in our document enclosed between curly braces. Asciidoctor will fill in the value when the document is transformed. Instead of a plain value we can also use styling markup in the document attribute definition. We must use the passthrough macro and allow for quote substitution.

In the following example document we define three document attributes: cl-added, cl-updated and cl-changed. We use the passthrough macro, quotes substation to assign CSS classes:

= Attributes with styles
// Include contents of docinfo.html
// in HTML head with CSS style
// definitions for .label.added,
// .label.changed and .label.updated
// used in the document attributes
// cl-added, cl-changed and cl-updated.
:docinfo1:

// Document attribues with styling,
// using the passthrough macro
// and quotes subsitution.
// We can use quotes or the short-hand q.
:cl-added: pass:quotes[[.label.added]#Added:#]
:cl-changed: pass:q[[.label.changed]#Changed:#]
:cl-updated: pass:q[[.label.updated]#Updated:#]


== Sample section

* {cl-added} Document attributes for document.
* {cl-changed} Definition of attributes to include
 more options.
* {cl-updated} New version of Asciidoctor.

Notice we need a file docinfo.html with the CSS style definitions:

<style>
.label {
    color: #fff;
    padding: .2em .6em .3em;
    font-weight: 700;
    border-radius: .25em;
    font-size: 90%;
}

.added {background-color: #007700;}
.changed {background-color: #088;}
.updated {background-color: #3344bb;}
</style>

When run Asciidoctor to get HTML output we see the following:

Written with Aciidoctor 1.5.7.1.

August 31, 2018

Micronaut Mastery: Change The Default Package For Generated Classes

When we use the Micronaut command line commands to generate controllers, beans and more, the classes will have a default package name. We can use a fully qualified package name for the class we want to generate, but when we only define a class name, Micronaut will use a default package name automatically. We can set the default package for an application when we first create an application with the create-app command. But we can also alter the default package name for an existing Micronaut application.

To set the default package name when we create a new application we must include the package name in our application name. For example when we want to use the default package name mrhaki.micronaut for an application that is called book-service we must use the following create-app command:

$ mn create-app mrhaki.micronaut.book-service
| Generating Java project.....
| Application created at /Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/micronaut/book-service
$ tree src
src
├── main
│   ├── java
│   │   └── mrhaki
│   │       └── micronaut
│   │           └── Application.java
│   └── resources
│       ├── application.yml
│       └── logback.xml
└── test
    └── java
        └── mrhaki
            └── micronaut

9 directories, 3 files

Notice the tree structure reflects the structure for the package mrhaki.micronaut.
When we invoke the create-controller book command the generated files use the package name mrhaki.micronaut:

$ mn create-controller book
| Rendered template Controller.java to destination src/main/java/mrhaki/micronaut/BookController.java
| Rendered template ControllerTest.java to destination src/test/java/mrhaki/micronaut/BookControllerTest.java
$

We can change the default package name for an existing application as well. We must edit the file micronaut-cli.yml in the root of our project and change the property defaultPackage. In the following example we use the sed command on MacOS to replace the current default package with com.mrhaki.microanut, but we could of course also use any text editor to make the change:

$ cat micronaut-cli.yml
profile: service
defaultPackage: mrhaki.micronaut
---
testFramework: junit
sourceLanguage: java
$ sed -E -i '' 's/(defaultPackage:) .*/\1 com\.mrhaki\.micronaut/g' micronaut-cli.yml
$ cat micronaut-cli.yml
profile: service
defaultPackage: com.mrhaki.micronaut
---
testFramework: junit
sourceLanguage: java

Let's generate a new controller and see if our new default package name for the application is used:

$ mn create-controller author
| Rendered template Controller.java to destination src/main/java/com/mrhaki/micronaut/AuthorController.java
| Rendered template ControllerTest.java to destination src/test/java/com/mrhaki/micronaut/AuthorControllerTest.java
$

Written with Micronaut 1.0.0.M4.

August 30, 2018

Micronaut Mastery: Use Micronaut Beans In Spring Applications

We can add Micronaut beans to the application context of a Spring application. Micronaut has a MicronautBeanProcessor class that we need to register as Spring bean in the Spring application context. We can define which Micronaut bean types need to be added to the Spring application context via the constructor of the MicronautBeanProcessor class. This way we can use Micronaut from inside a Spring application. For example we can use the declarative HTTP client of Micronaut in our Spring applications.

First we need to add dependencies on Micronaut to our Spring application. In this example we will use the Micronaut HTTP client in our Spring application and use Gradle as build tool. We must add the following dependencies:

// File: build.gradle
...
dependencyManagement {
    imports {
        mavenBom 'io.micronaut:bom:1.0.0.M4'
    }
}
...
dependencies {
    ...
    annotationProcessor "io.micronaut:inject-java"
    compile "io.micronaut:http-client"
    compile "io.micronaut:spring"
    ...
}
...

Next we register a MicronautBeanProcessor bean in the Spring application context. We specify in the constructor that Micronaut beans annotated with @Client must be added to the Spring application context:

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

import io.micronaut.http.client.Client;
import io.micronaut.spring.beans.MicronautBeanProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class SampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }

    @Bean
    public MicronautBeanProcessor httpClientMicronautBeanProcessor() {
        // Register beans with @Client annotation.
        // We could for example also use @Singleton
        // and others, because the constructor has
        // a variable number of arguments.
        return new MicronautBeanProcessor(Client.class);
    }

}

We add the source for our Micronaut declarative HTTP client, where we access httpbin.org as a remote webservice:

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

import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.client.Client;

@Client("http://httpbin.org")
interface HttpBinClient {
    
    @Get("/uuid")
    ResponseUuidData uuid();
    
    @Post("/anything")
    ResponseData data(String message);
    
}

Inside a Spring controller we use the client as Spring bean:

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

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;

@RestController
@RequestMapping("/sample")
public class HttpBinController {

    private final HttpBinClient client;

    // Inject Micronaut HTTP client via implicit
    // constructor injection.
    public HttpBinController(final HttpBinClient client) {
        this.client = client;
    }

    @GetMapping(value = "/uuid", produces = TEXT_PLAIN_VALUE)
    Mono<String> uuid() {
        return Mono.fromCallable(() -> client.uuid().getUuid().toString());
    }

    @GetMapping(value = "/data")
    Mono<ResponseData.MessageResponseData> data() {
        return Mono.fromCallable(() -> client.data("Micronaut rocks").getJson());
    }

}

Finally we have some POJO classes with the results from the remote web service calls:

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

import java.util.UUID;

public class ResponseUuidData {
    private UUID uuid;
    public UUID getUuid() { return uuid; }
    public void setUuid(final UUID uuid) { this.uuid = uuid; }
}
// File: src/main/java/mrhaki/micronaut/ResponseData.java
package mrhaki.micronaut;

public class ResponseData {
    private MessageResponseData json;
    public MessageResponseData getJson() { return json; }
    public void setJson(final MessageResponseData json) { this.json = json; }
    
    static class MessageResponseData {
        private String message;
        public String getMessage() { return message; }
        public void setMessage(final String message) { this.message = message; }
    }
}

It is time to start our Spring application and invoke /sample/uuid and /sample/data URLs to see the results:

$ curl -X GET http://localhost:8080/sample/uuid
7149a954-da9a-4bfb-ba04-4b9f814698fa
$ curl -X GET http://localhost:8080/sample/data
{"message":"Micronaut rocks"}

Written with Micronaut 1.0.0.M4 and Spring Boot 2.0.4.RELEASE.

August 29, 2018

Micronaut Mastery: Adding Custom Info To Info Endpoint

In a previous post we learned how to add build information to the /info endpoint in our application. We can add custom information to the /info endpoint via our application configuration or via a bean by implementing the InfoSource interface. Remember we need to add the io.micronaut:management dependency to our application for this feature.

Any configuration property that is prefixed with info will be exposed via the /info endpoint. We can define the properties in configuration files, using Java system properties or system environment variables. In the following example configuration file we add the property info.sample.message:

// File: src/main/resources/application.yml
...
info:
  sample:
    message: Micronaut is awesome

Another option is to create a new class that implements the InfoSource interface. We need to override the getSource method to return a Publisher with a PropertySource. In our example application we have a ConferenceRepository bean that can access a database with data about conferences. In the following example InfoSource implementation we want to return the number of conferences that are stored in the database:

package mrhaki.micronaut;

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.MapPropertySource;
import io.micronaut.context.env.PropertySource;
import io.micronaut.core.async.SupplierUtil;
import io.micronaut.management.endpoint.info.InfoEndpoint;
import io.micronaut.management.endpoint.info.InfoSource;
import io.micronaut.runtime.context.scope.Refreshable;
import io.reactivex.Flowable;
import org.reactivestreams.Publisher;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

// Component is refreshable via a refresh event
@Refreshable
// Only create this component when the following 
// requirements are met:
@Requires(beans = InfoEndpoint.class)
@Requires(property = "endpoints.info.config.enabled", notEquals = "false")
public class DatabaseInfoSource implements InfoSource {

    /**
     * Repository to fetch count of conferences in the database.
     */
    private final ConferenceRepository conferenceRepository;

    /**
     * Supplier for returning result.
     */
    private final Supplier<PropertySource> supplier;

    public DatabaseInfoSource(final ConferenceRepository conferenceRepository) {
        this.conferenceRepository = conferenceRepository;
        
        // Cache result from database.
        // This component is refreshable via 
        // a refresh event to get the latest value
        // from the database.
        this.supplier = SupplierUtil.memoized(this::retrieveDatabaseInfo);
    }

    /**
     * Return information.
     * 
     * @return Information about the database.
     */
    @Override
    public Publisher<PropertySource> getSource() {
        return Flowable.just(supplier.get());
    }

    /**
     * Get information from the database via the {@link #conferenceRepository} instance.
     * 
     * @return Number of conferences in the database.
     */
    private MapPropertySource retrieveDatabaseInfo() {
        final Map<String, Long> databaseInfo = new HashMap<>();
        databaseInfo.put("db.conferences.count", conferenceRepository.count());
        return new MapPropertySource("database", databaseInfo);
    }

}

Let's run our Micronaut application and invoke the /info endpoint. We see the following response if we have 42 conference records in our database:

{
    "db": {
        "conferences": {
            "count": 42
        }
    },
    "sample": {
        "message": "Micronaut is awesome"
    }
}

Written with Micronaut 1.0.0.M4.

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 23, 2018

Micronaut Mastery: Using Stubs For Testing

Writing tests is always a good idea when developing an application. Micronaut makes it very easy to write tests. Using the @Client annotation we can generate a client for our REST resources that uses HTTP. Starting up a Micronaut application is so fast we can run our actual application in our tests. And using dependency injection we can replace components from the production application with stubs in our tests.

Let's show how we can use stub in our tests for an example application. In the example application we have controller ConferenceController that returns Conference objects. These objects are fetched from a simple data repository ConferenceDataRepository. When we write a test we want to replace the ConferenceDataRepository with a stub, so we can return the appropriate Conference objects for our tests.

First we take a look at the Conference class:

package mrhaki.micronaut;

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

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

    public String getName() {
        return name;
    }

    public String getLocation() {
        return location;
    }
    
}

We add an interface that describe the functionality we expect for getting Conference objects in our application:

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

import io.reactivex.Maybe;
import io.reactivex.Single;

import java.util.List;

interface ConferenceService {

    Single<List<Conference>> all();

    Maybe<Conference> findByName(final String name);
    
}

For our example the implementation of the ConferenceService is simple, but in a real world application the code would probably access a database to get the results:

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

import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;

import javax.inject.Singleton;
import java.util.Arrays;
import java.util.List;

@Singleton
public class ConferenceDataRepository implements ConferenceService {

    private final static List<Conference> CONFERENCES =
            Arrays.asList(new Conference("Gr8Conf EU", "Copenhagen"),
                          new Conference("Gr8Conf US", "Minneapolis"),
                          new Conference("Greach", "Madrid"));

    public Single<List<Conference>> all() {
        return Single.just(CONFERENCES);
    }

    public Maybe<Conference> findByName(final String name) {
        return all()
                .flatMapObservable(conferences -> Observable.fromIterable(conferences))
                .filter(conference -> name.equals(conference.getName()))
                .singleElement();
    }

}

Finally our controller to return conference data via HTTP REST that uses via dependency injection the ConferenceDataRepository implementation of the ConferenceService interface:

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

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

import java.util.List;

@Controller("/conference")
public class ConferenceController {

    private final ConferenceService conferenceService;

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

    @Get("/")
    public Single<List<Conference>> all() {
        return conferenceService.all();
    }

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

To add a stub for the ConferenceService in our test we must write a new implementation of the ConferenceService that is available on the test class path and not on the production code class path. In our test code directory (src/test/{java|groovy|kotlin}) we write our stub implementation. We use the @Primary annotation to instruct Micronaut to use this implementation of the ConferenceService interface. If we leave out the @Primary annotation we get an error, because we have two implementations of the interface on our classpath, ConferenceDataRepository and ConferenceServiceStub, and Micronaut doesn't know which one to use. By using @Primary we tell Micronaut to use the stub implementation.

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

import io.micronaut.context.annotation.Primary
import io.reactivex.Maybe
import io.reactivex.Single

import javax.inject.Singleton

@Singleton
@Primary
class ConferenceServiceStub implements ConferenceService {

    Single<List<Conference>> all() {
        return Single.just([new Conference("Gr8Conf", "Copenhagen")])
    }

    Maybe<Conference> findByName(final String name) {
        if (name == 'Gr8Conf') {
            return Maybe.just(new Conference("Gr8Conf", "Copenhagen"))
        } else {
            return Maybe.empty()
        }
    }

}

In our test directory we also add a declarative HTTP client to invoke the REST resource. This client is only used for testing and makes invoking the REST resource very easy:

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

import io.micronaut.http.annotation.Get
import io.micronaut.http.client.Client
import io.reactivex.Maybe
import io.reactivex.Single

@Client("/conference")
interface ConferenceClient {

    @Get("/")
    Single<List<Conference>> all()

    @Get("/{name}")
    Maybe<Conference> findByName(final String name)

}

We write a Spock specification to test our REST resource and it will use the stub code as implementation of ConferenceService:

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

import io.micronaut.context.ApplicationContext
import io.micronaut.runtime.server.EmbeddedServer
import io.reactivex.Maybe
import io.reactivex.Single
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class ConferenceContollerSpec extends Specification {
    
    @Shared
    @AutoCleanup
    private EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)

    @Shared
    private ConferenceClient client = embeddedServer.applicationContext.getBean(ConferenceClient)

    void 'get all conferences'() {
        when:
        final Single<List<Conference>> result = client.all()
        
        then:
        final conference = result.blockingGet().first() 
        with(conference) {
            name == 'Gr8Conf'
            location == 'Copenhagen'
        }
    }
    
    void 'find conference by name'() {
        when:
        final Maybe<Conference> result = client.findByName('Gr8Conf')
        
        then:
        final conference = result.blockingGet()
        with(conference) {
            name == 'Gr8Conf'
            location == 'Copenhagen'
        }
    }
    
    void 'return empty when conference cannot be found by name'() {
        when:
        final Maybe<Conference> result = client.findByName('JavaOne')

        then:
        result.isEmpty().blockingGet()
    }
}

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 20, 2018

Micronaut Mastery: Add Custom Health Indicator

When we add the io.micronaut:management dependency to our Micronaut application we get, among other things, a /health endpoint. We must enable it in our application configuration where we can also configure how much information is shown and if we want to secure the endpoint. Micronaut has some built-in health indicators, some of which are only available based on certain conditions. For example there is a disk space health indicator that will return a status of DOWN when the free disk space is less than a (configurable) threshold. If we would have one or more DataSource beans for database access in our application context a health indicator is added as well to show if the database(s) are available or not.

We can also add our own health indicator that will show up in the /health endpoint. We must write a class that implements the HealthIndicator interface and add it to the application context. We could add some conditions to make sure the bean is loaded when needed. Micronaut also has the abstract AbstractHealthIndicator class that can be used as base class for writing custom health indicators.

First we must add the io.micronaut:management dependency to our compile class path, like in the following example Gradle build file:

// File: build.gradle
...
dependencies {
    ...
    compile "io.micronaut:management"
    ...
}
...

Next we write a class that implements HealthIndicator or extends AbstractHealthIndicator. In the following example we implement HealthIndicator and the method getResult. This health indicator will try to access a remote URL and will return a status UP when the URL is reachable and DOWN when the status code is invalid or an exception occurs. We also use the @Requires annotation to make sure the indicator is only loaded when the correct value is set for a configuration property and when the HealthPoint bean is available.

package mrhaki.micronaut;

import io.micronaut.health.HealthStatus;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.client.Client;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.management.health.indicator.HealthIndicator;
import io.micronaut.management.health.indicator.HealthResult;
import org.reactivestreams.Publisher;

import javax.inject.Singleton;
import java.util.Collections;

@Singleton
// Only create bean when configuration property
// endpoints.health.url.enabled equals true,
// and HealthEndpoint bean to expose /health endpoint is available.
@Requires(property = HealthEndpoint.PREFIX + ".url.enabled", value = "true")
@Requires(beans = HealthEndpoint.class)
public class RemoteUrlHealthIndicator implements HealthIndicator {

    /**
     * Name for health indicator.
     */
    private static final String NAME = "remote-url-health";

    /**
     * URL to check.
     */
    private static final String URL = "http://www.mrhaki.com/";

    /**
     * We use {@link RxHttpClient} to check if
     * URL is reachable.
     */
    private RxHttpClient client;

    /**
     * Inject client with URL to check.
     * 
     * @param client Client to check if URl is reachable.
     */
    public RemoteUrlHealthIndicator(@Client(URL) final RxHttpClient client) {
        this.client = client;
    }

    /**
     * Implementaton of {@link HealthIndicator#getResult()} where we
     * check if the url is reachable and return result based
     * on the HTTP status code.
     * 
     * @return Contains {@link HealthResult} with status UP or DOWN.
     */
    @Override
    public Publisher<HealthResult> getResult() {
        return client.exchange(HttpRequest.HEAD("/"))
                     .map(this::checkStatusCode)
                     .onErrorReturn(this::statusException);
    }

    /**
     * Check response status code and return status UP when code is
     * between 200 and 399, otherwise return status DOWN.
     * 
     * @param response Reponse with status code.
     * @return Result with checked URL in the details and status UP or DOWN.
     */
    private HealthResult checkStatusCode(HttpResponse<?> response) {
        final int statusCode = response.getStatus().getCode();
        final boolean statusOk = statusCode >= 200 && statusCode < 400;
        final HealthStatus healthStatus = statusOk ? HealthStatus.UP : HealthStatus.DOWN;

        // We use the builder API of HealthResult to create 
        // the health result with a details section containing
        // the checked URL and the health status.
        return HealthResult.builder(NAME, healthStatus)
                           .details(Collections.singletonMap("url", URL))
                           .build();
    }

    /**
     * Set status is DOWN when exception occurs checking URL status.
     * 
     * @param exception Exception thrown when checking status.
     * @return Result with exception in details in status DOWN.
     */
    private HealthResult statusException(Throwable exception) {
        // We use the build API of HealthResult to include
        // the original exception in the details and 
        // status is DOWN.
        return HealthResult.builder(NAME, HealthStatus.DOWN)
                           .exception(exception)
                           .build();
    }

}

Finally we add configuration properties to our application.yml file:

# File: src/main/resources/application.yml
...
endpoints:
  health:
    enabled: true
    sensitive: false # non-secured endpoint
    details-visible: ANONYMOUS # show details for everyone
    url:
      enabled: true
...

Let's run our Micronaut application and invoke the /health endpoint. In the JSON response we see the output of our custom health indicator:

...
        "remote-url-health": {
            "details": {
                "url": "http://www.mrhaki.com/"
            },
            "name": "micronaut-sample",
            "status": "UP"
        },
...

When we use a URL that is not available we get the following output:

...
        "remote-url-health": {
            "details": {
                "error": "io.micronaut.http.client.exceptions.HttpClientResponseException: Not Found"
            },
            "name": "micronaut-sample",
            "status": "DOWN"
        },
...

Written with Micronaut 1.0.0.M4.