Yet another java framework

Squeeds Julkalender | 2021-12-02 | Max Gabrielsson
As a Java developer I have come across several frameworks that all work in similar but different ways. As most in the field I have been using Spring framework and was first introduced to Spring in 2007 solving university labs and incorporating something new and shiny. When it comes to tech I have always been drawn to exploring new buzzwords. In the last couple of years there have been some new frameworks popping up for us Java developers, one of which has crossed my path.

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

GraalVM

RESTEasy