Micronaut Microservices Framework: Introduction

No Comments

1. Introduction

Welcome Micronaut: a modern JVM-based full-stack framework. If you have not yet heard of Micronaut, then it is time to get informed and try it out. In this blog post you will get acquainted with Micronaut’s key features, scaffolding tools and a simple HTTP server and client example.

Here is a list of Micronaut’s key features:

Supports Java, Groovy, and Kotlin. Being a JVM-based framework, Micronaut provides first-class support for Java, Groovy and Kotlin.

Natively cloud-native. Micronaut’s cloud support is built right in, including support for common discovery services, distributed tracing tools, and cloud runtimes. Micronaut is ready to develop serverless applications and is designed for building resilient microservices.

Fast startup time and low memory consumption. Micronaut avoids using reflection at runtime and uses compile-time AST transformations instead. This is why Micronaut has a fast startup time and a minimal memory footprint.

Reactive and non-blocking. Micronaut is a non-blocking HTTP server built on Netty and has a declarative, reactive and compile-time HTTP client.

Fast and easy testing. Efficient compile-time dependency injection and AOP.

Fast data-access configuration. Micronaut provides sensible defaults that automatically configure your favourite data access toolkit and APIs to make it easy to write your own integrations.

2. Prerequisites

In order to run the examples in this article, you need to install the following tools:

  • SDKMAN – a tool for managing parallel versions of multiple SDKs

    curl -s "https://get.sdkman.io" | bash
  • Java Development Kit

    sdk install java
  • Micronaut CLI

    sdk install micronaut

3. Scaffolding

As many other modern frameworks, Micronaut comes with a handy CLI scaffolding tool. Micronaut CLI can be used as a CLI tool:

mn help
mn help create-app

It can also be used in interactive mode with command completion.

$ mn
| Starting interactive mode...
| Enter a command name to run. Use TAB for completion:
mn> help
...
mn> help create-app
...

Try both modes and see what you like more.

3.1. Creating a new Gradle project

Micronaut supports Java, Groovy, and Kotlin as first-class citizens. Let us create a new Java application using Micronaut CLI:

mn create-app mn-hello-java

This will scaffold a new Gradle project. If you prefer Maven, add a --build maven parameter. If you want to create a new Groovy or Kotlin project, add a --lang parameter:

mn create-app --lang groovy mn-hello-groovy
mn create-app --lang kotlin mn-hello-kotlin

By default Micronaut HTTP server will listen on a random port, but you can alter that by adding the following configuration to src/main/resources/application.yml:

src/main/resources/application.yml
micronaut:
    server:
        port: 8080

The application can be immediately run with Gradle:

cd mn-hello-java
./gradlew run
> Task :run
 
11:56:49.340 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 654ms. Server Running: http://localhost:8080
<=========----> 75% EXECUTING [7s]

Now we can check that Micronaut is running:

curl http://localhost:8080
{"_links":{"self":{"href":"/","templated":false}},"message":"Page Not Found"}

To run the application with IntelliJ IDEA, you need to enable annotation processing:

  1. open Settings / Build, Execution, Deployment / Compiler / Annotation Processors

  2. Set the checkbox “Enable annotation processing”

Then you can run the application by executing the application class in IDEA.

3.2. Adding an HTTP controller

Let us use the CLI again to create an HTTP controller. Run this command in the directory of the project that we created:

mn create-controller HelloController
| Rendered template Controller.java to destination src/main/java/mn/hello/java/HelloController.java
| Rendered template ControllerTest.java to destination src/test/java/mn/hello/java/HelloControllerTest.java

As we can see, Micronaut cares about Test-Driven Development and has created a test along with the controller:

HelloController.java
package mn.hello.java;
 
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;
    }
}
HelloControllerTest.java
package mn.hello.java;
 
import io.micronaut.context.ApplicationContext;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.runtime.server.EmbeddedServer;
import org.junit.Test;
 
 
import static org.junit.Assert.assertEquals;
 
public class HelloControllerTest {
 
    @Test
    public void testIndex() throws Exception {
        EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class);
 
        RxHttpClient client = server.getApplicationContext().createBean(RxHttpClient.class, server.getURL());
 
        assertEquals(HttpStatus.OK, client.toBlocking().exchange("/hello").status());
        server.stop();
    }
}

3.3. Adding a compile-time-generated HTTP client

Micronaut provides a great declarative compile-time HTTP client. You can use it for testing your own server (as we will do in the next example) or for communicating with external servers.

mn create-client HelloClient
| Rendered template Client.java to destination src/main/java/mn/hello/java/HelloClient.java

Here is what Micronaut creates:

HelloClient.java
package mn.hello.java;
 
import io.micronaut.http.client.Client;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.HttpStatus;
 
@Client("hello")
public interface HelloClient {
 
    @Get("/")
    HttpStatus index();
}

That is all of it – only the interface. Micronaut will generate the implementation at compile time.

4. Hello World

Now that we got acquainted with Micronaut’s scaffolding CLI, let us create a more comprehensive example. We will extract the server API and reuse it in the HTTP controller in the production code and in the HTTP client in the test:

hello micronaut class diagram
Figure 2. Sharing server and client API

4.1. Server API

First, let us define the server API:

HelloApi.java
package mn.hello.java;
 
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.QueryValue;
 
public interface HelloApi {
 
    @Get("/")
    HelloMessage index(@QueryValue("name") String name);
}

Here is what is happening:

  • the annotation @Get("/") specifies the path and the HTTP method of the endpoint

  • the annotation @QueryValue("name") maps the GET parameter name to the method parameter name

  • the return type HelloMessage tells Micronaut to serialize the POJO returned by the HTTP controller to JSON

HelloMessage.java
package mn.hello.java;
 
public class HelloMessage {
    public String greeting;
}

4.2. HTTP controller

Let us implement a simple HTTP controller that will return a greeting for the given name:

HelloController.java
package mn.hello.java;
 
import io.micronaut.http.annotation.Controller;
 
@Controller("/hello")
public class HelloController implements HelloApi {
 
    @Override
    public HelloMessage index(String name) {
        HelloMessage m = new HelloMessage();
        m.greeting = "Hello " + name + "!";
        return m;
    }
}

Since we return a POJO in the controller, Micronaut considers the method blocking and will execute it on the I/O thread pool. However, it is also possible to write a non-blocking reactive implementation and return a non-blocking type such as Single. In this case, the request is considered non-blocking and the method will be executed on the Netty event loop thread.

4.3. HTTP client & testing

Since we extracted the server API into a separate interface, we can now easily create an HTTP client for our application:

HelloClient.java
package mn.hello.java;
 
import io.micronaut.http.client.Client;
 
@Client("/hello")
public interface HelloClient extends HelloApi {}

We don’t need to write any implementation, Micronaut will do that for us at compile time. Here is what a test can look like using this HTTP client:

HelloControllerTest.java
package mn.hello.java;
 
import io.micronaut.context.ApplicationContext;
import io.micronaut.runtime.server.EmbeddedServer;
import org.junit.Test;
 
import static org.junit.Assert.assertEquals;
 
public class HelloControllerTest {
 
    @Test
    public void testIndex() throws Exception {
        EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class);
 
        HelloClient client = server.getApplicationContext().getBean(HelloClient.class);
 
        HelloMessage serverResponse = client.index("codecentric");
 
        assertEquals("Hello codecentric!", serverResponse.greeting);
        server.stop();
    }
}

In this test we actually spin up our Micronaut application and execute an HTTP request against it using the generated HTTP client.

4.4. Docker-ready

Micronaut CLI generates a Dockerfile as well, making it easy to package your application for a container environment such as Kubernetes. Let us run our example application with Docker:

  1. Build the application into a fat-jar:

    ./gradlew build
  2. Build a Docker image:

    docker build . -t mn-hello-world
  3. Run the Docker image:

    docker run --rm -p 8080:8080 mn-hello-world
  4. Check that it is running:

    curl http://localhost:8080/hello?name=codecentric
    {"greeting":"Hello codecentric!"}

5. Conclusion

In this article we have only scratched the surface of what Micronaut has to offer. Here is what’s left out of scope:

  • Reactive programming

  • Database access with GORM

  • Service discovery

  • Serverless applications

  • Distributed tracing

All in all, Micronaut is a fast-evolving framework and looks very promising. Go try it yourself or maybe even use it for your next project!

Full stack software developer at codecentric. Interested in software architecture, CI/CD and TDD.

Comment

Your email address will not be published. Required fields are marked *