|Quarkus 2.7.1 Released - Why Quarkus?|
|Written by Nikos Vaggalis|
|Thursday, 17 February 2022|
Quarkus, the Java framework for microservices initially released by RedHat in March 2019, has reached version 2.7.1. But before looking at what's new, let's take a look at what Quarkus does differently and how it contributes to the current popularity of Java.
Describing Quakus as a "Java framework for microservices" isn't 100% accurate. Despite the fact that Quarkus was created with microservices in mind, you can also build monoliths with it. Also, while Java is the primary language, you can write Quarkus programs in Kotlin and Scala as well.
Quarkus, together with Micronaut and Helidon, is part of a new league of open-source frameworks that have sprung up in the last few years in order to boost the usage of Java in the microservices world. Java supremacy in this arena was under threat due to the bloat that existing frameworks were burdened with - hundreds of class files required, resolving dependencies at runtime, use of reflection, large memory footprint, extended warm up time. Spring and Jakarta EE were the typical perpetrators.
Spring of course is a monumental piece of technology - a versatile framework that does a lot under the covers to boost developer productivity. That magic comes with a drawback, however. The magic Spring pulls is happening at runtime, analyzing your code and the annotations, injecting beans, etc thus using a lot of runtime loading as well as reflection under the hood. And with bloated runtime comes an impact on memory usage and of course startup time. The usual remedy for such performance loss is caching, but that comes with even more memory load.
But in the world of cloud native with Kubernetes and containers, performance is more important than ever since in an environment like that, instances/pds are killed and started automatically. As such rapid startup time is especially important also considering the memory footprint it bears on the auto-scaling properties of Kubernetes.
However, these frameworks contribute a twist to the story - they introduce AOT. As described in "Micronaut 3. 2 Released for More Performant Microservices"
The underlying technology was already there, on Android. The pointers of course is on the Ahead Of Time Compilation, a technique which determines what the application is going to need at compile time in order to avoid reflection completely thus keeping memory requirements down and performance up by doing more things at compile time than at runtime.
The trade-off is slower build time vs. faster runtime.
To take it a step further they also integrate with GraalVM to
compile Java bytecode into native self-contained executables, under which it can create native images which can reduce the start up time to milliseconds, up to 100x faster than when running on the JVM. Of course by giving up platform portability since the image made is machine specific, plus that the built time is extraordinary time-consuming.
A fast startup and low runtime is especially beneficial in a contemporary cloud or serverless production environment:
Usage of native images can have significant performance benefits in certain situations, when JVM is not required to run, load, and initialize classes, resulting in a fast startup and low footprint.
We already seen this in practice in "Compile Spring Applications To Native Images With Spring Native". Spring Native lets you compile Spring applications to native images using the GraalVM native-image compiler:
What's the advantage in that? Instant startup, instant peak performance, and reduced memory consumption, since the native Spring applications are deployed as a standalone executable, well docker image, without including a JVM installation.
What's the disadvantage? It's that the GraalVM build process, which tries to make the most optimal image possible, throws a lot of stuff out. This could be dependencies, resources or parts of your code.
These features are part of the commonalities between the frameworks. Next we ask, how does Quarkus differentiate?
Standards. With Quarkus you don't need to start learning from point zero. Quarkus is built on top of standards like CDI, JAX-RS;in fact it supports the Eclipse MicroProfile specifications:
Thus Jakarta EE users can immediately feel at home.
Extensions. Quarkus has first class support for technologies like Hibernate, Kafka, Camel, Vert. x, etc. Every release brings new ones and one way to keep up to date is to go to https://code.quarkus.io and check if the technology/framework that you are looking for has been integrated as a Quarkus extension. You can also look at the extensions branch of its github repo.
Reactivity. Based on Netty and Eclipse Vert.x, everything in Quarkus is non-blocking. In order to squeeze all the performance out of it you are required to go all "reactive" by requiring the extensions that it makes contact with to do the same. In Hibernate goes Reactive - What Does That Mean? we explored an example of that requirement:
NV: Why does it have to be non-blocking all the way down to the driver level, like R2DBC?
RH: So, the main difference is that the JDBC API (and implemented drivers) will block the I/O. Meaning it will block at the point the data in the relational database is being accessed, managed, etc. Wrapping in a Completable Future, in this case, will still be waiting for the JDBC thread to complete before delivering via future.
On the other hand, the R2DBC spec provides a rough outline to communicate with a relational DB in a way that doesn’t block disk I/O. The spec itself is really just a guideline, and the actual implementation is left up to you but it can also be used in combination with CompletableFutures.
The gist being the DB wire protocol implementation can be used in a more efficient way, eliminating threads, thereby decreasing memory usage and possibly Increasing throughout. Of course, it all really depends on the use case.
The magic sentence is:
"Wrapping in a Completable future, in this case, will still be waiting for the JDBC thread to complete before delivering via a future".
In that case we just had wrapped a synchronous call in an asynchronous wrapper, just faking asynchrony. The thread that makes the actual jdbc call will block until the query returns and won't be able to go back to the thread pool and serve another request.
That is the essence of non-blocking I/O, but reactivity means much more than that. Adding it to the mix we get the caller/subscriber controlling the volume of the data returned by the publisher/database, aka backpressure, streaming results, and of course the advantage of Reactive programming versus the imperative model.
As such Quarkus calls out to Hibernate reactive. But there's a twist. While inherently reactive you can also use it imperatively or mix both models.
Quarkus implements a proactor pattern that switches to worker thread when needed. Thanks to hints in your code (such as the @Blocking and @NonBlocking annotations), Quarkus extensions can decide when the application logic is blocking or non-blocking.
In scenarios where RESTEasy Reactive is used along with Hibernate, the @Blocking annotation should be placed on the endpoint methods that interact with Hibernate.
In scenarios where RESTEasy Reactive is used along with Hibernate Reactive, no @Blocking annotation is necessary on the endpoint methods that interact with Hibernate Reactive.
Thus, thanks to hints in your code (such as the `@Blocking` and `@NonBlocking` annotations), Quarkus extensions can decide when the application logic is blocking or non-blocking. For example, the RESTEasy Reactive extension uses the `@Blocking` annotation to determine if the method needs to be invoked using a worker thread, or if it can be invoked using the I/O thread.
Thus, while specific solutions such as non-blocking database drivers can be used, they’re not mandatory.
Compatibility. Quarkus has a Spring API compatibility layer which includes Spring DI, Spring Web, and Spring Data JPA. However they come with the following disclaimers:
While users are encouraged to use CDI annotations for injection, Quarkus provides a compatibility layer for Spring dependency injection in the form of the spring-di extension.
While users are encouraged to use JAX-RS annotation for defining REST endpoints, Quarkus provides a compatibility layer for Spring Web in the form of the spring-web extension.
While users are encouraged to use Hibernate ORM with Panache for Relational Database access, Quarkus provides a compatibility layer for Spring Data JPA repositories in the form of the spring-data-jpa extension.
Conventions. Configuring project properties a la Spring plus initializing your project like in Spring Initializr.
Hot reload in dev mode. Change our code and see those changes with a simple refresh. Just run mvn quarkus:dev and you are good to go.
Open source. Of course the project is open source and all related code is up online on its Github repo. Also very important in OSS is who's backing up the project in order to ensure its maintenance longevity. Fortunately Quarkus is fully backed by RedHat whose brainchild it is. Current its contributors number 586!
With the basics out of the way, let's now focus on what release 2.7.1 as of February 08 , a maintenance release for the 2.7 release train, has on offer. It contains bugfixes and documentation improvements:
The previous major release 2.7.0 included:
To sum it up, with state of the art microservice frameworks, GraalVm's native capabilities, the advancements that Java version 17 brought and with ongoing projects at large like Loom, Java is being rejuvenated, with its usage reaching new heights. Quarkus is certainly part of that success story.
Summer SALE Kindle 9.99 Paperback $10 off!!
or email your comment to: firstname.lastname@example.org
|Last Updated ( Thursday, 17 February 2022 )|