JVM Advent

The JVM Programming Advent Calendar

Fly like a rocket with Helidon

Helidon – in Greek a swallow (Χελιδόνι), is a small bird with long, sharp wings and a typical double tail. I am sure that each of us admires the flight and grace of this bird, and the way it lives in flocks is somewhat reminiscent of the world of microservices.

For us, programmers, Helidon is a set of libraries primarily for developing microservices, and is a representative of a family of, let’s call it, MicroProfile based development tools. Is a completely open source project, lies on Github, and is distributed under the Apache 2.0 license.

I am sure MicroProfile is already familiar to many developers as a set of specifications for building microservices. Having started its development in 2016 with only three specifications – CDI, JAX-RS and JSON-P, and even then, taken from Java EE, by the end of 2020 in version 3.3 these are already 12 specifications entirely focused on creating microservices:

There are several other specifications that are not part of the official version of MicroProfile, such as GraphQL support, Long Running Actions, Concurrency, Reactive Messaging, Event Data, and Reactive DB Access.

Helidon at this stage of its development does not strive to be a Full-Stack development tool like Spring Boot or Dropwizard, it is focused specifically on microservices. A typical microservice on Helidon (for JVM) will weigh no more than 20 Mb, which is several times less than, for example, Spring Boot. Well, the compiled ahead-of-time in Native Image is even less than 10 Mb.

Best of all, the API is completely declarative and completely portable within the MicroProfile standards.

As a wise man say – Talk is cheap, show me the code:

@Path("hello")
public class HelloWorld {

   @GET
   public String hello() {
      return "Hello World";
   }
}

Just a couple of standard annotations – and you can go to production!

Helidon 2.1.0

So, what can we do with Helidon

Helidon MP 2.1.0 Supported Specifications

Helidon supports everything that is specified in MicroProfile 3.3 plus CORS and gRPC (Server + Client)

How do you start coding? There are two options – download CLI, run it and answer a couple of questions (more on that later), or just add one dependency in an empty Maven project:

<dependency>

  <groupId>io.helidon.microprofile.bundles<\groupId>

  <artifactId>helidon-microprofile<\artifactId>

<\dependency>

 

Let’s make a small Hello World JAX-RS application!

For this we need the JAX-RS Resource Class:

@Path("/")
@RequestScoped
public class HelloWorldResource {
    @GET
    @Produces(MediaType.TEXTPLAIN)
    public String message() {
        return "Hello World";
    }
}

 

An example is a very simple “Hello World” service. It supports GET requests to create a greeting message and PUT requests to modify the greeting itself. The response is encoded with JSON. For instance:

curl -X GET http://localhost:8080/greet
{"message":"Hello World!"}
curl -X GET http://localhost:8080/greet/Joe
{"message":"Hello Joe!"}
curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Hola"}' http://localhost:8080/greet/greeting
curl -X GET http://localhost:8080/greet/Jose
{"message":"Hola Jose!"}

 

Moreover, right out of the box we have available such MicroProfile goodies as Health Check and Metrics:

You can learn about Health like this:

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

… about Metrics in Prometheus format:

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

As part of this artifact, a Docker file is also generated, which allows us to easily build a docker image:

docker build -t helidon-quickstart-mp .

… and run it immediately:

docker run --rm -p 8080:8080 helidon-quickstart-mp:latest

Everything is very cool, it works very quickly, and right out of the box!!!

By the way, Helidon is very modern!

This means that from the very beginning he lives in the Java environment at least version 11. This means that starting with JDK 9, we have access to jlink commands, which supports building a set of modules and their dependencies in a custom runtime image. The helidon-maven-plugin supports the simple creation of such a runtime image for your Helidon application, resulting in smaller size and better performance by eliminating unnecessary things.

There are two ways to create your own runtime image:

  • Locally, on a work machine
  • Using Docker

Locally, this is incredibly simple, you just need to use the jlink-image profile

This profile uses helidon-maven-plugin, which creates our custom image. By the way, during regeneration, the plugin prints a lot of useful information about how the image size has decreased.

The target/helidon-quickstart-mp directory is a standalone custom image of our application. It contains your application, its dependencies, and the JDK modules it depends on. You can start your application using the command:

./target/helidon-quickstart-mp/bin/start

But you can build the image directly to the docker, and also with a simple command:

docker build -t helidon-quickstart-mp-jlink -f Dockerfile.jlink

This command will make a complete build inside the Docker container. It will take some time on first launch because all Maven dependencies will be downloaded and cached at the Docker level. Subsequent builds will be much faster if you don’t change the pom.xml file. If the pom is changed, the dependencies will be reloaded.

And voila, we can run our application directly in Docker:

docker run --rm -p 8080: 8080 helidon-quickstart-mp-jlink: latest

You can test it with the quickstarter curls above.

Custom runtime images are ideal for use when you need all the JDK JVM performance in a fairly compact form.

But what if we need an uncompromisingly small image size and minimal application start time?

For this we have GraalVM Native Images!

Native image is an ahead-of-time compiled Java code that creates a stand-alone executable file. As a result, the application starts almost instantly and has less memory overhead at runtime compared to running on the JVM.

You can create a native file in two ways:

  • On local GraalVM
  • And, of course, on Docker!

On the local machine, everything is also outrageously simple! We have a ready-made native-image profile that uses the same helidon-maven-plugin. Let’s just build using it:

mvn package -Pnative-image

The build will take longer than usual as there is a huge amount of ahead-of-time compilation going on. In the end, we will get the native executable! Yes, yes, direct executable file, JVM is no longer needed! You can start it by running:

./target/helidon-quickstart-mp

… and yes, this thing is just extremely fast and small!

Well, as in the case of jlink, all manipulations on the build in the native image can be done directly in the docker! We just do:

docker build -t helidon-quickstart-mp-native -f Dockerfile.native

Just like with jlink, the first build will take longer, but all subsequent builds will use all the dependencies already downloaded.

To run the application, run:

docker run --rm -p 8080: 8080 helidon-quickstart-mp-native: latest

… and like last time our application starts up indecently quickly and takes up indecently little space!

Native-images are ideal for applications with high horizontal scalability requirements where the ability to quickly launch multiple instances is important.

However, native images have some limitations, and for long-running applications where launch and memory footprint are not as critical, the Java SE HotSpot VM may be more suitable.

A complete list of modules that support native image is given here: https://helidon.io/docs/v2/#/mp/aot/01introduction

It should be noted that native-image support is implemented on the full CDI version without any restrictions!

But that is not all!..

Tooling

Helidon 2 introduces a console utility that helps us build and also accelerates our development process.

CLI Helidon makes it easy to create a Helidon project by choosing from a set of archetypes. It also supports a developer loop that continuously compiles and restarts the application. You can just code, and the CLI will notice all the changes that you made, and you will immediately see them in action.

The CLI is distributed as a separate executable file (compiled using GraalVM) for ease of installation. It is currently available for download for Linux and Mac. Just download binary, install it in a location accessible from your PATH and you’re done.

Next, just type:

helidon init

… and answer a few questions … and you’re done!

Open the created project in your favorite IDE and start coding!

At the moment, using the tool, you can create a sample project with full MicroProfile support. You can also create a project based entirely on the low-level reactive APIs for maximum performance.

And for now, the third version of the application is an example using DB Client.

CLI Helidon functionality will be expanded very soon. But what is now is already 

Welcome to the Danger Zone!

As we have seen, Helidon is a great development tool that greatly simplifies the creation of Microservices. With a few annotations and a minimal amount of code, you can write a complete production application with little or almost no effort. But many others can do it as well. However, it should be noted that, being MicroProfile based, Helidon provides full portability of programs running on it within the framework of specifications. Standardization is Helidon’s main strong point and focus.

But all this magic is not given for free, it comes at the price of performance. Helidon’s architecture builds on CDI Extensions, which call a low-level API. All maintenance of this CDI infrastructure costs eforts.

What is this low-level API? This API is named Helidon SE.

Essentially Helidon SE is a very compact reactive toolkit built on top of Netty, and uses the latest Java SE innovations such as reactive streams, asynchronous and functional programming, and fluent-style APIs.

Helidon architecture

Essentially, in a way, soft and fluffy Helidon is a wrapper over the hardcore Helidon SE.

Helidon SE is a non-blocking microframework with a microscopic memory footprint, with a focus on asynchrony and performance. There is no “magic” in it – no dependency injection, practically no annotations, only the latest Java features (starting from version 11). Helidon SE offers a complete set of APIs for building reactive applications in a functional style.

Helidon SE currently supports the following specifications:

Helidon SE Supported Modules 

Experimental components are shown in green. In Helidon, experimental means that developers reserve the right to change the API in a minor release.

So how do you write on this beast? Quite easy, actually. The code is more verbose, more expressive and without any annotations.

Routing routing = Routing.builder()

    .get("/hello", (req, res) ->

    res.send("Hello World"))

    .build();

 

WebServer.create(routing)

   .start();

 

Particular attention should be paid to Helidon DB Client. It managed to implement fully non-blocking database access, regardless of whether the underlying JDBC driver is blocking or not. Out of the box, relational DBMSs such as Postgres, MySQL, MariaDB are supported (in general, everything for which there is a JDBC driver), as well as non-relational ones like MongoDB.

This is how the Select call in a transaction looks like:

dbClient.inTransaction(tx -> tx

    .createQuery("SELECT name FROM Pokemons WHERE id = :id")

    .addParam("id", 1)

    .execute()

);

 

And so looks an Update call for MongoDb:

dbClient.execute(exec -> exec

    .createUpdate("{\"collection\": \"pokemons\","

        + "\"value\":{$set:{\"name\":$name}},"

        + "\"query\":{id:$id}}")

    .addParam("id", 1)

    .addParam("name", "Pikachu")

    .execute()

);

 

At the same time, the configuration takes place centrally through Helidon Config. For instance:

db:

  source: "jdbc" 

  connection:

    url: "jdbc:mysql://127.0.0.1:3306/pokemon?useSSL=false" 

    username: "user"

    password: "password"

  statements: 

    ping: "DO 0" 

    select-all-pokemons: "SELECT id, name FROM Pokemons"

 

This makes the code partially portable, allows you to change the database without recompilation.

More information and examples in the official documentation https://helidon.io/docs/v2/#/se/dbclient/01introduction

Helidon SE also offers its reactive non-blocking Web Client. It supports all Helidon SE observability features, and is extensible by design. It is extremely easy to use:

WebClient client = WebClient.builder()

        .baseUri("http://localhost")

        .build();

Single response = client.get()

        .path("/endpoint")

        .request(String.class); 

 

It can be configured via Helidon Config. 

More info in excellent and detailed documentation https://helidon.io/docs/v2/#/se/webclient/01introduction

Reactive Streams / Messaging support in Helidon is also worth noting.

Helidon has its own set of Stream operators, independent from the outside the Helidon ecosystem. These operators can be used with java.util.concurrent.Flow based reactive streams. A threading operator chain can be easily built using io.helidon.common.reactive.Multi or io.helidon.common.reactive.Single for single value streams.

The Reactive Streams API is widely used and fully compatible with existing APIs and implementations (RxJava, Reactor, etc.).

There is already integration with Kafka and Oracle Streaming Service.

Helidon developers are especially proud of gRPC Server & Client. The implementations of both modules were essentially written from scratch, without any dependencies on Google libraries, and work fine in the Java9 + modular environment.

But, Helidon can be not only a server for gRPC services, but also be a consumer of them.

Helidon gRPC client provides a framework for building gRPC client applications. This framework provides a uniform way to access gRPC services that use either Protobuf or some custom serialization format.

gRPC and server and client are already stabilized and ready for use in production. More information: https://helidon.io/docs/v2/#/se/grpc/01_introduction

By the way, since performance was mentioned, it’s worth noting that Helidon is more likely to handle many operations better than others. Sometimes twice as good!

Operations per second. Bigger is better. The code for this benchmark is available here: https://github.com/danielkec/helidon-jmh

In general, Helidon SE is a secret weapon for fans of performance, creativity and other hardcore. 

Instead of hiding low-level APIs, the Helidon team made them open to everyone. It is clear that for 90% of cases “magic” will be quite enough, but often those 10% of “other hardcore cases” can be absolutely critical in terms of performance and reaction speed. You need to write a little more code, but it will work much faster!

Great .. but how to test this?

It’s actually very simple! Helidon SE is pure Java without magic. You can just test JUnit 🙂

But for Helidon MP, given that there is a lot of magic from CDI, there are special helpers. The test can be marked with the @HelidonTest annotation. This annotation will start the CDI container before calling any test method and stop it after calling the last method. This annotation also allows injection into the test class itself. 

What does the user get in the end of the day?

All of the above is great, but I’m sure many of us want to see numbers! Is Helidon really that small?

How much can our application weigh? In fact, there may be several options, depending on how we build it. Helidon offers several build profiles.

  1. Executable JAR: Hollow jar. All external dependencies are saved separately. This is especially useful in the context of using Docker and its layering;
  2. Jlink image: as already mentioned, Helidon lives on Java version 11 or higher, so it can easily use all the goodies, including Jlink! This creates a JRE + optimized application. As a result, we have a better start, a smaller size .. and no code restrictions!
  3. And of course, GraalVM native-image: with a certain number of known restrictions on the code and runtime operations, we get the fastest start time, minimum memory footprint and size!

If in numbers, then it looks like this:

And the disk footprint:

And of course, the start time !!!:

More than great results! Especially if you look at the Helidon SE application compiled in native-image!

And, as you know, time (and place) is money! Especially in the clouds!

To wrap it up.

Helidon continues to evolve quickly, but remains fast and graceful. It is small, fast and very agile! Helidon combines such qualities as standardization and portability, thanks to MicroProfile support, as well as the highest performance, thanks to reactive low-level APIs written from scratch and their implementations in the latest versions of Java, starting from version 11, with full use of all innovations.

Having access to low-level high-performance APIs allows you to create highly loaded reactive applications without wasting magic.

To get started, you just need to go to the site http://helidon.io and get acquainted with the very detailed documentation with a bunch of examples… For video lovers, there is a great channel at youtube, and for those who like to read – official blog 🙂

Better yet just download CLI 🙂

At the moment, we are developing implementations of both the main and non-main MicroProfile specifications, such as Long Running Actions and GraphQL. All this with the obligatory support for GraalVM native-image!

And while Helidon shows solid performance numbers, a lot of work continues to be done to make it even faster. Helidon is a complete production-ready solution built on the latest Java versions without compromise. Plus it is completely standard!

 

Next Post

Previous Post

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© 2024 JVM Advent | Powered by steinhauer.software Logosteinhauer.software

Theme by Anders Norén