Ejercicio: Implementación de una aplicación de Java EE (Jakarta EE) en JBoss EAP en Azure App Service

Completado

En este ejercicio, implementará una aplicación de Java EE (Jakarta EE) en JBoss EAP en Azure App Service. Usará el complemento Maven para configurar el proyecto, compilar e implementar la aplicación, y configurar un origen de datos.

Configuración de la aplicación con el complemento Maven para Azure App Service

Vamos a configurar la aplicación mediante la ejecución del objetivo de configuración en el complemento de Maven para Azure App Service.

./mvnw com.microsoft.azure:azure-webapp-maven-plugin:2.9.0:config

Importante

Si ha cambiado la región del servidor MySQL, debe cambiar también el servidor de aplicaciones de Java EE a la misma región para minimizar los retrasos de latencia.
En el comando, seleccione Java 11 para la versión de Java y JBoss EAP 7 para la pila del entorno de ejecución.

Elemento de entrada Value
Available subscriptions: Your appropriate subsctioption
Choose a Web Container Web App [\<create\>]: 1: <create>
Define value for OS [Linux]: Linux
Define value for javaVersion [Java 17]: 2: Java 11
Define value for runtimeStack: 1: Jbosseap 7
Define value for pricingTier [P1v3]: P1v3
Confirm (Y/N) [Y]: Y

Después de ejecutar el comando, verá mensajes como el siguiente en el terminal:

$ ./mvnw com.microsoft.azure:azure-webapp-maven-plugin:2.9.0:config
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------< com.microsoft.azure.samples:jakartaee-app-on-jboss >---------
[INFO] Building jakartaee-app-on-jboss 1.0-SNAPSHOT
[INFO] --------------------------------[ war ]---------------------------------
[INFO] 
[INFO] --- azure-webapp-maven-plugin:2.5.0:config (default-cli) @ jakartaee-app-on-jboss ---
[WARNING] The POM for com.microsoft.azure.applicationinsights.v2015_05_01:azure-mgmt-insights:jar:1.0.0-beta is invalid, transitive dependencies (if any) will not be available, enable debug logging for more details
[INFO] Auth type: OAUTH2
Username: YOUR_EMAIL_ADDRESS@microsoft.com
Available subscriptions:
[INFO] Subscription: YOUR_SUBSCRIPTION(********-****-****-****-************)
[INFO] It may take a few minutes to load all Java Web Apps, please be patient.
Web Container Web Apps in subscription Microsoft Azure Internal Billing-CDA:
* 1: <create>
  2: jakartaee-app-on-jboss-yoshio (linux, jbosseap 7.2-java8)
Please choose a Web Container Web App [<create>]: 
Define value for OS [Linux]:
* 1: Linux
  2: Windows
  3: Docker
Enter your choice: 
Define value for javaVersion [Java 8]:
* 1: Java 8
  2: Java 11
Enter your choice: 
Define value for runtimeStack:
  1: Jbosseap 7.2
  2: Jbosseap 7
* 3: Tomcat 8.5
  4: Tomcat 9.0
Enter your choice: 1
Define value for pricingTier [P1v3]:
  1: P3v3
  2: P2v3
* 3: P1v3
Enter your choice: 
Please confirm webapp properties
Subscription Id : ********-****-****-****-************
AppName : jakartaee-app-on-jboss-1625038814881
ResourceGroup : jakartaee-app-on-jboss-1625038814881-rg
Region : westeurope
PricingTier : P1v3
OS : Linux
Java : Java 8
Web server stack: Jbosseap 7.2
Deploy to slot : false
Confirm (Y/N) [Y]: 
[INFO] Saving configuration to pom.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:43 min
[INFO] Finished at: 2021-06-30T16:40:47+09:00
[INFO] ------------------------------------------------------------------------
$ 

Una vez que se haya completado el comando, podrá ver que se agrega la entrada siguiente en el archivo pom.xml de Maven.

  <build>
    <finalName>ROOT</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.3.2</version>
      </plugin>
        <plugin>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-webapp-maven-plugin</artifactId>
            <version>2.9.0</version>
            <configuration>
                <schemaVersion>v2</schemaVersion>
                <resourceGroup>jakartaee-app-on-jboss-1625038814881-rg</resourceGroup>
                <appName>jakartaee-app-on-jboss-1625038814881</appName>
                <pricingTier>P1v3</pricingTier>
                <region>centralus</region>
                <runtime>
                    <os>Linux</os>
                    <javaVersion>Java 11</javaVersion>
                    <webContainer>Jbosseap 7</webContainer>
                </runtime>
                <deployment>
                    <resources>
                        <resource>
                            <directory>${project.basedir}/target</directory>
                            <includes>
                                <include>*.war</include>
                            </includes>
                        </resource>
                    </resources>
                </deployment>
            </configuration>
        </plugin>
    </plugins>
  </build>

Importante

Compruebe el elemento <region>. Si no es la misma ubicación de instalación que MySQL, cámbiela a la misma ubicación.

Después de agregar la configuración anterior para la implementación en Azure, agregue las siguientes entradas XML para implementar el archivo de inicio. El recurso <type>startup</type> implementa el script especificado como startup.sh (Linux) o startup.cmd (Windows) en /home/site/scripts/. Configuraremos el script de inicio en el paso siguiente.

              <!-- Please add following lines -->
              <resource>
                <type>startup</type>
                <directory>${project.basedir}/src/main/webapp/WEB-INF/</directory>
                <includes>
                  <include>createMySQLDataSource.sh</include>
                </includes>
              </resource>
              <!-- Please add following lines -->

Nota:

Puede especificar el siguiente recurso que se va a implementar en el código XML:

  • type=<war|jar|ear|lib|startup|static|zip>

    • type=war implementará el archivo war en /home/site/wwwroot/app.war si no se especifica el elemento path.
    • type=war&path=webapps/<appname>\ se comportará exactamente igual que wardeploy descomprimiendo la aplicación en /home/site/wwwroot/webapps/<appname>
    • type=jar implementará el archivo war en /home/site/wwwroot/app.jar. Se omitirá el parámetro path.
    • type=ear implementará el archivo war en /home/site/wwwroot/app.ear. Se omitirá el parámetro path.
    • type=lib implementará el archivo jar en /home/site/libs. Se debe especificar el parámetro path.
    • type=static implementará el script en /home/site/scripts. Se debe especificar el parámetro path.
    • type=startup implementará el script como startup.sh (Linux) o startup.cmd (Windows) en /home/site/scripts/. Se omitirá el parámetro path.
    • type=zip descomprimirá el archivo ZIP en /home/site/wwwroot. El parámetro path es opcional.

Ahora, compruebe los valores del nombre del grupo de recursos y el nombre de la aplicación en el archivo XML anterior. Anote estos nombres o, mejor, asígnelos a variables de entorno.

<resourceGroup>jakartaee-app-on-jboss-1625038814881-rg</resourceGroup>
<appName>jakartaee-app-on-jboss-1625038814881</appName>

Si usa Bash, configure las variables de entorno con el comando siguiente. Usará estos valores más adelante.

export RESOURCEGROUP_NAME=jakartaee-app-on-jboss-1625038814881-rg
export WEBAPP_NAME=jakartaee-app-on-jboss-1625038814881

Compilación de la aplicación de Java EE

Después de configurar las opciones de implementación de Azure App Service, compile y empaquete el código fuente.

./mvnw clean package

Aparece la salida siguiente en el terminal:

[INFO] Packaging webapp
[INFO] Assembling webapp [jakartaee-app-on-jboss] in [/private/tmp/mslearn-jakarta-ee-azure/target/ROOT]
[INFO] Processing war project
[INFO] Copying webapp resources [/private/tmp/mslearn-jakarta-ee-azure/src/main/webapp]
[INFO] Webapp assembled in [369 msecs]
[INFO] Building war: /private/tmp/mslearn-jakarta-ee-azure/target/ROOT.war
[INFO] WEB-INF/web.xml already added, skipping
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  7.656 s
[INFO] Finished at: 2023-03-04T12:35:43-05:00
[INFO] ------------------------------------------------------------------------

Implementación de la aplicación de Java EE en JBoss EAP en Azure App Service

Después de compilar y empaquetar el código, implemente la aplicación:

./mvnw azure-webapp:deploy

Aparece el mensaje siguiente en el terminal:

[INFO] Creating resource group jakartaee-app-on-jboss-1625038814881-rg in region westeurope...
[INFO] Successfully created resource group jakartaee-app-on-jboss-1625038814881-rg.
[INFO] Creating app service plan...
[INFO] Successfully created app service plan asp-jakartaee-app-on-jboss-1625038814881.
[INFO] Creating web app jakartaee-app-on-jboss-1625038814881...
[INFO] Successfully created Web App jakartaee-app-on-jboss-1625038814881.
[INFO] Trying to deploy artifact to jakartaee-app-on-jboss-1625038814881...
[INFO] Deploying (/private/tmp/mslearn-jakarta-ee-azure/target/ROOT.war)[war]  ...
[INFO] Successfully deployed the artifact to https://jakartaee-app-on-jboss-1625038814881.azurewebsites.net
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:11 min
[INFO] Finished at: 2023-03-04T12:38:39-05:00
[INFO] ------------------------------------------------------------------------

Anote la dirección URL de la aplicación implementada, específicamente la siguiente línea de la salida de Maven:

[INFO] Successfully deployed the artifact to https://jakartaee-app-on-jboss-1625038814881.azurewebsites.net

Configuración de una conexión de base de datos

La aplicación de ejemplo se conecta a la base de datos MySQL y muestra los datos.

En la configuración del proyecto de Maven en pom.xml, se ha especificado el controlador JDBC de MySQL de la siguiente manera:

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>${mysql-jdbc-driver}</version>
    </dependency>

Como resultado, JBoss EAP instala automáticamente el controlador JDBC en el paquete de implementación (ROOT.war). Puede hacer referencia al nombre del controlador JDBC de MySQL de la siguiente manera:

ROOT.war_com.mysql.cj.jdbc.Driver_8_0

Creación del objeto DataSource de MySQL en JBoss EAP

Para acceder a Azure Database for MySQL, debe configurar el objeto DataSource en JBoss EAP y especificar el nombre de JNDI en el código fuente.

Para crear un objeto DataSource de MySQL en JBoss EAP, se ha creado el archivo de script de shell siguiente. El archivo de script es createMySQLDataSource.sh, en el directorio /WEB-INF.

Nota:

En el script, se enlaza el objeto DataSource de MySQL mediante un comando de la CLI de JBoss. La cadena de conexión, el nombre de usuario y la contraseña usan las variables de entorno MYSQL_CONNECTION_URL, MYSQL_USER y MYSQL_PASSWORD.

El origen del archivo de script se muestra a continuación. Este archivo de script ya se ha cargado en App Service, pero aún no se ha configurado para invocarse.

#!/usr/bin/bash

# In order to use the variables in JBoss CLI scripts
# https://access.redhat.com/solutions/321513
#
sed -i -e "s|.*<resolve-parameter-values.*|<resolve-parameter-values>true</resolve-parameter-values>|g" /opt/eap/bin/jboss-cli.xml

/opt/eap/bin/jboss-cli.sh --connect <<EOF
data-source add --name=JPAWorldDataSourceDS \
--jndi-name=java:jboss/datasources/JPAWorldDataSource \
--connection-url=${MYSQL_CONNECTION_URL} \
--driver-name=ROOT.war_com.mysql.cj.jdbc.Driver_8_0 \
--user-name=${MYSQL_USER} \
--password=${MYSQL_PASSWORD} \
--min-pool-size=5 \
--max-pool-size=20 \
--blocking-timeout-wait-millis=5000 \
--enabled=true \
--driver-class=com.mysql.cj.jdbc.Driver \
--jta=true \
--use-java-context=true \
--valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker \
--exception-sorter-class-name=com.mysql.cj.jdbc.integration.jboss.ExtendedMysqlExceptionSorter
exit
EOF

Ahora, configure la instancia de App Service para que invoque al script de inicio:

az webapp config set --startup-file '/home/site/scripts/startup.sh' \
-n ${WEBAPP_NAME} \
-g ${RESOURCEGROUP_NAME}

Después de la ejecución del script, se le invocará cada vez que se reinicie el servidor de aplicaciones.

Nota:

Si el artefacto de implementación no es ROOT.war, también tendrá que cambiar el valor de --driver-name=YOUR_ARTIFACT.war_com.mysql.cj.jdbc.Driver_8_0.

Configuración de las variables de entorno para conectarse a MySQL

Después de configurar el script de inicio, configure App Service para que use determinadas variables de entorno:

az webapp config appsettings set \
  --resource-group ${RESOURCEGROUP_NAME} --name ${WEBAPP_NAME} \
  --settings \
  MYSQL_CONNECTION_URL='jdbc:mysql://mysqlserver-**********.mysql.database.azure.com:3306/world?useSSL=true&requireSSL=false' \
  MYSQL_PASSWORD='************' \
  MYSQL_USER=azureuser

Sugerencia

Los valores de MYSQL_CONNECTION_URL, MYSQL_USER y MYSQL_PASSWORD se han establecido en la unidad anterior.

Confirmación de la referencia de DataSource en el código

Para acceder a la base de datos MySQL desde la aplicación, debe configurar la referencia del origen de datos en el proyecto de aplicación. Se ha implementado el código de acceso a la base de datos mediante la API Persistence de Java (JPA).

La configuración de la referencia de DataSource se ha agregado en persistence.xml, que es el archivo de configuración de JPA.

Acceda al archivo siguiente:

├── src
│   ├── main
│   │   ├── resources
│   │   │   └── META-INF
│   │   │       └── persistence.xml

Compruebe si el nombre de DataSource coincide con el nombre usado en la configuración. El código ya ha creado el nombre de JNDI como java:jboss/datasources/JPAWorldDataSource:

  <persistence-unit name="JPAWorldDatasourcePU" transaction-type="JTA">
    <jta-data-source>java:jboss/datasources/JPAWorldDataSource</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="hibernate.generate_statistics" value="true" />
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
    </properties>
  </persistence-unit>
</persistence>

Después, puede acceder a la base de datos MySQL a la que se hace referencia en el nombre de unidad PersistenceContext como se indica a continuación:

@Transactional(REQUIRED)
@RequestScoped
public class CityService {

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

Acceso a la aplicación

En la aplicación de ejemplo, se han implementado tres puntos de conexión REST. Puede acceder a la aplicación y validar estos puntos de conexión mediante un explorador web o un comando curl.

Para acceder a la aplicación, debe hacer referencia a su dirección URL, que ha obtenido de una sección anterior:

[INFO] Successfully deployed the artifact to  
https://jakartaee-app-on-jboss-1606464084546.azurewebsites.net

Ejecute el comando siguiente para obtener toda la información de continentes en formato JSON.

Screenshot that shows area as the REST endpoint.

$ curl https://${WEBAPP_NAME}.azurewebsites.net/area
["North America","Asia","Africa","Europe","South America","Oceania","Antarctica"]$ 

Si especifica el continente en la dirección URL, puede obtener todos los países o regiones del continente especificado.

Screenshot that shows continent as the REST endpoint.

$ curl https://${WEBAPP_NAME}.azurewebsites.net/area/Asia | jq '.[] | { name: .name, code: .code }'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--   100 16189  100 16189    0     0  65278      0 --:--:-- --:--:-- --:--:-- 65542
{
  "name": "Afghanistan",
  "code": "AFG"
}
{
  "name": "United Arab Emirates",
  "code": "ARE"
}
{
  "name": "Armenia",
  "code": "ARM"
}
{
  "name": "Azerbaijan",
  "code": "AZE"
}
{
  "name": "Bangladesh",
  "code": "BGD"
}
....

Por último, si especifica un código de país o región después de /countries, puede obtener todas las ciudades, con una población superior a 1 millón dentro del país o región.

Screenshot that shows cities as the REST endpoint.

$ curl https://${WEBAPP_NAME}.azurewebsites.net/countries/JPN | jq '.[].name'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--   100   788  100   788    0     0   2671      0 --:--:-- --:--:-- --:--:--  2662
"Tokyo"
"Jokohama [Yokohama]"
"Osaka"
"Nagoya"
"Sapporo"
"Kioto"
"Kobe"
"Fukuoka"
"Kawasaki"
"Hiroshima"
"Kitakyushu"

Resumen del ejercicio

Ahora ha validado los puntos de conexión REST de la aplicación y ha probado que puede obtener datos de la base de datos MySQL.

En la unidad siguiente, examinará los registros del servidor.