Micronaut for Microservices Part II

J Banks
Software Tidbits
Published in
8 min readSep 14, 2019

About

This session expands on the first by providing enough detail of the framework to construct an actual microservice. In the first session, reasons to consider Micronaut were identified along with specifics on how value is added over existing frameworks (e.g. Spring). If you missed that session, I recommend the quick read (referenced below) to better understand why to consider Micronaut for microservice or serverless development.

Getting Started

With any microservices project, there are various options to consider when starting out. Options such as which build tool to utilize, the primary programming language, and additional dependencies. The easiest way to get up-and-running with Micronaut is to use the provided CLI (command-line interface). It’s not required, but does make the initial setup more pleasant.

Why Provide These Options?

Many organizations developing microservices have to support teams with different skill sets and technical interests. Micronaut is a full-stack polyglot framework for microservice development. Using the CLI, allows those preferences to be specified which best fit the development team.

It’s not uncommon to have one team using Kotlin with Gradle, another team using Groovy with Gradle, and another team using Java with Maven. All of these teams are co-existing in the same company with integration & CI/CD requirements. Micronaut’s framework supports this agility across development teams.

CLI Installation

Starting with the preliminary setup, the Micronaut CLI is operating system specific. The examples for this session’s CLI usage will be using Windows 10, but know there is support for setup in other environments.

There is excellent getting started information for other operating systems in Micronaut’s documentation.

For Windows 10, follow Micronaut’s directions to the binary on how to obtain the binary, and then use the CLI to create a server applications.

Create a Server Application

Once installed, run the Micronaut CLI so you can issue commands to create a server application. Make sure to add the Micronaut home directory to the environment PATH so the binary CLI command can be issued from any directory.

Next, startup the CLI from the command window using the mn command provided after the installation.

c:\> mn

Once inside the CLI, issue the help option (-h) to get more information about this command. In the following example, help is being obtained for the create-app using the -h option.

mn> create-app -h

The help flag provides a list of additional options to be used when executing the create-app command.

Example Command: create-app -h

For this example, the focus will be on a few of the options available.

When creating the application, Gradle will be identified as the build tool. Kotlin will be specified as the language of choice for setting up the project, and the -v option will be used to obtain verbose output when the command is executed.

The create-app command doesn’t require these options, the create-app command can be issued with all defaults applied. Currently, Gradle defaults for the build tool, with Java as the target language, and no verbose output.

Lastly, the name of the project will be included (e.g. “micronaut-howdy”) as the final argument of the create-app command.

mn> create-app -b gradle -l kotlin -v micronaut-howdy| Generating Kotlin project...
| Application created at C:\_projects\micronaut\miconaut-howdy
| Initializing application. Please wait...

A side note for those wondering what other primary commands are supported in addition to create-app. Just type help at the mn command prompt. Not only can an application be created, a profile, cli-app, federation, and function can be created. See the Micronaut documentation for those options.

Structure Created

From the directory where the create-app command was issued results in the structure shown below.

Inside the directory, a project scaffolding was constructed, providing project structure all ready for import into an IDE. Two key files residing in the root project directory are the build.gradle and micronaut-cli.yml that were created specific to the options provided using the CLI.

Since the command specified Gradle as the build tool, the proper Gradle configurations are in place along with the build.gradle script. Inside the script there are specific Kotlin dependencies along with key dependencies for Micronaut.

At this point, now that the create-app command has completed, the run task can be issued to startup the microservice.

> gradlew run

This results in the following output for the run command:

> Task :run
20:36:23.575 [main] INFO io.micronaut.runtime.Micronaut — Startup completed in 2666ms. Server Running: http://localhost:8080

That is how fast, in just a few steps, a Micronaut application is up and running!

Creating a Web Controller

To make this project example more meaningful, let’s create a controller in the stubbed out application so requests over HTTP can be received with a somewhat meaningful response.

The following greeting controller will be added, where when the howdy resource path is issued, a plain text message is returned back as a response.

Similar to the way Spring provides annotations, Micronaut uses the @Controller annotation on the controller class. The @Get annotation is used to identify an HTTP GET request, which also identifies the MediaType being returned as plain text.

package micronaut.howdy.controller

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

@Controller("/howdy")
open class GreetingController {

@Get(processes = [MediaType.TEXT_PLAIN])
internal fun howdy(): String {
return "Howdy from Micronaut!!"
}
}

When running this simple controller, the following result is displayed when an HTTP GET request is sent.

Browser — GET Simple Greeting

Adding Complexity

Now, let’s add some business logic by adding a reference to a Greeting Generator. The generator will provide lingual translations of “Howdy” based on a locale input.

Starting with a LanguageGreeting interface, the function sayHowdyIn(…) is defined. A single parameter is provided to identify the language as a java.util.Locale.

package micronaut.howdy.generator

import java.util.*

/**
* Interface for greeting based on locale input.
*/
interface LanguageGreeting {
fun sayHowdyIn(language: Locale):String
}

The class that implements this interface will be named GreetingGenerator. This class will will manage mappings of a language locale to the translation of “howdy”.

package micronaut.howdy.generator

import java.util.*
import javax.inject.Singleton
import javax.validation.constraints.NotBlank

const val TRANSLATION_UNKNOWN = "Unknown Greeting Translation"

@Singleton
class GreetingGenerator : LanguageGreeting {

private val messages = mapOf(
Locale.ENGLISH to "Howdy",
Locale.FRENCH to "Salut",
Locale.GERMAN to "Hallo")

override fun sayHowdyIn(@NotBlank language:Locale):String {
return messages.getOrDefault(language, TRANSLATION_UNKNOWN)
}
}

Now, with the controller and its associated language generator in-place, the HTTP request will be handled when invoked, and delegated to the generator to do the work of the translation. This decouples the web-controller logic residing in the GreetingController from the business logic contained in the GreetingGenerator.

New Version of Controller

In the second iteration of the controller, the URI “/translate/{locale}” is introduced. The {locale} will be of type String used as the target value in the translation. Referring back to the generator, recall that 3 types of locales are supported (English, German, and French). When a locale that is not recognized, the value of the constant TRANSLATION_UNKNOWN will be returned.

package micronaut.howdy.controller

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

import micronaut.howdy.generator.GreetingGenerator
import sun.util.locale.LanguageTag
import java.util.*

@Controller("/howdy")
open class GreetingController internal constructor(private var
generator: GreetingGenerator) {

@Get(uri = "/translate/{locale}",processes=[MediaType.TEXT_PLAIN])
internal fun translate(locale: String): String {
val tag: LanguageTag = LanguageTag.parse(locale, null)
return generator.sayHowdyIn(Locale.forLanguageTag(tag.language))
}
}

Invoking Translate

Response for a locale of English “en”.

English Howdy

Response for locale of German “de”.

German Hallo

Response for locale of French “fr”.

French Salut

Response for locale of “foobar”.

Unknown Translation

Testing ?

In my opinion, every developer should be doing some level of testing along with their development. If you are not, you might want to research the benefits to code quality and how it reduces the 3 a.m. calls when the customer locates a bug easily caught with a test.

Micronaut provides a simple way to get started with testing through a testing framework extension called Micronaut Test.

An interesting aspect of this framework extension is it supports both functional and unit-level testing. Many times, the driving factor for keeping these types of tests separate is that functional tests can be timely to run and may consume more memory.

The increase in runtime and memory is largely due to dependencies loaded for functional testing. In comparison, unit-level tests are typically written and executed in isolation, while targeting a single piece of logic.

In the previous session, it was identified that Micronaut supports Java, Kotlin, and Groovy for polyglot development. As such, it makes sense for Micronaut Test to support three testing frameworks that utilize the three languages. As of this writing, the Micronaut Test extension version 1.1.x supports JUnit5.5, Kotlin Test, and Spock.

Summary

In part II, a Micronaut microservice project was created based on developer preferences using the Micronaut CLI. Once created, some simple web controller logic was added along with business logic for lingual translations.

All of this was quickly setup, with a touch of manual verification using a browser. The simplicity of Micronaut was illustrated using built-in dependencies supporting everything demonstrated out of the box.

To learn how to add unit and functional test coverage to a Micronaut application, stay tuned for Micronaut for Microservice Part III for a deep-dive into testing capabilities and options.

Until next time…

References

--

--