A Micronaut introduction

Olivier Revial - Jun 1 '20 - - Dev Community

Note: this article was originally published on Stack Labs blog.

Until not too long ago, when we wanted to develop JVM microservices, there wasn't much choice beyond the almighty Spring Boot. This is not true anymore with the arrival of frameworks such as Eclipse Microprofile, Helidon, Quarkus or Micronaut. In this article we will focus on Micronaut framework to discover how we can use it to develop microservices through a simple hello-world app example.

Micronaut goal

You might be wondering why using another JVM framework when Spring Boot is already working quite well for us.

If we read Micronaut official documentation introduction, it becomes clear what Micronaut claims to provide:

  • Fast startup time
  • Reduced memory footprint
  • Minimal use of reflection
  • Minimal use of proxies
  • Easy unit testing

By providing these points Micronaut clearly takes a completely different approach to the one taken by older frameworks like Spring Boot, Spring or Grails, and Micronaut developers know what they are talking about because they are... former Grails framework developers !

Although there are downsides to famous historical frameworks, a lot of things work well (well, that's why frameworks like Spring Boot became so popular, isn't it ?) and Micronaut intends to keep all these well-working things, such as dependency injection, auto-configuration, service discovery and HTTP clients and servers.

Now that we know what Micronaut goal is, let's dive into a real Micronaut application to see concretely how it goes for us developers ⌨️️

Creating a fully-featured app skeleton with Micronaut CLI

All the great frameworks provide a quick way to create a pre-configured app skeleton to enable for a very quick start. The idea behind this being that you spend less time initializing and configuring your app and more time actually coding and adding business value.

If you've ever seen one of Pivotal best speaker Josh Long, you already know "start dot spring dot io" where you can select the various features and other configurations for your brand new app.

Well Micronaut does not provide a web UI but a simple CLI called mn to allow easy app creation. You can easily install it using Sdkman or directly with Homebrew on a Mac.

Mac install:

> brew update && brew install micronaut
Enter fullscreen mode Exit fullscreen mode

Unix install:

# With Sdkman installed
> sdk update && sdk install micronaut
Enter fullscreen mode Exit fullscreen mode

➡️ Let's see what features Micronaut provides by running the following command:

> mn profile-info service

Resolving dependencies..
| Profile: service
--------------------
The service profile

| Provided Commands:
--------------------
  create-bean              Creates a singleton bean
  create-client            Creates a client interface
  create-controller        Creates a controller and associated test
  create-job               Creates a job with scheduled method
  create-repository        Creates a repository and associated test
  create-test              Creates a simple test for the project's testing framework
  create-websocket-client  Creates a Websocket client
  create-websocket-server  Creates a Websocket server
  help                     Prints help information for a specific command

| Provided Features:
| (+) denotes features included by default.
--------------------
  annotation-api (+)        Adds Java annotation API
  application (+)           Facilitates creating an executable JVM application and adds support for creating fat/uber JARs
  asciidoctor               Adds Asciidoctor documentation
  aws-api-gateway           Adds support for AWS API Gateway
  aws-api-gateway-graal     Creates an AWS API Gateway Proxy Lambda with Graal Native Image
  cassandra                 Adds support for Cassandra in the application
  config-consul             Adds support for Distributed Configuration with Consul (https://www.consul.io)
  data-hibernate-jpa        Adds support for Micronaut Data Hibernate/JPA
  data-jdbc                 Adds support for Micronaut Data JDBC
  discovery-consul          Adds support for Service Discovery with Consul (https://www.consul.io)
  discovery-eureka          Adds support for Service Discovery with Eureka
  ehcache                   Adds support for Ehcache (https://www.ehcache.org/)
  elasticsearch             Adds support for Elasticsearch in the application
  file-watch                Adds automatic restarts and file watch
  flyway                    Adds support for Flyway database migrations (https://flywaydb.org/)
  graal-native-image        Allows Building a GraalVM Native Image
  graphql                   Adds support for GraphQL in the application
  groovy                    Creates a Groovy application
  hazelcast                 Adds support for Hazelcast (https://hazelcast.org)
  hibernate-gorm            Adds support for GORM persistence framework
  hibernate-jpa             Adds support for Hibernate/JPA
  http-client (+)           Adds support for creating HTTP clients
  http-server (+)           Adds support for running a Netty server
  java                      Creates a Java application
  jdbc-dbcp                 Configures SQL DataSource instances using Commons DBCP
  jdbc-hikari               Configures SQL DataSource instances using Hikari Connection Pool
  jdbc-tomcat               Configures SQL DataSource instances using Tomcat Connection Pool
  jib                       Adds support for Jib builds
  jrebel                    Adds support for class reloading with JRebel (requires separate JRebel installation)
  junit                     Adds support for the JUnit 5 testing framework
  kafka                     Adds support for Kafka
  kafka-streams             Adds support for Kafka Streams
  kotlin                    Creates a Kotlin application
  kotlintest                Adds support for the KotlinTest testing framework
  kubernetes                Adds support for Kubernetes
  liquibase                 Adds support for Liquibase database migrations (http://www.liquibase.org/)
  log4j2                    Adds Log4j2 Logging
  logback (+)               Adds Logback Logging
  management                Adds support for management endpoints
  micrometer                Adds support for Micrometer metrics
  micrometer-appoptics      Adds support for Micrometer metrics (w/ AppOptics reporter)
  micrometer-atlas          Adds support for Micrometer metrics (w/ Atlas reporter)
  micrometer-azure-monitor  Adds support for Micrometer metrics (w/ Azure Monitor reporter)
  micrometer-cloudwatch     Adds support for Micrometer metrics (w/ AWS Cloudwatch reporter)
  micrometer-datadog        Adds support for Micrometer metrics (w/ Datadog reporter)
  micrometer-dynatrace      Adds support for Micrometer metrics (w/ Dynatrace reporter)
  micrometer-elastic        Adds support for Micrometer metrics (w/ Elastic reporter)
  micrometer-ganglia        Adds support for Micrometer metrics (w/ Ganglia reporter)
  micrometer-graphite       Adds support for Micrometer metrics (w/ Graphite reporter)
  micrometer-humio          Adds support for Micrometer metrics (w/ Humio reporter)
  micrometer-influx         Adds support for Micrometer metrics (w/ Influx reporter)
  micrometer-jmx            Adds support for Micrometer metrics (w/ Jmx reporter)
  micrometer-kairos         Adds support for Micrometer metrics (w/ Kairos reporter)
  micrometer-new-relic      Adds support for Micrometer metrics (w/ New Relic reporter)
  micrometer-prometheus     Adds support for Micrometer metrics (w/ Prometheus reporter)
  micrometer-signalfx       Adds support for Micrometer metrics (w/ SignalFx reporter)
  micrometer-stackdriver    Adds support for Micrometer metrics (w/ Stackdriver reporter)
  micrometer-statsd         Adds support for Micrometer metrics (w/ Statsd reporter)
  micrometer-wavefront      Adds support for Micrometer metrics (w/ Wavefront reporter)
  mongo-gorm                Configures GORM for MongoDB for Groovy applications
  mongo-reactive            Adds support for the Mongo Reactive Streams Driver
  neo4j-bolt                Adds support for the Neo4j Bolt Driver
  neo4j-gorm                Configures GORM for Neo4j for Groovy applications
  netflix-archaius          Adds support for Netflix Archaius in the application
  netflix-hystrix           Adds support for Netflix Hystrix in the application
  netflix-ribbon            Adds support for Netflix Ribbon in the application
  picocli                   Adds support for command line parsing (http://picocli.info)
  postgres-reactive         Adds support for the Reactive Postgres driver in the application
  rabbitmq                  Adds support for RabbitMQ in the application
  redis-lettuce             Configures the Lettuce driver for Redis
  security-jwt              Adds support for JWT (JSON Web Token) based Authentication
  security-session          Adds support for Session based Authentication
  spek                      Adds support for the Spek testing framework
  spock                     Adds support for the Spock testing framework
  springloaded              Adds support for class reloading with Spring-Loaded
  swagger-groovy            Configures Swagger (OpenAPI) Integration for Groovy
  swagger-java              Configures Swagger (OpenAPI) Integration for Java
  swagger-kotlin            Configures Swagger (OpenAPI) Integration for Kotlin
  tracing-jaeger            Adds support for distributed tracing with Jaeger (https://www.jaegertracing.io)
  tracing-zipkin            Adds support for distributed tracing with Zipkin (https://zipkin.io)
  vertx-mysql-client        Add support for the Reactive MySQL Client in the application
  vertx-pg-client           Add support for the Reactive Postgres Client in the application
Enter fullscreen mode Exit fullscreen mode

Great, we can see all the features provided by Micronaut. By the way we can note that some features such as annotation-api, application, http-client, http-server or logback are included by default in any application created through mn command as they are considered basic features for any real-world application 🙂

➡️ Let's now create a hello-world Java application with a few features (plus features included by default) and with Maven build tool (mn defaults to Gradle):

> mn create-app com.stacklabs.micronaut.hello-world --lang=java --build=maven --features=junit 
| Generating Java project...
| Application created at /Users/path/to/the/micronaut-workshop/hello-world
Enter fullscreen mode Exit fullscreen mode

Great, we now have our app skeleton, time to code !

Note: Micronaut CLI is great for initializing an app and avoiding the creation of Maven/Gradle boilerplate, but it is pretty basic and you cannot use it to add new features to your app once it has been initialized. If you have forgotten features, you will have to add them manually on your existing app build.

Creating a simple controller

The controller

We will now create a controller, but before that take a look at the main class generated for us by mn command:

public class Application {

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

Not too complicated right ?

➡️ Now, to create our controller we can use mn command again:

> mn create-controller Hello
| Rendered template Controller.java to destination src/main/java/fr/stacklabs/HelloController.java
| Rendered template Spec.groovy to destination src/test/groovy/fr/stacklabs/HelloControllerSpec.groovy
Enter fullscreen mode Exit fullscreen mode

As we can see, the command line generated a Java class (because we created a Java app) for our controller and its test counterpart. Let's have a look at this controller:

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.HttpStatus;

@Controller("/hello")
public class HelloController {

    @Get("/")
    public HttpStatus index() {
        return HttpStatus.OK;
    }
}
Enter fullscreen mode Exit fullscreen mode

Pretty basic, the default skeleton creates a controller with a simple endpoint returning a HTTP 200. Note that the @Controller and @Get annotations are not JAX-RS annotations but Micronaut specific annotations.

➡️ Let's modify it so it adds "Hello Micronaut !" to our HTTP 200 response body:

@Controller("/hello")
public class HelloController {

    @Get("/")
    public String index() {
        return "Hello Micronaut !";
    }
}
Enter fullscreen mode Exit fullscreen mode

The test

➡️ Let's now open the test and check the default content:

import io.micronaut.context.ApplicationContext;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.RxHttpClient;
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 HelloControllerTest {

    @Inject
    EmbeddedServer embeddedServer;

    @Test
    public void testIndex() throws Exception {
        try(RxHttpClient client = embeddedServer.getApplicationContext().createBean(RxHttpClient.class, embeddedServer.getURL())) {
            assertEquals(HttpStatus.OK, client.toBlocking().exchange("/hello").status());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

What do we do have here ?

  • @MicronautTest annotation tells Micronaut that it needs to make the ApplicationContext available for our test
  • We use a constructor injection to inject an embedded server into our test
  • @test is a simple JUnit 5 annotation, nothing new here
  • Using the application context of the embedded server, we will create a simple HTTP Client bean and we configure it to connect to our embedded server (dynamic) url
  • We then simply execute a blocking GET request to test our very first controller method

➡️ Although this test will work (we are returning a string within an HTTP 200), let's modify it to test our "hello" string response instead:

@MicronautTest
public class HelloControllerTest {

    @Inject
    EmbeddedServer embeddedServer;

    @Test
    public void testIndex() throws Exception {
        try(RxHttpClient client = embeddedServer.getApplicationContext().createBean(RxHttpClient.class, embeddedServer.getURL())) {
            assertEquals("Hello Micronaut !", client.retrieve("/hello").blockingFirst());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

We are now ready to run the test using maven wrapper:

> ./mvnw test
Enter fullscreen mode Exit fullscreen mode

Once finished, you should see an output similar to:

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.stacklabs.micronaut.workshop.agency.HelloControllerTest
23:18:45.538 [main] INFO  i.m.context.env.DefaultEnvironment - Established active environments: [test]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.62 s - in com.stacklabs.micronaut.workshop.agency.HelloControllerTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
Enter fullscreen mode Exit fullscreen mode

Running the app

You probably love tests but you might want to run the app, so let's do that.

➡️ First let's compile and package our application:

> ./mvnw clean install
Enter fullscreen mode Exit fullscreen mode

By default Micronaut will add Shadow Jar dependencies and plugins to our build so it generates two jars, the "original" one that only contain
our application classes, and a self-contained executable "uber-jar" (or fat-jar) that we can deploy as is.

➡️ Let's check the output of our build:

> ls -l target/
-rw-rw-r-- 1 user group  13M mars  10 13:14 hello-world-0.1.jar
-rw-rw-r-- 1 user group  11K mars  10 13:14 original-hello-world-0.1.jar
Enter fullscreen mode Exit fullscreen mode

➡️ We can now launch our app using the fat-jar and interact with it using our browser or curl:

> java -jar target/hello-world-0.1.jar
13:24:15.361 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 968ms. Server Running: http://localhost:8080
> curl http://localhost:8080/hello
Hello Micronaut !
Enter fullscreen mode Exit fullscreen mode

That's it, we now have a working REST application that we can test, compile and run !

Going further...

...with Micronaut

Of course we only created and developed a simple hello-world application so you might be a bit frustrated, but the goal of this article was simply showing how simple it is to both create and develop a Micronaut application.

You may ask yourself: where should I start to dive further into Micronaut framework ? Well it all depends on what you are trying to achieve but here are some hints:

...beyond Micronaut

As stated in the introduction we now have quite a lot of new JVM frameworks that aim to answer today's microservices needs. Although we won't compare them to Micronaut in this article (maybe in a future article ?), I'll still leave some links here in case you want to dig into one of them:


References:

I can only recommend the great Micronaut documentation that provides most of the information you need. You can basically find it in two places:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .