Cloud-Native Microservices with Micronaut

Kenny Chen
Lexicon Digital
6 min readJul 7, 2020

--

I believe in Object-Oriented Programming and have been a Java developer my entire career… In the last 12 months, I switched to Golang to build several microservices, as the client I was working for was moving away from Java to more cloud-oriented application development. This allowed me to focus on quicker development lifecycle, lower memory usage, and quicker startup times.

After using Golang, I truly understood the problems related to Java/Spring-Boot based applications, especially in Cloud-based environments where things like startup time and memory consumption are really important. I wondered how myself, a Java developer, could embrace this new development platform. This led me to Micronaut, a Java-based cloud-native application framework.

You can read about Micronaut here and how it works. Overall, it compiles Java byte code into native machine code using GraalVM with the support of Dependency-injection and Hibernate. This enables Micronaut to provide faster startup time and lower memory usage. From its official release in 2018, it has reduced the startup time down to around 20ms, and with the latest release, it is said to have improved by another 20%.

Lets now take a look at my example below of how this all fits together. The below diagram shows the overall idea. Here is the repository containing all the code/configuration used for the project. The repository is bare, however, soon it will be clearer.

Overview

The application is quite simple and has an endpoint to return a random number

RandController.kt

Before we start, you will have to set up your GCP account with Billing and have all the above Google products enabled. I found the instructions provided by Google quite easy to follow as I did not aim for proper security and organisational setup.

NOTE: you will be charged for Cloud-Build, Cloud-Storage and Cloud-Run.

Code commit:

I chose to use GitHub and synced the code to Cloud Source, you can have the code committed directly to Cloud Source.

Repository Sync:

To set up the sync is simple, just follow the instructions “Mirroring a GitHub repository” found on Google. The screenshot below shows the settings once you have correctly set up the sync.

Cloud-Source Repository Sync settings

Build & Deploy:

Once your code is committed and synced, your Cloud-build will pick up the new commit and will start to build and deploy your new code changes.

You first need to follow the instructions to create the build trigger. At this point, your Cloud-Source repository should be ready. The screenshot below are settings of my application trigger.

Cloud-Build trigger to build/deploy

Notice that I am using cloudbuild.yaml to instruct Cloud-Build the steps and configuration need to build and deploy the app

Notice that, in the “options” section of the cloudbuild.yaml, I have to pick an overpowered machine type to build my image. This is because at the time of writing Cloud-Build only support two Machine Types: 8 CPU or 32 CPU. Building a native-image using GraavlVM takes a large amount of RAM and CPU power.

On average, it took about 5min to finish the build and deploy my project. The building of the native image took about half of the total duration. Therefore, I suggest being patient when the build is running and possibly making a coffee…

Once your build is successful and the image is pushed to Cloud-Storage, the Cloud-Build will kick off the deployment and you can see in the Cloud-Run that your application is deployed.

The configuration of your Cloud-Run is specified in your cloudbuild.yaml as well. It starts from line 12 to 22. You can refer to the Cloud-Run documentation to change these options in your cloudbuild.yaml.

Notice that I have not added “ — allow-unauthenticated” option in the deployment, which means you wouldn’t be able to access my endpoint deployed in GCP.

List of applications deployed with Cloud-Run.

If you click on the name of the deployment, you will see the details of the deployment and you can find your endpoint accessible via a URL. The “details” page also has a lot of information about your deployment.

Access your endpoint from Cloud-Run

If you access the URL with /rand, you should see the result similar to the screenshot below.

/rand endpoint in the browser

Let’s now have a look at the logs and configuration for startup time and memory consumption.

The log indicates that the startup of the application took 306ms. It is possible the delay is related to the docker container deployment in GCP. More on this in a later section.

Startup native image in Cloud-Run

and the configuration shows that we only use 256RAM, this is the least memory required for this project.

Running native image locally:

As you can see running the image locally without the docker container is much faster, the startup time took only 23ms and it consumes only 16mb of memory.

Startup native image locally

Memory consumption of Micronaut

Compare with Golang:

I have set up a little GoLang microservice using Chi with the same endpoint, for a very simple startup time & memory usage comparison. As you can see the startup time of the GoChi microservice is around 35ms and the memory consumption is only 1.5MB.

Startup time for GoChi

Memory consumption of GoChi

This is not a very sophisticated comparison but as you can see both frameworks have similar startup time, but GoLang still shines with just 1.5MB of memory consumption.

There are various factors that can impact the startup time of the application, 35ms vs 23ms is quite close in my opinion.

Running native locally with docker:

You are also able to run the dockerized native-image locally. To build the docker image locally run

./docker-build.sh

The build will take a while and you’ll require GraalVM 11.0 or above for it work. After the build, follow the instructions printed to start the application in a docker.

docker run -p 8080:8080 micronaut-rest

As you can see, locally even within the docker image, the application started in 30ms. Not bad…

Local docker image startup

Conclusion

There are still a lot of areas that both Micronaut and GraavlVM can improve on. For example, reducing the build time and resources required to build native-images.

I personally believe this is one of the future paths where Java can be an option for Serverless kinds of architecture.

I hope you have enjoyed this article and let’s hope there will be more innovations in this area from the wider software community.

There are other Java cloud-native frameworks out there, Quarkus is another very competitive framework. There are many articles comparing both frameworks with Spring-Boot.

--

--