Evaluating Micronaut

micronaut

I have been using Spring Boot for sometime, however I always find it excessively heavy when trying to build micro services. Jetty, while one of my favorite application servers, also feels like taking too long to start up when spinning up a container. So I always keep on trying to find alternate ways to host my Java code.

Some years back, two of my favorite alternatives were Spark Java and Vert.x. At that time I was into trying quite a bit of reactive applications on the JVM. Vert.x came with its own event bus and creating event driven asynchronous application was a breeze.

So when time came for my next personal project, and I could start with a fresh slate, I thought of starting to look for what’s new in frameworks.

While looking for frameworks, I found Micronaut as one of the alternatives. They promise to be “A modern, JVM-based, full-stack framework for building modular, easily testable microservice and serverless applications”. That sounded like everything that you would need – and it is by the same team that gave us grails, so they know their spring boot well. While spring relies on runtime reflection to do AOP and DI, micronaut does it without use of reflection and bakes it in during compilation. Micronaut eventually produces smaller code and a blazing fast server startup.

The First Steps

Micronaut can be directly used as dependency in maven, or it provides a command line interface to simplify project creation. To start, I thought I will get the CLI and create my first application. I am on a Mac, so could just download using Homebrew, but other options for download are also available.

% brew update
% brew install micronaut

Overall, that is what is needed to install micronaut. To get my feet wet, I thought I will just create a very simple application, that will return me some Zen quotes. It should also be allowed to add new ones to it, but to keep it simple, I did not want to get a DB yet – so quotes would not be saved. Micronaut by default builds gradle projects, but I wanted to use maven, so that was the only parameter I sent to CLI. Everything else remains default.

% mn create-app zen-proverbs --build maven

I see by default it built a Dockerfile so that I can deploy it to a container if I need to. It got the base image for openjdk13 from adoptopenjdk to build the docker image. Rest of the file is pretty standard. Also by design, generated pom file will build a uber jar using the shade plugin.

Zen starts here…

My Zen POJO has an author and proverb. I have also kept a type to identify what has been added at a later time, so that I can do some operations on the custom proverbs if need be. Below is my bean.

package zen.proverbs.beans;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonRootName;
import io.micronaut.context.annotation.Primary;
import io.micronaut.core.annotation.Introspected;

@Primary
@Introspected
@JsonRootName("zen")
@JsonIgnoreProperties({"type"})
public class ZenBean {
    private char   type;
    private String author;
    private String proverb;

    public ZenBean() {

    }

    public ZenBean(final char type, final String author, final String proverb) {
        this.type = type;
        this.author = author;
        this.proverb = proverb;
    }
  
  /* Getters and Setters not shown */
}

Let’s analyze the micronaut specific annotations. Primary means that this is the primary bean and should be selected in case of conflicts with other implemented interfaces. Introspected will generate the bean metadata (introspection) for this object at design time. Rest of the class is pretty mundane, having a default constructor and also an all argument constructor.

package zen.proverbs.service;

import zen.proverbs.beans.ZenBean;

import java.util.List;

public interface ZenService {

    public int getProverbCount();
    public int getDefaultProverbCount();
    public int getCustomProverbCount();
    public ZenBean getProverb();
    public List<ZenBean> getProverbs();
    public List<ZenBean> getDefaultProverbs();
    public List<ZenBean> getCustomProverbs();
    public void addProverb(final ZenBean prov);
    public void addProverbs(final List<ZenBean> prov);
}
package zen.proverbs.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import zen.proverbs.beans.ZenBean;

import javax.inject.Singleton;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Singleton
public class ZenServiceImpl implements ZenService {

    private static final List<ZenBean> ZEN_DATA_ORG = Arrays.asList(
            new ZenBean('a', "Unknown", "If you do not get it from yourself, Where will you go for it?"),
            /* Skipped */
      		new ZenBean('a', "Unknown", "If you're attached to anything, you surely will go far astray."),
            new ZenBean('a', "Jesus Christ", "Let the dead bury the dead"),
            new ZenBean('a', "Unknown", "One falling leaf is not just one leaf; it means the whole autumn."),
            new ZenBean('a', "Unknown", "Knock on the sky and listen to the sound.")
    );

    private static final Logger LOG = LoggerFactory.getLogger(ZenService.class);
    private static final SecureRandom SRAND = new SecureRandom();

    private final List<ZenBean> ZEN_DATA;

    public ZenServiceImpl() {
        ZEN_DATA = new ArrayList<>();
        ZEN_DATA.addAll(ZEN_DATA_ORG);
    }
    public int getProverbCount() {
        return ZEN_DATA.size();
    }

    public int getDefaultProverbCount() {
        return (int) ZEN_DATA.stream()
                .filter(l -> l.getType() == 'a').count();
    }

    public int getCustomProverbCount() {
        return (int) ZEN_DATA.stream()
                .filter(l -> l.getType() == 'c').count();
    }

    public ZenBean getProverb() {
        final ZenBean aquote = ZEN_DATA.get(SRAND.nextInt(ZEN_DATA.size()));
        LOG.info("Returning proverb {} by {}", aquote.getProverb(), aquote.getAuthor());
        return aquote;
    }

    public List<ZenBean> getProverbs() {
        return ZEN_DATA;
    }

    public List<ZenBean> getDefaultProverbs() {
        return ZEN_DATA.stream()
                .filter(l -> l.getType() == 'a').collect(Collectors.toList());
    }

    public List<ZenBean> getCustomProverbs() {
        return ZEN_DATA.stream()
                .filter(l -> l.getType() == 'c').collect(Collectors.toList());
    }

    public void addProverb(final ZenBean prov) {
        LOG.info("Adding proverb from author: {}", prov.getAuthor());
        prov.setType('c');
        ZEN_DATA.add(prov);
    }

    public void addProverbs(final List<ZenBean> prov) {
        LOG.debug("Adding proverb count: {}", prov.size());
        prov.forEach(p -> addProverb(p));
    }
}

Preceding two boxes show the codes for services. First one defines the interface agreement for the service and the second one is the implementation. The quotes I took from https://belovequotes.com/45-popular-zen-proverbs-sayings/. The only point of interest in the implementation code is the usage of annotation @Singleton. As the name implies this ensures that this class is only instantiated once. Rest of the class defines other service methods like adding a new proverb, returning random proverbs or return the full set of proverbs etc.

The final class that is required will be the controller. This is also made simple by providing annotation based DI.

package zen.proverbs.controller;

import io.micronaut.context.annotation.Requires;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import zen.proverbs.beans.ResponseBean;
import zen.proverbs.beans.ZenBean;
import zen.proverbs.service.ZenServiceImpl;

import javax.inject.Inject;
import java.util.List;

@Controller("/zen")
@Requires(beans = ZenServiceImpl.class)
public class ZenController {

    @Inject
    private ZenServiceImpl zens;

    @Get("/count")
    @Produces(MediaType.TEXT_PLAIN)
    public int getProverbCount() {
        return zens.getProverbCount();
    }

    @Get("/count/default")
    @Produces(MediaType.TEXT_PLAIN)
    public int getDefaultProverbCount() {
        return zens.getDefaultProverbCount();
    }

    @Get("/count/custom")
    @Produces(MediaType.TEXT_PLAIN)
    public int getCustomProverbCount() {
        return zens.getCustomProverbCount();
    }

    @Get("/proverb")
    @Produces(MediaType.APPLICATION_JSON)
    public ZenBean getProverb() {
        return zens.getProverb();
    }

    @Get("/proverbs")
    @Produces(MediaType.APPLICATION_JSON)
    public List<ZenBean> getProverbs() {
        return zens.getProverbs();
    }
    /* Rest of methods not shown */
}

This class is also self explanatory. We are defining the controller with ‘/zen’ endpoint and are also defining the Get and Post HTTP methods. We are also injecting the service bean here so that it is initialized when the class loads. Requires annotation in this case is optional, but just added for brevity. No real exception handling has been done anywhere, so exception will be re-thrown to the calling client.

The final class of interest is an auto generated class; it will start up this application using a Netty server (default is blocking calls).

package zen.proverbs;

import io.micronaut.runtime.Micronaut;

public class Application {

    public static void main(String[] args) {
        Micronaut.run(Application.class);
    }
}

Final steps is to build this service with Maven and startup the uber jar built. However, notice the server startup time. It is less than 2 seconds! That is what will be my biggest selling factor. Imagine this application sitting in the cloud where machines cycles and servers restarts every so often. Any saving in application server startup time will eventually add to lower downtime for my application. Of course I do not know how it performs under load, but that can be left for a separate discussion.

% java -jar target/zen-proverbs-0.1.jar
00:52:31.107 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed 
in 1635ms. Server Running: http://localhost:8080

Testing Capabilities

This evaluation will not be complete without the discussion for ease of unit testing capabilities. One of the claims for this framework is ‘easily testable’. So, I tried to see the capabilities for testing. Micronaut comes standard with an embedded server and HTTP client implementation. We can directly inject these two capabilities and have a very easy testing implementation. I created unit tests for the counts, and they worked without a hitch.

package zen.proverbs;

import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.annotation.MicronautTest;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;

import static org.junit.jupiter.api.Assertions.assertEquals;

@MicronautTest
public class TestCounts {
    @Inject
    EmbeddedServer server;

    @Inject
    @Client("/")
    HttpClient client;

    @Test
    public void getProverbCount() {
        final String ret = client.toBlocking()
                .retrieve(HttpRequest.GET("/zen/count"));
        assertEquals("20", ret);
    }

    @Test
    public void getDefaultProverbCount() {
        final String ret = client.toBlocking()
                .retrieve(HttpRequest.GET("/zen/count/default"));
        assertEquals("20", ret);
    }

    @Test
    public void getCustomProverbCount() {
        final String ret = client.toBlocking()
                .retrieve(HttpRequest.GET("/zen/count/custom"));
        assertEquals("0", ret);
    }
}

Conclusion

Based on this quick test, I am convinced that I am going to spend more time with this framework and may use it in my project.

Source for this project can be found at, https://github.com/chakrab/suturf-com.ibrave.host/tree/master/zen-proverbs

Ciao for now!