Yet another java framework
Quarkus
For two years I have been using Quarkus to build microservices. Here are a few topics that I find worth mentioning for someone curious about the framework.
Native image
The main reason why Quarkus was considered is the small footprint of microservices, Quarkus is using GraalVM to build a native image. Another positive metric is the start time. Native image compiles Java code ahead of time into a standalone executable that contains everything needed to run the program in the standalone executable, as opposed to just in time compilation. This affects both the CI system and the Java code.
Building
Building a native image consumes a lot of resources. The ci pipeline we set up in the cloud was on regular small kubernetes nodes big enough to run our tests. It turns out that the native image can consume 80% of the available memory on the machine up to 14GB, and builds faster the more memory available for the build process. We ended up with scaling up large build nodes during our CI pipeline with a lot of memory available to accommodate the demanding build process. Building native image still takes around 5 minutes with our current configuration. To top off the qutie long build times another pain is to find out why problems related to native image compilation occur. I have found myself looking at my terminal for hours figuring out why my native image is not working.
Reflection
Reflection, or reflective access to object elements, is just partially supported in GraalVM. Partially because native-image must know the elements that are reflected upon ahead of time. In Quarkus this is done automatically on model objects used in Quarkus Rest Api components. For other processing of data such as Kafka topics the developer must manually provide the metadata information. This can be accomplished by using Quarkus annotation @RegisterForReflection of said class.
@RegisterForReflection
public class MyKafkaModel {
Integer id;
String value;
...
}
or if the class resides in an external package
@RegisterForReflection(targets = {
MyKafkaModel.class
})
public class ReflectionConfiguration {
}
Serialization
Serialization is another example that is not fully supported. This has been an interesting obstacle as it means Hibernate cannot use composite keys. There is work being done on this very topic in Quarkus and GraalVM and there are ways to register classes for serialization as well using -H:SerializationConfigurationFiles build args.
However, Java serialization has been a persistent source of security vulnerabilities. The Java architects have announced that the existing serialization mechanism will be replaced with a new mechanism avoiding these problems in the near future.
Quarkus Dependency Injection framework
Quarkus is using a CDI implementation called ArC. The API is build on top of javax.inject with the @Inject annotation.
Scope
One thing that we found interesting was the use of scope. When a user in our system calls a Rest API all code is executed inside a scope called RequestScope. This means that in our code we can associate a specific bean instance with the current http request being made.
The system we are building has other means of interaction, we are connecting IoT devices over tcp. When one of our devices connects we wanted to leverage the same scope isolation to be able to associate connection specific beans with the ongoing tcp connection. This can be accomplished with the help of Arc.container() API.
@Inject
Device requestScopedDevice;
private void setupClinetInRequestScope(SomeConnection connection) {
try {
Arc.container().requestContext().activate();
requestScopedDevice.init(connection);
} catch (Exception e) {
exceptionally(e);
} finally {
Arc.container().requestContext().terminate();
}
}
The injected Device will be a proxy client Device_ClientProxy, that will hold information about our IoT device during the started request.
Testing
One of the joys of using Quarkus has been the use of rest-easy together with Restassured for tests. Rest easy is an implementation of the JAX-RS api. An excerpt from the quarkus getting started guide will maybe get you to understand why I like it so much.
A simple endpoint can be defined
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
The test for it is as easy
@QuarkusTest
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("hello"));
}
}
Quarkus also bundles a way to run your tests but with the native built application, this is necessary in the CI steps. In the above example the class is annotated with @QuarkusTest that will start the application before JUnit runs the tests. With another annotation @NativeImageTest a native image with your code is started but the same test is executed. Native tests can mitigate problems with reflection or other native image related problem. For our above test class we can declare a test
@QuarkusTest
public class NativeGreetingResourceIT extends GreetingResourceTest {
}
that will look for a pre-built native runner and start that before JUnit is executed.
Further reading
Quarkus - supersonic subatomic java
Quarkus - creating your first application