Migración: JEE a Quarkus

Tabla de Contenidos

  1. Descripción General
    1. Dependencias
    2. Cambios en el Código
  2. Conclusión

La meta de este artículo es exponer los obstáculos y pasos que tuve que seguir para migrar un microservicio creado en Java EE hacia Quarkus, el cual es una de las nuevas implementaciones de MicroProfile.

Se preguntarán el ¿Por qué quise migrar un aplicativo productivo y completamente funcional a otro framework diferente?. La respuesta es sencilla: primero porque queria mejorar el tiempo de arranque o start up del microservicio (que además es quizás una de métricas más importantes, especialmente si hablamos de arquitecturas serverless) y en segundo lugar, porque me pareció extremadamente interesante.

Descripción General

La aplicación que utilice es un microservicio creado hace dos años usando los API de Java EE 7, de manera que puede ejecutarse en cualquier servidor de aplicaciones que implemente el estandar EE 7. Como es de suponer la aplicación hace uso de CDI, JPA, EJB y Rest. Además, se conecta a una base de datos Oracle 12c en donde están todas las tablas que este servicio requiere.

La posibilidad de migrarla a Quarkus (0.21.2) y poder crear imágenes nativas para containers con GraalVM resulta sumamente interesante, pero no es un proceso sencillo.

Los pasos que seguí son los siguientes:

Dependencias

El primer paso fue tomar el pom de la aplicación y reemplazar la dependencia de JavaEE 7

Por lo cual pasamos de esto:

<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-web-api</artifactId>
    <version>7.0</version>
    <scope>provided</scope>
</dependency>

a esto:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-bom</artifactId>
      <version>${quarkus.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
<dependencies>
  <dependency>
     <groupId>io.quarkus</groupId>
     <artifactId>quarkus-arc</artifactId>
  </dependency>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy</artifactId>
  </dependency>
</dependencies>

Veamos para que sirve cada una de ellas:

  • quarkus-arc: Es una de las principales extensiones y nos provee la funcionalidad de dependency injection.
  • quarkus-resteasy: Es la extensión que realiza la implementación de JAX-RS, entre otras funcionalidades.

Posteriormente, debemos reemplazar los plugins de maven por el de Quarkus, que sería así:

<plugin>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-maven-plugin</artifactId>
  <version>${quarkus.version}</version>
  <executions>
    <execution>
      <goals>
        <goal>build</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Si en este momento ejecutamos el comando mvn compile quarkus:dev vas a tener un gran número de errores. Por ejemplo:

 package javax.ejb does not exist
 package javax.persistence does not exist
 cannot find symbol
     [ERROR]   symbol:   class EntityManager
 package javax.json does not exist

No te preocupes, seguiremos ajustando nuestro código de manera incremental.

Cambios en el Código

Uno de los primeros erróres que podemos ver son dependencias no satisfechas y con el objetivo de simplificar la lectura he cambiado los nombres de clases y métodos. Una de ellas tiene que ver con las anotaciones de EJB, en donde teniamos algo similar a lo siguiente:

@Stateless
public class MiServicio {
    ...
    @TransactionalAttribute(TransactionAttributeType.REQUIRES_NEW)
    public boolean miMetodo(Strong param1) {
      .....
    }
}

Así que las reemplazo por lo siguiente:

@ApplicationScoped
public class MiServicio {
    ...
    @Transactional
    public boolean miMetodo(Strong param1) {
      .....
    }
}

Otro punto relevante tiene que ver con el uso de atributos privados. Si recibes un mensaje similar a este:

Found unrecommended usage of
private members (use package-private instead)
in application beans

Debes cambiar el modificar de acceso para que no sea private o si consideras que si debe ser privado, entonces debes emplear la estrategia de package private. En mi caso simplemente lo pase de private a protected. La razón de esto es que Quarkus procura evitar el uso de reflexión tanto como sea posible. Puedes tener más detalles al respecto aquí.

El siguiente aspecto tiene que ver con el archivo persistence.xml el cual -aunque tiene soporte en Quarkus- no es el mecanismo predilecto para definir los atributos de conexión a la base de datos. La forma más adecuada es proveer un archivo llamado application.properties en src\main\resources.

Un ejemplo de esto es lo siguiente:

quarkus.datasource.url=jdbc:oracle:thin:@servidor:puerto/pdbms
quarkus.datasource.driver=oracle.jdbc.OracleDriver
quarkus.hibernate-orm.dialect=org.hibernate.dialect.Oracle12cDialect
quarkus.datasource.username=USER
quarkus.datasource.min-size=3
quarkus.datasource.max-size=13
quarkus.hibernate-orm.database.generation=none
quarkus.hibernate-orm.log.sql=false

Deben notar que definimos el URL de la base de datos, el driver, el dialecto de la base datos, el usuario para la conexion y de manera adicional establecí el mínimo y máximo de conexiones a emplear.

Esto nos lleva además a agregar algunas dependencias adicionales, que serían la de hibernate-orm y el driver de JDBC de Oracle que necesitamos. Quarkus provee de manera directa drivers para diversas bases de datos de manera nativa, pero a la fecha no para Oracle.

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-hibernate-orm</artifactId>
</dependency>
<dependency>
  <groupId>com.oracle</groupId>
  <artifactId>ojdbc8</artifactId>
  <version>12.2.0.1</version>
</dependency>

Si compilamos nuestro código, ya no deberían aparecer muchas dependencias faltantes. En mi caso tenemos el siguiente error:

package javax.json does not exist.

Y esto es porque mi microservicio genera una respuesta de la siguiente manera:

 return Response.status(Response.Status.OK)
    .entity(Json.createObjectBuilder().add("estado", pers.getIndEstado()).build())
    .type(MediaType.APPLICATION_JSON)
    .build();

Así que debemos incorporar una dependencia extra de Quarkus que es:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>

Algo importante que mencionar es que debes eliminar el archivo persistence.xml si agregas la configuración del datasource en el application.properties. En caso contrario recibirías este error:

Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
	[error]: Build step io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#build threw an exception: io.quarkus.deployment.configuration.ConfigurationError: Hibernate ORM configuration present in persistence.xml and Quarkus config file at the same time

Por lo cual simplemente eliminó el persistence.xml y con eso ya logramos que nuestro código compile correctamente.

Luego, el siguiente cambio tiene que ver con la manera en que se injecta el EntityManager, así que debemos cambiar de:

@PersistenceContext(unitName="appPU")
EntityManager em;

a lo siguiente:

@Inject
EntityManager em;

Y si ejecutamos el comando mvn compile quarkus:dev observamos que nuestro aplicativo a funcionado sin problemas:

INFO  [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
INFO  [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 826ms
INFO  [io.quarkus] (main) Quarkus 0.21.2 started in 2.795s. Listening on: http://[::]:8080
INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, narayana-jta, resteasy, resteasy-jsonb]

Si ejecutamos un mvn package y luego ejecutamos el jar de nuevo tenemos lo siguiente:

INFO  [io.quarkus] (main) Quarkus 0.21.2 started in 2.126s. Listening on: http://[::]:8080
INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, narayana-jta, resteasy, resteasy-jsonb]

Al validar la invocación de nuestro servicio obtenemos una respuesta satisfactoria y da por confirmada que la migración fue exitosa. Y además, el uberjar de nuestro ejemplo mide 294K y contiene todo lo necesario para funcionar.

Ahora bien, como indique al inicio, este servicio requiere de una conexión a una base de datos Oracle y generar la imagen nativa es un trabajo que requiere de algunos ajustes adicionales que serán presentados en otro artículo. No obstante, es completamente funcional el uberjar que generamos previamente.

Conclusión


Espero que este artículo les brinde una perspectiva inicial de los pasos que deben realizar para migrar microservicios creados en Java EE y observar como lograr que funcionen correctamente en Quarkus. Los tiempos de arranca son consideramente más rápidos en comparación al tiempo que consume el servidor de capa media que estaba usando.

Written by

Gerardo Arroyo Arce

Arquitecto de Soluciones AWS certificado x10 con pasión por compartir conocimiento. Como miembro activo de AWS Community Builders, ex-AWS Ambassador y AWS User Group Leader, me dedico a construir puentes entre la tecnología y las personas. Desarrollador Java de corazón y consultor independiente, llevo la arquitectura cloud más allá de la teoría a través de conferencias internacionales y soluciones del mundo real. Mi curiosidad insaciable por aprender y compartir me mantiene en constante evolución junto a la comunidad tech.

Inicia la conversación