JEE9 - Aplicación minima jaxrs desde cero

2021/02/18

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.*):

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>

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

El código del proyecto se encuentra en el repositorio ~jumapico/jakarta-ee-experiments bajo el directorio jee9-minimal-jaxrs-from-scratch.