Avenue Code Snippets

How to Build Microservices with Helidon

Written by Pankaj Bharambe | 4/14/21 5:00 PM

This is your practical guide to Oracle's new open source framework, Project Helidon, a collection of Java libraries designed for creating microservices-based applications.

Originally named J4C (Java for Cloud), Helidon was designed to be simple and fast. It has two versions: Helidon SE and Helidon MP

Helidon SE features three core APIs to create a microservice -- a web server, configuration, and security -- for building microservices-based applications. An application server is not required. 

Helidon MP supports the MicroProfile 1.1 specification for building microservices-based applications.

In this snippet, we will mostly concentrate on Helidon SE.

Prerequisites

Helidon requires Java 11 (or newer) and Maven.

Web Server

Helidon has a small, functional style API that is reactive, simple, and transparent; an application server is not required.

A Helidon microservice is a Java SE application that starts a tinny HTTP server from the main method.

To start webServer API, we need to add the required Maven Dependency to the pom.xml

<dependency>

        <groupId>io.helidon.webserver</groupId>

        <artifactId>helidon-webserver</artifactId>

</dependency>

Inspired by NodeJS and other Java frameworks, Helidon's web server component is an asynchronous and reactive API that runs on top of Netty.

The WebServer interface provides basic server lifecycle and monitoring enhanced by configuration, routing, error handling, and building metrics and health endpoints.

See the startServer() method below: 

private static void startServer() {

Config config = Config.create();

     WebServer server = WebServer.builder(createRouting(config))

                             .addMediaSupport(JsonpSupport.create())

                 .build();

server.start()

         .thenAccept(ws -> {

  System.out.println("WEB server is up! http://localhost:" + ws.port() + "/greet");

                    ws.whenShutdown().thenRun(()-> System.out.println("WEB server is DOWN. Good bye!"));

          })

          .exceptionally(t -> {

System.err.println("Startup failed: " + t.getMessage());

                    t.printStackTrace(System.err);

                    return null;

          });

return server;

 }

private static Routing createRouting(Config config) {

        MetricsSupport metrics = MetricsSupport.create();

        GreetService greetService = new GreetService(config);

        HealthSupport health = HealthSupport.builder()

                .addLiveness(HealthChecks.healthChecks())                   .build();




        return Routing.builder()

                .register(health)                   

                .register(metrics)                  

                .register("/greet", greetService)

                .build();

    }

First, we need to build an instance of the Routing interface that serves as an HTTP request-response handler with routing rules: see createRouting() . In this example, in createRouting() we have registered Metric, Health, and context path /greet. We use GreetService to greet the user. The information below is from GreetService.java:

@Override

public void update(Routing.Rules rules) {

        rules

            .get("/", this::getDefaultMessageHandler)

            .get("/{name}", this::getMessageHandler)

            .put("/greeting", this::updateGreetingHandler);

}

After building the Routing Interface, we need to start the server. Helidon provides a functional interface for start and shutdown methods for servers. 

Let’s build and run our server application with Maven:

$ mvn package

The command above will help build the application, and you'll be able to see the logs below on your terminal.

[INFO] Scanning for projects...

[INFO]----------------------------------------------------------

[INFO] Detecting the operating system and CPU architecture

[INFO]----------------------------------------------------------[INFO] os.detected.name: osx

[INFO] os.detected.arch: x86_64

[INFO] os.detected.version: 10.15

[INFO] os.detected.version.major: 10

[INFO] os.detected.version.minor: 15

[INFO] os.detected.classifier: osx-x86_64

.............................................

After a Successful Build, run the server by invoking jar:

$ java -jar target/avenuecode-helidon-se.jar

When the server starts, you should see the following in your terminal window:

2020.12.29 12:12:02 INFO io.helidon.common.LogConfig Thread[main,5,main]: Logging at initialization configured using classpath: /logging.properties

2020.12.29 12:12:02 INFO io.helidon.common.HelidonFeatures Thread[features-thread,5,main]: Helidon SE 2.2.0 features: [Config, Health, Metrics, WebServer]

2020.12.29 12:12:07 INFO io.helidon.webserver.NettyWebServer Thread[nioEventLoopGroup-2-1,10,main]: Channel '@default' started: [id: 0x59794fb8, L:/0:0:0:0:0:0:0:0:65015]

WEB server is up! http://localhost:65015/greet

You can see the server is started on 65015, one of the available random ports.

Run the curl: 

$ curl -X GET http://localhost:65015/greet/AvenuCode 

You should see:

{"message":"Hello AvenuCode!"}
Configuration Component

The configuration component, Config, loads and processes configuration properties in key/value format. By default, configuration properties will be read from a defined application.properties or application.yaml file placed in the /src/main/resources directory.

The following code example demonstrates how to use Config and builds upon the previous example by reading an applications.yaml file to specify a port on which to start the web server.

app:

  greeting: "Hello"

server:

  port: 8080

  host: 0.0.0.0

The greeting subnode defines the server response that we hard-coded in the previous example. The port subnode defines port 8080 for the web server to use upon startup.

Let’s update our startServer() method to take advantage of the configuration we just defined:

private static void startServer() {

Config config = Config.create();

     WebServer server = WebServer.builder(createRouting(config))

.config(config.get("server"))                             .addMediaSupport(JsonpSupport.create())

                 .build();

server.start()

         .thenAccept(ws -> {

  System.out.println("WEB server is up! http://localhost:" + ws.port() + "/greet");

                    ws.whenShutdown().thenRun(()-> System.out.println("WEB server is DOWN. Good bye!"));

          })

          .exceptionally(t -> {

System.err.println("Startup failed: " + t.getMessage());

                    t.printStackTrace(System.err);

                    return null;

          });

return server;

 }

First, we need to build an instance of the Config interface by invoking its create() method to read our configuration file. The get(String key) method, provided by Config, returns a node, or a specific subnode, from the configuration file specified by key. For example, config.get("server") will return the content under the server node.

Next, we create an instance of ServerConfiguration, providing immutable web server information. This is possible when we invoke its create() method by passing in the statement, config.get("server"). 

The web server is created as in the previous example, except we use a config() method that accepts the config instance.

We can now build and run this version of our web server application using the same Maven and Java commands. Execute the same curl command:

$ curl -X GET http://localhost:8080/greet/AvenuCode 

You should see:

{"message":"Hello AvenuCode!"}
Security

The Security class provides support for authentication, authorization, and auditing. A number of security providers for use in Helidon applications have been implemented. There are three ways security may be built into a Helidon application: from a builder; by configuration; or a hybrid of the first two.

The following code example demonstrates how to build an instance of Security, use Config to obtain user authentication (with encrypted password) and display the server time.

// application.yaml

http-basic-auth:

 users:

   login: "avenuecode"

   password: "${CLEAR=somePassword}"

   roles: ["user","admin"]




Config config = Config.create();

Security security = Security.builder()

       .config(config)

       .addProvider(...)

       .build();

String user = config.get("http-basic-auth.users.login").asString();

String password = config.get("http-basic-auth.users.password").asString();

System.out.println("\n");

System.out.println("INFO: user = " + user);

System.out.println("INFO: password = " + password);
Health and Metrics

Helidon provides built-in support for health and metrics endpoints.

Health: 

curl -s -X GET http://localhost:8080/health

Metrics in Prometheus Format : 

curl -s -X GET http://localhost:8080/metrics

Metrics in JSON Format : 

curl -H 'Accept:application/json' -X GET http://localhost:8080/metrics 
Getting Started

Helidon provides quick start examples to demonstrate the differences between Helidon SE and Helidon MP.

The following Maven and Java commands will generate and package the Helidon SE example to create a REST service using Helidon's web server.

mvn -U archetype:generate -DinteractiveMode=false \

    -DarchetypeGroupId=io.helidon.archetypes \

    -DarchetypeArtifactId=helidon-quickstart-se \

    -DarchetypeVersion=2.2.0 \

    -DgroupId=com.avenuecode.snippet \

    -DartifactId=avenuecode-helidon-se \

    -Dpackage=com.avenuecode.snippet.quickstart




$ mvn package

$ java -jar target/quickstart-se.jar
Conclusion

In today's Snippet, we provided an introduction to Helidon, the new Java microservice framework that Oracle recently open sourced. Helidon is lightweight, simple, and fast, and more and more developers are excited about its capabilities. How have you used Helidon for your projects? Tell us in the comments below!

 

Quick Links for Reference

Project Helidon

Helidon Javadocs 

Maven 

Java 11