Accelerate Java application development on GCP with Micronaut
Sergio del Amo
Senior Software Engineer, OCI
Editor’s note: Want to develop microservices in Java? Today we hear from Object Computing, Inc. (OCI), a Google Cloud partner that is also the driving force behind the Micronaut JVM framework. Here, OCI senior software engineer Sergio del Amo talks about how to use Micronaut on GCP to build serverless applications, and walks you through an example.
Traditional application architectures are being replaced by new patterns and technologies. Organizations are discovering great benefits to breaking so-called monolithic applications into smaller, service-oriented applications that work together in a distributed system. The new architectural patterns introduced by this shift call for the interaction of numerous, scope-limited, independent applications: microservices.
To support microservices, modern applications are built on cloud computing technologies, such as those provided by Google Cloud. Rather than managing the health of servers and data centers, organizations can deploy their applications to platforms where the details of servers are abstracted away, and services can be scaled, redeployed, and monitored using sophisticated tooling and automation.
In a cloud-native world, optimizing how a Java program’s logic is interpreted and run on cloud servers via annotations and other compilation details takes on new importance. Additionally, serverless computing adds incentive for applications to be lightweight and responsive and to consume minimal memory. Today's JVM frameworks need to ease not just development, as they have done over the past decade, but also operations.
Micronaut comes with built-in support for GCP services and hosting. Then, in addition to out-of-the-box auto-configurations, job scheduling, and myriad security options, Micronaut provides a suite of built-in cloud-native features, including:
- Service discovery. Service discovery means that applications are able to find each other (and make themselves findable) on a central registry, eliminating the need to look up URLs or hardcode server addresses in configuration. Micronaut builds service-discovery support directly into the
@Clientannotation, meaning that performing service discovery is as simple as supplying the correct configuration and then using the "service ID" of the desired service.
- Load balancing. When multiple instances of the same service are registered, Micronaut provides a form of "round-robin" load-balancing, cycling requests through the available instances to ensure that no one instance is overwhelmed or underutilized. This is a form of client-side load-balancing, where each instance either accepts a request or passes it along to the next instance of the service, spreading the load across available instances automatically.
- Retry mechanism and circuit breakers. When interacting with other services in a distributed system, it’s inevitable that at some point, things won’t work out as planned—perhaps a service goes down temporarily or drops a request. Micronaut offers a number of tools to gracefully handle these mishaps. Retry provides the ability to invoke failed operations. Circuit breakers protect the system from repetitive failures.
As a result of this natively cloud-native construction, you can use Micronaut in scenarios that would not be feasible with a traditional Model-View-Controller framework in the JVM, including low-memory microservices, Android applications, serverless functions, IoT deployments, and CLI applications.
Micronaut also provides a reactive HTTP server and client based on Netty, an asynchronous networking framework that offers high performance and a reactive, event-driven programming model.
Sample App: Google Cloud Translate API
To see how easy it is to integrate a Micronaut application with Google Cloud services, review this tutorial for building a sample application that consumes the Google Cloud Translation API.
Step 1: Install Micronaut
You can build Micronaut from the source on Github or download it as a binary and install it on your shell path. However the recommended way to install Micronaut is via SDKMAN!. If you do not have SDKMAN! installed already, you can do so in any Unix-based shell with the following commands:
You can now install Micronaut itself with the following SDKMAN! command (use
sdk list micronaut to view available versions; at the time of this writing, the latest is
Confirm that you have installed Micronaut by running mn -v:
Step 2: Create the project
mn command serves as Micronaut's CLI. You can use this command to create your new Micronaut project.
For this exercise, we will create a stock Java application, but you can also choose Groovy or Kotlin as your preferred language by supplying the
-lang flag (
-lang groovy or
mn command accepts a
features flag, where you can specify features that add support for various libraries and configurations in your project. You can view available features by running
mn profile-info service.
We're going to use the
spock feature to add support for the Spock testing framework to our Java project. Run the following command:
Note that we can supply a default package prefix (
example.micronaut) to the project name (
translator). If we did not do so, the project name would be used as a default package. This package will contain the
Application class and any classes generated using the CLI commands (as we will do shortly).
By default the
create-app command generates a Gradle build. If you prefer Maven as your build tool, you can do so using the
-build flag (e.g.,
-build maven). This exercise uses the default Gradle project.
At this point, you can run the application using the Gradle
TIP: If you would like to run your Micronaut project using an IDE, be sure that your IDE supports Java annotation processors and that this support is enabled for your project. In the IntelliJ IDEA, the relevant setting can be found under
Preferences -> Build, Execution, Deployment -> Compiler -> Annotation Processors -> Enabled.
Step 3: Create a simple interface
Create a Java interface to define the translation contract:
If I want to translate
Hello World to Spanish, you can invoke any available implementations of the previous interface with
translationService.translate( "Hello World", "en", "es") .
We create a POJO to encapsulate the translation result.
Step 4: Expose an endpoint
Similar to other MVC frameworks such as Grails or Spring Boot, you can expose an endpoint by creating a controller.
The endpoint, which we will declare in a moment, consumes a JSON payload that encapsulates the translation request. We can map such JSON payload with a POJO.
Please note that the previous class uses the annotation
@ javax.validation.constraint.NotBlank to declare
target as required. Micronaut's validation is built in with the standard framework – JSR 380, also known as Bean Validation 2.0.
Hibernate Validator is a reference implementation of the validation API. You need an implementation of the validation API in the classpath. Thus, add the next snippet to build.gradle
Next, create a controller:
There are several things worth mentioning about the previous code listing:
- The Controller exposes a
/translateendpoint which could be invoked with a POST request.
- The value of
@Controllerannotations is a RFC-6570 URI template.
- Via constructor injection, Micronaut supplies a collaborator;
- Micronaut controllers consume and produce JSON by default.
@Bodyindicates that the method argument is bound from the HTTP body.
- To validate the incoming request, you need to annotate your controller with
@Validatedand the binding POJO with
In addition to constructor injection, as illustrated in the previous snippet, Micronaut supports the following types of dependency injection: Field injection, JavaBean property injection or Method parameter injection.
Integrate with Google Cloud Translation API
Now you want to add a dependency to Google Cloud Translate library:
Micronaut implements the JSR 330 specification for Java dependency injection, which provides a set of semantic annotations under the
javax.inject package (such as
@Singleton) to express relationships between classes within the DI container.
Create a singleton implementation of
TranslationService that uses the Google Cloud Translation API.
Here are a few things to mention about the above code:
@Singletonannotation is used to declare the class as a Singleton.
A method annotated with
@PostConstructwill be invoked once the object is constructed and fully injected.
Test the app Thanks to Micronaut’s fast startup time, it is easy to write functional tests with it.
Here’s how to write a functional test that verifies the behavior of the whole application.
Here are a few things to note about the above code:
- It’s easy to run the application from a test with the
- You can easily create an HTTP Client bean to consume the embedded server.
- Micronaut HTTP Client makes it easy to parse JSON into Java objects.
- Creating HTTP Requests is easy thanks to Micronaut’s fluid API.
- We verify the that server responds 400 (Bad request status code) when the validation of the incoming JSON payload fails.
Deploy to Google Cloud
There are multiple ways to deploy a Micronaut application to Google Cloud. You may choose to containerize your app or deploy it as a FAT jar. Check out these tutorials to learn more:
In addition to its cloud-native features, Micronaut also represents a significant step forward in microservice frameworks for the JVM, by supporting common Java framework features such as dependency injection (DI) and aspect-oriented programming (AOP), without compromising startup time, performance, and memory consumption.
Micronaut features a custom-built DI and AOP model that does not use reflection. Instead, an abstraction over the Java annotation processor tool (APT) API and Groovy abstract syntax tree (AST) lets developers build efficient applications without giving up features they know and love.
By moving the work of the DI container to the compilation phase, there is no longer a link between the size of the codebase and the time needed to start an application or the memory required to store reflection metadata. As a result, Micronaut applications written in Java typically start within a second.
This approach has opened doors to a variety of framework features that are more easily achieved with AOT compilation and that are unique to Micronaut.
Cloud-native development is here to stay, and Micronaut was built with this landscape in mind. Like the cloud-native architecture that motivated its creation, Micronaut’s flexibility and modularity allows developers to create systems that even its designers could not have foreseen.
To learn more about using Micronaut for your cloud-based projects, check out the Micronaut user guide. Learn how to use Micronaut in concert with Google Cloud Platform services, such as Cloud SQL, Kubernetes, and Google’s Instance Metadata Server in our upcoming webinar. There’s also a small but growing selection of step-by-step tutorials, including guides for all three of Micronaut's supported languages: Java, Groovy, and Kotlin.
Finally, the Micronaut community channel on Gitter is an excellent place to meet other developers who are already building applications with the framework and interact directly with the core development team.