17 maart 2023

At Qualogy,we stick to industry standards as much as we can in the projects we develop in-house. Some time ago we were using Jersey/Rest-Easy for projects, together with compatible dependencies for CDI, JWT etc. In doing so, even though we have the freedom to choose any technologies, we have to make sure these dependencies work together without conflicts. In this techblog, I will tell you more about Microprofile, how it relates to Thorntail, and how and why we finally changed to Quarkus. 

What is Microprofile?

The microprofile.io community (http://microprofile.io/) is dedicated to optimizing the Enterprise Java mission for microservice-based architectures. The MicroProfile specification consists of a collection of Enterprise Java APIs and technologies that together form a core baseline for microservices. MicroProfile was created in 2016 and, shortly after version 1.0 was released, it became part of the Eclipse Foundation to ensure that it would remain vendor-neutral.

Microprofile’s current version 5.0 contains the following specifications.

Microprofile’s major implementations are Open Liberty, Wildfly, Thorntail, Helidon, Websphere Liberty and Payara.

We started using Microprofile’s Thorntail implementation for two of our projects: the GPS SaaS solution for managing vehicle parking products at municipalities, and one developed for KNSB, the Skating Association of The Netherlands.

Thorntail was actively developing and implementing new versions of Microprofile.

In our visit to conferences like Devoxx, we noticed that Red Hat was developing Quarkus with the aim of creating developer-friendly, easy cloud-native applications.

What is Quarkus?

Quarkus was created to enable Java developers to create applications for a modern, cloud-native world. Quarkus is a Kubernetes-native Java framework tailored to GraalVM and HotSpot, crafted from best-of-breed Java libraries and standards. The goal is to make Java the leading platform in Kubernetes and serverless environments while offering developers a framework for addressing a wider range of distributed application architectures.
Quarkus is an Open Source project licensed under the Apache License version 2.0. Specifications and technologies underlying Quarkus are:
●    Eclipse MicroProfile
●    Eclipse Vert.x
●    Apache Camel
●    Hibernate
●    etc.

 

Interesting Features

●    Code changes are automatically reflected in your running application
●    Automatic provisioning and application wiring of supporting services such as databases, identity servers, and more.
●    Easy configurations
●    Get instant feedback on code changes as tests run in the background on impacted code.
●    Best of Breed Libraries and Standards
●    Designed to seamlessly combine the familiar imperative style code and the non-blocking, reactive style when developing applications.
 

End of Thorntail

Quarkus started being adopted by the development community, especially as major releases came with new features to make developer life easier. 

After the final release 2.7.0.Final Thorntail stopped further development.

We were monitoring the development of Quarkus to identify the right moment to switch from Thorntail. The good part here is that both are implementations of Microprofile, which made it easy to make the switch without major concerns. It proved again that keeping to standards always helps!

Migration steps explained

Migration Process

Get familiar with Quarkus / Proof of Concept

The first step was to get familiar with the Quarkus features and development process to properly make use of it. We went through the features and guides to get to know the available options.  

The main documents we referred to initially were: 

●    Configuration handling
●    Datasource configuration 
●    CDI and start up events handling 
●    Testing 
●    REST application creation 
●    Hibernate usage 
●    Transaction Handling 
●    JWT handling 
●    Mail service 

As the next step, we collected all the application’s requirements to connect that feature to the Quarkus features. The main technologies used in our application are:

●    JWT - creating and using tokens for authentication/authorization
●    PostgreSQL / MS SQL Server - We are using PostgreSQL in one application and MS SQL Server in another.
●    EclipseLink
●    Liquibase - Used to manage the database model and data
●    Maven
●    Azure - Application is deployed in Azure webapps, we are also using Azure storage from the application.

After this we started implementing the application’s features in the sample Quarkus application to get clarity on how it works and to get to know the needed dependencies. This way we gained confidence in the application’s migration and better understood the migration.

Quarkus’ sample application comes with docker configuration files, so use that as reference for your application docker image creation if needed.

We were using EclipseLink, so it was necessary to migrate to Hibernate as that's the one supported by Quarkus. Depending on the features used, a considerable amount of time needed for this migration. 
 

Migrating the application

We now had a better idea on how to handle the migration.

Migration of the application steps and time depends on the features of the application. Below you will see the changes we noticed based on the applications we migrated. 
 

Dependency Changes

Using Quarkus BOM
Thorntail and Quarkus provide a BOM dependency, so we started the change here to replace the thorntail BOM with Quarkus.

<quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
<quarkus.platform.version>”SelectThelatestQuarkusVersion”</quarkus.platform.version>

 

Quarkus-maven plugin usage
Next we changed the thorntail-maven plugin to the quarkus-maven plugin.


Thorntail to Quarkus dependencies
Now we removed all Thorntail dependencies and used <groupId>io.quarkus</groupId> dependencies of Quarkus.

    <dependency>
      <artifactId>microprofile</artifactId>
      <groupId>org.eclipse.microprofile </groupId>
      <scope>provided</scope>
      <type>pom</type>
    </dependency>
    <dependency>
      <artifactId>microprofile</artifactId>
      <groupId>io.thorntail</groupId>
    </dependency>
    <dependency>
      <artifactId>jaxrs-multipart</artifactId>
      <groupId>io.thorntail</groupId>
    </dependency>

      <artifactId>quarkus-arc</artifactId>
      <artifactId>quarkus-resteasy</artifactId>
      <artifactId>quarkus-smallrye-openapi</artifactId>
      <artifactId>quarkus-oidc</artifactId>
      <artifactId>quarkus-jdbc-postgresql</artifactId>
      <artifactId>quarkus-agroal</artifactId>
      <artifactId>quarkus-resteasy-jackson</artifactId>
      <artifactId>quarkus-rest-client</artifactId>
      <artifactId>quarkus-rest-client-jackson</artifactId>

 

Uber Jar in Quarkus
Since Quarkus applications can be packaged as an Uber Jar, you will want to consider using that. For more details go here: (https://quarkus.io/guides/maven-tooling#uber-jar-maven)

In case the application is packaged as war, remove the web.xml file.
Also notice that empty beans.xml is any longer needed in the Quarkus application.

Continue using Microprofile libraries
We can continue using other microprofile dependencies for fault tolerance, metrics etc. from eclipse.

<dependency>
 <groupId>org.eclipse.microprofile.fault-tolerance</groupId>
 <artifactId>microprofile-fault-tolerance-api</artifactId>
</dependency>

<dependency>
 <groupId>org.eclipse.microprofile.metrics

</groupId>
 <artifactId>microprofile-metrics-api</artifactId>
</dependency>

 

Handling missing dependencies
If you remove the thorntail dependencies, some transitive dependencies go missing, which can be added as needed. For example, we had to add commons-io and commons-httpclient explicitly after migration.

<dependency>
 <groupId>commons-io</groupId>
 <artifactId>commons-io</artifactId>
 <version>2.11.0</version>
</dependency>
<dependency>
 <groupId>commons-httpclient</groupId>
 <artifactId>commons-httpclient</artifactId>
 <version>3.1</version>
</dependency>

Quarkus Junit Usage
<dependency>
 <groupId>io.quarkus</groupId>
 <artifactId>quarkus-junit5</artifactId>
 <scope>test</scope>
</dependency>

 

Code Changes

Avoid private variables for Inject
Quarkus users are encouraged to not use private members in their beans. This involves injection fields, constructors and initializers, observer methods, producer methods and fields, disposers and interceptor methods. This is to avoid using reflection. You can use package-private modifiers.

@Inject private CategoryService service;  To   @Inject CategoryService service;

@Inject  @Claim("upn")  private ClaimValue<String> upn;

To 

@Inject  @Claim("upn")  ClaimValue<String> upn;

Explicitly add Scope and No Argument Constructor to classes to get injected
Add @ApplicationScoped or proper scope to your classes if missing.
Add  No argument constructor explicitly in case you have a parameterized constructor.

Changes in imports
import javax.annotation.Nullable;

To

import io.smallrye.common.constraint.Nullable;
 

Filter Classes to Configuration
We were using Filter classes for cors filter handling, which can be done in configuration.

@Provider
@WebFilter(filterName = "corsFilter", urlPatterns = "/api/*")
public class CorsFilter implements Filter {

 @Override
 public void doFilter(
     ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
     throws IOException, ServletException {
   HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
   HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;

   httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
   httpServletResponse.setHeader(
       "Access-Control-Allow-Methods", "OPTIONS, HEAD, GET, PATCH, POST, PUT, DELETE");
   httpServletResponse.setHeader(
       "Access-Control-Allow-Headers",
       "Accept, Authorization, Cache-Control, Content-Type, Bearer, xsrfheadername, xsrfcookiename");

   httpServletResponse.setHeader("Access-Control-Expose-Headers", "token, auth-2fa-enabled");

   if (httpServletRequest.getMethod()

.equals("OPTIONS")) {
     httpServletResponse.setStatus

(HttpServletResponse.SC_OK);
     return;
   }

   filterChain.doFilter(servletRequest, servletResponse);
 }
}


Instead of this class add in application.properties file,

quarkus.http.cors=true
quarkus.http.cors.methods

=GET,PUT,POST,PATCH,DELETE,OPTIONS
quarkus.http.cors.exposed-headers=token,auth-2fa-enabled

Complete documentation on this is here.
 

Configuration using application.properties
META-INF/microprofil-config.properties to resources/application.properties

More details here.

Generating a JWT token is easier
You will find examples with documentation here.
 

Transaction handling
All the boilerplate code to handle transaction and rolling back can be handled with just a @Transactional annotation on your service method, which needs transactional support.

try {
 resourceService.startTransaction();
   …
   resourceService.persist(entity);

 resourceService.commitTransaction();
} catch (Exception ex) {
 log.error(ex.getMessage());
 if (resourceService.getEntityManager()

.getTransaction().isActive())
   resourceService.rollback();
 throw ex;
}

Eclipselink to HIBERNATE migration
EclipseLink and Hibernate vary in certain areas in how some of the features are used. Even though we didn't see any problems using EclipseLink, as Quarkus supports only Hibernate now, we are forced to make this switch. 

Dependency Change
<dependency>
 <artifactId>eclipselink</artifactId>
  <groupId>org.eclipse.persistence

</groupId>
</dependency>

to

<dependency>
 <groupId>io.quarkus</groupId>
 <artifactId>quarkus-hibernate-orm</artifactId>
</dependency>


NamedQuery - Alias of columns is stricter in Hibernate
We modified all queries with alias to avoid conflicts and explicitly with “as” keyword
+ " r.id as raceId, r.competitor.id as competitorId"
+ " from competition_races r,"
 

Eager vs Lazy loading of list
You might have to recheck any list loaded with fetchType.Eager and remove the Eager, otherwise you might get performance issues and in Hibernate exceptions depend on the application.

Nullable Columns should use Object
@Column(name = "start_mode") private int startMode;

 To 

@Column(name = "start_mode") private Integer startMode;
 

Array Support in Hibernate is not same as EclipseLink
Passing arrays to native Query or stored procedure Query was handled in EclipseLink by obtaining an Accessor from ServerSession of Connection and converting the list of data to Array.

Accessor accessor = getAccessor(); // from serverSession
Array countryIdArray =
   accessor
       .getConnection()
       .createArrayOf("integer", countryList != null   ? countryList.toArray()   : new Object[] {});
query.setParameter(2, countryIdArray);

Instead of this, it is possible in Hibernate to set the list to parameter without using Accessor or conversion to Array.

query.setParameter(2, countryList);

In EclipseLink it was possible to aggregate values in array like " array_agg(cr.id) "
This needs to be handled differently in Hibernate. We chose to convert the array to string to handle this.

" array_to_string(array_agg(cr.id), ',') race_ids"
 

Entity refresh after transaction
It’s not posssible to refresh entities created inside a transaction after the @transactional block in hibernate. Either refresh inside - the @transactional method - or outside, fetch the entity again using id and refresh it.


Query Hints from Eclipse to Hibernate
query.setHint(org.eclipse.persistence.config.QueryHints.READ_ONLY, HintValues.TRUE);

To 

query.setHint(org.hibernate.annotations.QueryHints.READ_ONLY, true);
 

Conclusion

We can migrate from Thorntail to Quarkus easily as both are implementations of MicroProfile.
Instead of just migration, we also checked better options available in Quarkus and started using it. 

Every day, we enjoy the benefit of automatic change detection and deployment on running applications. Integration with other technologies is possible with just a few configurations. Recently we started using multi-tenancy, which works very smoothly. More details of multi-tenancy can be found here

Quarkus is developed actively with good documentation, which speeds up further developments. Apart from in-house projects, we also see that our consultants are now working on Quarkus projects for their clients, which demonstrates the wide acceptance of Quarkus in the industry. 

Kun je de vacature die je zoekt niet vinden?

Maak een Jobalert aan en ontvang een melding per mail
wanneer er nieuwe vacatures zijn!