Palo Alto Networks Completes Acquisition of Demisto
March 28, 2019ASUS ShadowHammer Episode – A Custom Made Supply Chain Attack
March 29, 2019Application Development
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.
Enter Micronaut. Last year, a team of developers at OCI released this open source JVM framework that was designed to simplify developing and deploying microservices and serverless applications.
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
@Client
annotation, 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.
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:
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk version
SDKMAN 5.6.4+305
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 1.0.3
):
sdk install micronaut 1.0.3
Confirm that you have installed Micronaut by running _mn -v_:
mn -v
| Micronaut Version: 1.0.3
| JVM Version: 1.8.0_171
Step 2: Create the project
The 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 -lang kotlin
).
The `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:
mn create-app example.micronaut.translator -features spock
| Application created at /Users/dev/translator
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 run
task.
./gradlew run
Starting a Gradle Daemon (subsequent builds will be faster)
Task :run
03:00:04.807 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 1109ms. Server Running: http://localhost:8080
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:
`src/main/java/example/micronaut/TranslationService.java`
```java
package example.micronaut.micronaut;
public interface TranslationService {
TranslationResult translate(String text, String sourceLanguage, String targetLanguage);
}
```
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.
`src/main/java/example/micronaut/TranslationResult.java`
```java
package example.micronaut.micronaut;
public class TranslationResult {
private String text;
private String source;
private String target;
private String translatedText;
public TranslationResult() {}
public TranslationResult(String text, String source, String target, String translatedText) {
this.text = text;
this.source = source;
this.target = target;
this.translatedText = translatedText;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getTranslatedText() {
return translatedText;
}
public void setTranslatedText(String translatedText) {
this.translatedText = translatedText;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TranslationResult that = (TranslationResult) o;
if (text != null ? !text.equals(that.text) : that.text != null) return false;
if (source != null ? !source.equals(that.source) : that.source != null) return false;
if (target != null ? !target.equals(that.target) : that.target != null) return false;
return translatedText != null ? translatedText.equals(that.translatedText) : that.translatedText == null;
}
@Override
public int hashCode() {
int result = text != null ? text.hashCode() : 0;
result = 31 * result + (source != null ? source.hashCode() : 0);
result = 31 * result + (target != null ? target.hashCode() : 0);
result = 31 * result + (translatedText != null ? translatedText.hashCode() : 0);
return 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.
src/main/java/example/micronaut/TranslationCommand.java
```java
package example.micronaut.micronaut;
import javax.validation.constraints.NotBlank;
public class TranslationCommand {
@NotBlank
private String text;
@NotBlank
private String source;
@NotBlank
private String target;
public TranslationCommand() {
}
public TranslationCommand(String text, String source, String target) {
this.text = text;
this.source = source;
this.target = target;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TranslationCommand that = (TranslationCommand) o;
if (text != null ? !text.equals(that.text) : that.text != null) return false;
if (source != null ? !source.equals(that.source) : that.source != null) return false;
return target != null ? target.equals(that.target) : that.target == null;
}
@Override
public int hashCode() {
int result = text != null ? text.hashCode() : 0;
result = 31 * result + (source != null ? source.hashCode() : 0);
result = 31 * result + (target != null ? target.hashCode() : 0);
return result;
}
}
```
Please note that the previous class uses the annotation @ javax.validation.constraint.NotBlank
to declare text
, source
, and 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_
_build.gradle_
compile "io.micronaut.configuration:micronaut-hibernatevalidator"
Next, create a controller:
src/ main/java/example/micronaut/TranslationController.java
```java
package example.micronaut.micronaut;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.validation.Validated;
import javax.validation.Valid;
@Validated
@Controller("/translate")
public class TranslationController {
private final TranslationService translationService;
public TranslationController(TranslationService translationService) {
this.translationService = translationService;
}
@Post("/{?translationCommand*}")
public TranslationResult translate(@Body @Valid TranslationCommand translationCommand) {
return translationService.translate(translationCommand.getText(),
translationCommand.getSource(),
translationCommand.getTarget());
}
}
```
There are several things worth mentioning about the previous code listing:
- The Controller exposes a _/translate_ endpoint which could be invoked with a POST request.
- The value of _@Post_ and _@Controller_ annotations is a RFC-6570 URI template.
- Via constructor injection, Micronaut supplies a collaborator; _TranslatorService_.
- Micronaut controllers consume and produce JSON by default.
- _@Body_ indicates that the method argument is bound from the HTTP body.
- To validate the incoming request, you need to annotate your controller with _@Validated_ and the binding POJO with _@Valid_.
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:
_build.gradle_
dependencies {
...
compile 'com.google.cloud:google-cloud-translate:1.55.0'
...
}
Micronaut implements the JSR 330 specification for Java dependency injection, which provides a set of semantic annotations under the javax.inject
package (such as @Inject
and @Singleton
) to express relationships between classes within the DI container.
Create a singleton implementation of _TranslationService_ that uses the Google Cloud Translation API.
_src/main/java/example/micronaut/GoogleTranslationService.java_
```java
package example.micronaut.micronaut;
import com.google.cloud.translate.Translate;
import com.google.cloud.translate.TranslateOptions;
import com.google.cloud.translate.Translation;
import javax.annotation.PostConstruct;
import javax.inject.Singleton;
@Singleton
public class GoogleTranslationService implements TranslationService {
private Translate translate;
@PostConstruct
private void initialize() {
translate = TranslateOptions.getDefaultInstance().getService();
}
@Override
public TranslationResult translate(String text, String sourceLanguage, String targetLanguage) {
Translation translation =
translate.translate(
text,
Translate.TranslateOption.sourceLanguage(sourceLanguage),
Translate.TranslateOption.targetLanguage(targetLanguage));
return new TranslationResult(text,
translation.getSourceLanguage(),
targetLanguage,
translation.getTranslatedText());
}
}
```
Here are a few things to mention about the above code:
-
_@Singleton_ annotation is used to declare the class as a Singleton.
-
A method annotated with _@PostConstruct_ will 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.
```
package example.micronaut
import example.micronaut.micronaut.TranslationCommand
import example.micronaut.micronaut.TranslationResult
import io.micronaut.context.ApplicationContext
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Requires
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class TranslationControllerSpec extends Specification {
@Shared
@AutoCleanup
EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)
@Shared
@AutoCleanup
RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL())
@Requires({env['GOOGLE_APPLICATION_CREDENTIALS']})
def "verify integration with google cloud translation api"() {
when:
TranslationCommand translationCommand = new TranslationCommand("Hello World",
"en",
"es")
HttpRequest request = HttpRequest.POST("/translate", translationCommand)
TranslationResult result = client.toBlocking().retrieve(request, TranslationResult)
then:
result
and:
result == new TranslationResult(translationCommand.text,
translationCommand.source,
translationCommand.target,
"Hola Mundo")
}
@Unroll
def "body #attribute is required"(String text, String source, String target, String attribute) {
when:
TranslationCommand cmd = new TranslationCommand(text, source, target)
HttpRequest request = HttpRequest.POST("/translate", cmd)
client.toBlocking().retrieve(request, TranslationResult)
then:
HttpClientResponseException e = thrown()
e.response.status == HttpStatus.BAD_REQUEST
where:
text | source | target
null | "en" | "es"
"Hello World" | null | "es"
"Hello World" | "en" | null
attribute = text == null ? 'text' : source == null ? 'source' : target == null ? 'target' : 'undefined'
}
}
```
Here are a few things to note about the above code:
- It’s easy to run the application from a test with the _EmbeddedServer_ interface.
- 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:
Deploy a Micronaut application to Google Cloud App Engine
Deploy a Micronaut application containerized with Jib to Google Kubernetes Engine
Micronaut performance
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.
Conclusion
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.