Objetivo
Se busca crear desde cero, sin partir de un arquetipo o proyecto existente, una aplicación minima Jakarta EE 9 empaquetada como un archivo war. Para construir la aplicación se utilizará maven y para ejecutarla el servidor de aplicaciones wildfly.
Instalando y configurando dependencias
git
Ya que se utilizará git para realizar el control de
versiones, si varios desarrolladores van a trabajar en el mismo proyecto
conviene preveer el ignorar los archivos generados por los IDE’s más comunes.
Para ello se utiliza el servicio gitignore.io, el cual
genera un archivo .gitignore
que excluya los archivos conocidos generados por
distintos IDE’s:
$ wget -O .gitignore 'https://www.toptal.com/developers/gitignore/api/vim,eclipse,vscode,netbeans,intellij+all'
maven wrapper
Aquí se sigue la receta para hacer yogur: partiendo de una versión de maven en el sistema se utiliza takari-maven-plugin para instalar maven-wrapper el cual instala la versión más reciente de maven (la que se encuentra en la página web de maven):
$ mvn -N io.takari:maven:0.7.7:wrapper -Dmaven=3.6.3
Nota: en el repositorio de maven-wrapper se puede leer que para la versión
3.7.0
de maven (ouch, casi!), el código será portado al maven-wrapper-plugin,
por lo que deberán actualizarse los pasos anteriores cuando salga la nueva
versión de maven.
Para ignorar los archivos descargados por el plugin que no correspondan a scripts para linux (tener en cuenta que si estos archivos no se encuentran serán descargados por el script):
$ rm mvnw.cmd
$ cat <<'END' >> .gitignore
### maven-wrapper ###
mvnw.cmd
.mvn/wrapper/MavenWrapperDownloader.java
.mvn/wrapper/maven-wrapper.jar
END
Por si se desea conocer más el porqué de la utilidad de utilizar un wrapper para maven ver Wrapping Maven distribution together with a project
wildfly preview
Como servidor de aplicaciones se utilizará [widlfly].
Ya que se desea utilizar Jakarta EE 9, según el anuncio WildFly 22 is released! se debe utilizar la versión Preview de wildfly, lo que implica que:
WildFly Preview should always be regarded as a tech-preview/beta distribution.
Por otro lado, se indica también que para los casos en que se utilicen librerías
de JEE 8 (que utilizan los paquetes javax.*
):
- Any libraries that were using EE 8 APIs were detected and instructions were incorporated in the feature pack telling Galleon to do byte code transformation of that library whenever it provisions a server using the feature pack.
Para descargar wildfly preview se utiliza:
$ wget 'https://download.jboss.org/wildfly/22.0.1.Final/wildfly-preview-22.0.1.Final.tar.gz'
$ tar xf wildfly-preview-22.0.1.Final.tar.gz
Nota 1: Si por error se utiliza la versión Jakarta EE Full & Web Distribution se obtiene un error del tipo:
18:26:35,809 WARN [org.jboss.modules.define] (Weld Thread Pool -- 3) Failed to define class uy.jumapico.demo.boundary.DemoApplication in Module "deployment.minimal-jaxrs-from-scratch-1.0-SNAPSHOT.war" from Service Module Loader: java.lang.NoClassDefFoundError: Failed to link uy/jumapico/demo/boundary/DemoApplication (Module "deployment.minimal-jaxrs-from-scratch-1.0-SNAPSHOT.war" from Service Module Loader): jakarta/ws/rs/core/Application
esto es, no se encuentra las clases jakarta.*
.
Nota 2: A la fecha (2021-02-18) los servidores de aplicaciones más populares tienen solamente soporte para Jakarta EE 9 en versiones beta.
Codificando demo
Creando archivo pom
Ya que se utiliza maven para generar el war de la aplicación debe crearse un archivo pom.xml:
① <?xml version="1.0" encoding="UTF-8"?>
② <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
③ <groupId>uy.jumapico</groupId>
<artifactId>minimal-jaxrs-from-scratch</artifactId>
<version>1.0-SNAPSHOT</version>
④ <packaging>war</packaging>
<properties>
⑤ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
⑥ <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
⑦ <failOnMissingWebXml>false</failOnMissingWebXml>
⑧ <reproducible-build-maven-plugin.version>0.13</reproducible-build-maven-plugin.version>
</properties>
<dependencies>
⑨ <dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>9.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
⑩ <plugin>
<groupId>io.github.zlika</groupId>
<artifactId>reproducible-build-maven-plugin</artifactId>
<version>${reproducible-build-maven-plugin.version}</version>
<executions>
<execution>
<id>strip-jar</id>
<phase>package</phase>
<goals>
<goal>strip-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
-
① Un pom es un archivo xml por lo cual se agrega la declaración XML
-
② Se comienza por un pom minimo con el tag
modelVersion
con valor4.0.0
que indica la versión del POM -
③ Se especifican las coordenadas del pom:
groupId
,artifactId
yversion
.Por defecto se utilza en la versión el sufijo
-SNAPSHOT
el cual indica que no es una versión estable -
④ Se indica que se desea empaquetar el código como war, ya que el empaquetamiento por defecto es
jar
-
⑥ Establecer la versión de java de los fuentes y que ejecutará la aplicación
-
⑦ No fallar si falta el archivo web.xml ya que es opcional en la especificación servlet si se utilizan anotaciones
-
⑧ Versión del Reproducible Build Maven Plugin para obtener un artefacto
.war
reproducible. -
⑨ Utilizar las coordenadas maven para la especificación Jakarta EE Platform 9
Agregado de clases java
Ya que se utiliza maven para generar el archivo war, hay que seguir las
convenciones del layout de directorios de maven,
esto es, crear el código bajo el directorio src/main/java
.
Como nombre del paquete se utilizará uy.jumapico.demo
.
Se seguirá el enfoque ECB - Entity Control Boundary
para la estructura del proyecto. Dado que es será una aplicación de ejemplo no
se utilizarán los paquetes entity
ni control
, ya que solo se definirá la
interfaz de la aplicación.
$ mkdir -p src/main/java/uy/jumapico/demo/boundary
Ya que se utilizará jaxrs se requiere publicar la aplicación, para lo cual se creará una subclase de Application anotada con la clase ApplicationPath la cual indicara el mapeo de las rutas que se agregarán al servidor:
$ cat > src/main/java/uy/jumapico/demo/boundary/DemoApplication.java <<'END'
package uy.jumapico.demo.boundary;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
@ApplicationPath("/")
public class DemoApplication extends Application {
}
END
Para crear los endpoints se agregará una resource class:
$ cat > src/main/java/uy/jumapico/demo/boundary/Hello.java <<'END'
package uy.jumapico.demo.boundary;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("hello")
public class Hello {
@GET
@Produces(MediaType.APPLICATION_JSON)
public String hello() {
return "Hello World!\n";
}
}
END
Aunque para este ejemplo no es necesario ya que no se utiliza inyección de
dependencias (CDI), se creará el
el archivo beans.xml
que debe encontrarse bajo el directorio src/main/webapp/WEB-INF
para que pueda extenderse el ejemplo para utilizar CDI en el futuro.
Un ejemplo del archivo beans.xml
se puede obtender de la especificación de
CDI o de
su esquema:
$ mkdir -p src/main/webapp/WEB-INF
$ cat > src/main/webapp/WEB-INF/beans.xml <<'END'
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_3_0.xsd"
version="3.0"
bean-discovery-mode="all">
</beans>
END
Cambio del context-root
Por defecto, al deployar el war este es expuesto bajo una ruta que tiene el
mismo nombre. En nuestro caso, ya que el artefacto es generado con un número de
versión este será deployado en la ruta /minimal-jaxrs-from-scratch-1.0-SNAPSHOT
.
Para cambiar la ruta en wildfly se puede utilizar el deployment descriptor
jboss-web.xml
el cual permite especificar opciones especificas de wildfly. En nuestro caso,
nos interesa cambiar el context-root
, cuya descripción se obtiene del archivo
jboss-web_14_1.xsd
que puede encontrarse en la web o bajo el directorio docs/schema
de wildfly:
The context-root element specifies the context root of a web application. This is normally specified at the ear level using the standard JEE application.xml descriptor, but it may be given here for standalone wars. This should not override the application.xml level specification.
Para cambiar el context root a /minimal-jaxrs-from-scratch
:
$ cat > src/main/webapp/WEB-INF/jboss-web.xml <<'END'
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web xmlns="http://www.jboss.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee https://www.jboss.org/schema/jbossas/jboss-web_14_1.xsd"
version="14.1">
<context-root>/minimal-jaxrs-from-scratch</context-root>
</jboss-web>
END
Nota: Como se puede apreciar, las referencias a la versión del archivo son
incorrectas en el esquema, ya que se utiliza
http://www.jboss.org/schema/jbossas/jboss-web_14_0.xsd
en lugar de https://www.jboss.org/schema/jbossas/jboss-web_14_1.xsd
y como
versión 14.0
en lugar de 14.1
. Cuidado con los ejemplos dentro de los
esquemas.
Deploy y prueba
Para generar el archivo war se utiliza el wrapper de maven:
$ ./mvnw clean package
Para realizar el deploy se copia el archivo
target/minimal-jaxrs-from-scratch-1.0-SNAPSHOT.war
al directorio standalone/deployments/
de wildfly-preview.
Por último, se comprueba que el war fué deployado correctamente y está funcionando:
$ curl -v 'http://localhost:8080/minimal-jaxrs-from-scratch/hello'
$ curl -v 'http://localhost:8080/minimal-jaxrs-from-scratch/hello'
* Trying ::1:8080...
* connect to ::1 port 8080 failed: Connection refused
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /minimal-jaxrs-from-scratch/hello HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Connection: keep-alive
< Content-Type: application/json
< Content-Length: 13
< Date: Fri, 19 Feb 2021 02:49:11 GMT
<
Hello World!
* Connection #0 to host localhost left intact
Conclusiones
- A Febrero de 2021 los servidores de aplicaciones para Jakarta EE 9 con licencia open source más populares -wildfly, payara, openliberty- no tienen versión estable, lo cual dificulta su adopción para nuevos desarrollos.
- El uso del Reproducible Build Maven Plugin aumenta considerablemente -duplica
para este caso- el tamaño del archivo
pom.xml
pero es útil ya que alcanza con realizar un checksum al archivo para poder comparar distintas versiones y confirmar que distintos desarrolladores pueden generar el mismo artefacto. - El uso de maven wrapper implica que se deben descargar archivos desde internet o configurar variables de entorno para utilizar un cache local.
- La aplicación al final no fué mínima, ya que decidí agregar los archivos
beans.xml
yjboss-web.xml
para que a futuro sea más fácil copiar el código como base para otras pruebas. - En si la aplicación no tiene nada de especial pero lleva un tiempo importante el encontrar la documentación que describe las distintas opciones que se deben utilizar en lugar de copiar y pegar código de ejemplo encontrado en internet.
El código del proyecto se encuentra en el repositorio ~jumapico/jakarta-ee-experiments bajo el directorio jee9-minimal-jaxrs-from-scratch.