Introduction to Micronaut

Jacek Mazgaj

It’s hard to say and calculate how many phones and computers are currently connected to the internet, considering the IoT devices that number is probably significantly bigger. The amount of physical users also grows every day, and some of them are currently having a bunch of devices with internet access.

This leads to the need of constant growth of server infrastructures handling the requests from those devices. The term scalability on side of computer science stopped being a buzz word a long time ago, but also some time ago people noticed, the vertical scaling of a server infrastructure has a lot of restrictions (and can be much more expensive), then the horizontal scaling.
There are few tricks and techniques to scale out the monolithic application, but they tend to require lots of additional effort and if the number of users of such application would quickly grow they could still not be able to handle incoming requests in reasonable time.
The answer for such struggles is a microservice architecture which can scale out easily, but again, such solution creates different issues that need to be handled. Using a microservice approach leads to the necessity of handling the orchestration, provisioning and discovery of new nodes. The widely used java Spring framework requires the need of using additional dependencies and a bunch of configuration to allow for a simple service discovery.

Newborn in the Java family

Titular Micronaut java framework is mainly oriented on microservice architecture. As it home page says, it is a:

A modern, JVM-based, full-stack framework for building modular, easily testable microservice and serverless applications.

The first release of Micronaut framework took place in early 2018s, but besides being so fresh, it already contains lots of features. The framework was build from the ground by OCI company (the one behind Grails framework). Micronaut by default tries to provide necessary microservice system tools like:

  • Dependency Injection and Inversion of Control (IoC)
  • Sensible Defaults and Auto-Configuration
  • Configuration and Configuration Sharing
  • Service Discovery
  • HTTP Routing
  • HTTP Client with Client-Side Load Balancing

And tries to reduce some the cons of Spring and Grails frameworks by having:

  • Fast startup time
  • Reduced memory footprint
  • Minimal use of reflection
  • Minimal use of proxies
  • Easy Unit Testing

Currently Micronaut supports writing applications in JavaGroovy and Kotlin languages.

Main differences to Spring framework

Micronaut has built-in support for all necessary components for cloud development, making popular cloud features quickly available in the application.

The jar size, start-up time and memory usage of Micronaut are lower than in Spring, which makes it a better choice for developing serverless functions. The huge performance of Micronaut against Spring is caused by that, annotations in Micronaut are processed at compile time, therefore it reduces the need of using reflections and creating proxy objects at runtime to the minimum.

Currently, Spring has few advantages in side of ways the application can be configured, supports more cache providers and offers few more ways of management and monitoring of an application.

Installation

The preferred way of creating a new Micronaut application is to use the command line interface tool. It can be simply installed through Sdkman or Homebrew package managers, for OSes without those (poor Windows…), the binary archive needs to be downloaded, extracted to some folder. Later it requires to create the MICRONAUT_HOME environment variable, which would point to the extraction folder, and in the last step to update the environment PATH variable with %MICRONAUT_HOME%\bin. After that, the mn command with additional parameters can be used to create an application skeleton. Executing just the mn command without any argument starts the interactive mode.

Run the mn help to see available options and commands with their description. It’s also possible to run mn help <command-name>. For example, mn help create-app shows the usage, available flags and list of features that can be included in a new app.

NOTE: make sure to enable annotation processing when working in an IDE

The reason behind using the Micronaut CLI tool to create a new application is to ensure that all basic configuration files and necessary dependencies are delivered to the new application.

Sample project with the CLI

The simplest way to create a Micronaut skeleton application is to run the mn create-app my-app command (the last argument defines the project name). That command will create the my-app directory and generate basic and necessary files under it.

Standard setting of the Micronaut CLI, will put that project into my.app group and create the base package with the same name. To customize that behaviour the above command can be amended to mn create-app my.package.my-app, which results in group and and base package named as my.package.
By default, an application created with the mn command is using Gradle as the build tool. Add the --build maven option to the create-app command to switch to Maven. The -l option can be used to change the default project language. For example, to make a Kotlin application the below command can be invoked:

mn create-app mn-kotlin-sample -l kotlin

Similarly to the Spring Initializr, the generated project will have embedded Gradle Wrapper, basic .gitignore and application.properties files and the class with public static void main method with statements launching the application with the Micronaut context. Additionally, Micronaut creates basic Dockerfile and logback.xml configuration files.

To create an application with custom features requires the -f option with wanted features listed after it, for example:

mn create-app -f=elasticsearch,hibernate-jpa,junit,swagger-java mn-with-features

An application generated with the mn comes with the ShadowJar plugin and building a project also produces the jar artifact with all required dependencies (the file has -all postfix).

An alternative way for generating new Micronaut applications is the Microstarter.io.
The Microstarter.io is an online tool, very similar to the Spring Initializr. It allows us to define basic properties, language, group and artifact names, features and component stubs for a new project.
The generated project comes as ZIP archive with similar files and structure to the project generated with the CLI tool.
For sample usage refer to the ”Hello, Micronaut” Using Microstarter.io article which was written by the Microstarter.io author.

Additional features of the CLI

Inside a Micronaut application directory, we can take even bigger advantage of using the Micronaut CLI. Under the application dir mn allows us to use additional commands like: create-controllercreate-beancreate-client. Run the mn help to see those commands with their descriptions and mn help <cmd-name> to see the syntax and available options for given command name.

For example, running below command in a Micronaut application directory:

mn create-controller pac.kage.Home

Creates the HomeController class in pac.kage package with implementation from a standard template:

package pac.kage;

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

@Controller("/home")
public class HomeController {

    @Get("/")
    public HttpStatus index() {
        return HttpStatus.OK;
    }

    @Get(value = "/hello", produces = MediaType.TEXT_PLAIN) //manually added method
    public String hello() {
        return "Hello World";
    }
    @Get(value = "/{name}")
    public String helloWithName(String name) { //manually added method
        return "Hi " + name;
    }
}

In the same time, it creates a package with corresponding name in the test source set and a class named HomeControllerTest in that package:

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;

@MicronautTest
public class HomeControllerTest {
    @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("/home").status());
        }
    }
}

„A critical component of any Microservice architecture is the client communication between Microservices.” Having that outspoken, Micronaut features an easy way of creating a HTTP clients by invoking the create-client command. For example, calling the mn create-client pac.kage.Hello command creates:

@Client("/home") // by default it's created without '/', which throws an exception at runtime, so remember to add the '/'
public interface HomeClient {
    @Get("/")
    HttpStatus index();

    //below are manually added, to cover controller methods
    @Get(value = "/hello")
    Single<string> hello();

    @Get(value = "/{name}")
    Single<string> helloWithName(String name);
}

Such clients become handy when writing tests or integrating separate services. For example, the value inside the @Client annotation can be set to an external service like:

@Client("http://localhost:8081/home")

And later used in beans, jobs, etc, e.g.:

@Singleton
public class Job {
    @Inject
    HomeClient client;

    @Scheduled(fixedRate = "5s")
    public void process() {
        System.out.println(client.helloWithName("j-labs").blockingGet());
    }
}

HTTP Clients in Micronaut have both a low-level API and a higher level AOP-driven API.

AOP FTW!

As mentioned earlier, one of the goals of Micronaut is to reduce the use of reflections and runtime proxies to the minimum. The same attitude applies to the AOP (Aspect-Oriented Programming) patterns provided by the Micronaut. Let’s make an example using the AOP by implementing an „Around” advice (it decorates a method behaviour). The example implements a method calls counting aspect for methods annotated with the selfmade annotation. The first needed thing is an interceptor class:

@Singleton //indicates that only one instance of annotated class will exist in the application runtime
public class LogTimeInterceptor implements MethodInterceptor<object,object> {
    private static final Logger log = LoggerFactory.getLogger(LogTImeInterceptor.class);

    @Override
    public Object intercept(MethodInvocationContext<object, object=""> context) {
        String prettyMethod = context.getDeclaringType().getSimpleName() + "." + context.getName();
        long start = System.nanoTime();
        Object result = context.proceed();
        long end = System.nanoTime() - start;
        log.info("Execution of " + prettyMethod + " took: " + (end/1000) + "ms.");
        return result;
    }
}

Now, an annotation which injects the above interceptor is needed:

@Retention(RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Around //indicates around advice
@Type(LogTimeInterceptor.class) //this injects the interceptor
public @interface LogTime {
}

Annotating a method with the @LogTime will result in the LogTimeInterceptor.intercept method being called.
Using that annotation on class element will invoke the interceptor over every method in that element.

Creating multiple services at once

There are a few more cases where the Micronaut CLI becomes useful, one of them is an ability to create set of services/apps by invoking a single command when generating a new project. It uses the create-federation command with additional arguments similar to the example:

mn create-federation multiple-services -s=service1,service2,service3

This generates the multiple-services project with subprojects declared after the -s= option. The generated project will be structured to:

Root project 'multiple-services'
+--- Project ':service1'
+--- Project ':service2'
\--- Project ':service3'

The root project gets Gradle Wrapper and the settings.gradle file will automatically include generated service projects. Each of those services is generated with their own build scripts, properties files, classes with static main method and other files similar to a project generated with the create-app command.

Conclusion

For the big companies looking for stable and mature solutions, the Micronaut framework might be hard to adopt, because of its young age, but it is worth remembering that the mature software company stands behind Micronaut, which could be a factor in ensuring the stability of the framework. Micronaut aims to facilitate the work with microservices. Having similar concepts and terminology used in Spring framework should reduce the learning curve. The Micronaut CLI interface is a very powerful tool providing features for quick prototyping and generating basic project skeletons. In the growing tendency of using cloud architectures and solutions, makes the features offered by Micronaut a good choice for new projects and applications. Its goal to reduce the size of binary files and memory usage in the runtime environment, clearly says that the cost of delivering and maintaining the application will also be cheaper.

The Micronaut guides site contains examples of projects for different use cases along with step-by-step instructions. The best source of knowledge about the features and capabilities of Micronaut is its [user guide(https://docs.micronaut.io/latest/guide/index.html). It’s very well written and structured, moving from the basics to more and more advanced usage.

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami