Ejercicio: Creación de una aplicación de Quarkus

Completados

En esta unidad, creará una aplicación básica de Quarkus. Puede usar Maven para arrancar la aplicación y un entorno de desarrollo integrado (IDE) de su elección para editar el código. Use el entorno de Bash configurado en los requisitos previos para ejecutar el código. Use Docker para iniciar una base de datos de PostgreSQL local para poder ejecutar y probar la aplicación localmente.

Generación de la aplicación Quarkus mediante Maven

Hay varias maneras de generar una estructura de proyecto de Quarkus. Puede usar la interfaz web de Quarkus, un complemento IDE o el complemento Quarkus Maven. Vamos a usar el complemento Maven para generar la estructura del proyecto.

La aplicación se genera con varias dependencias:

  • La dependencia resteasy para exponer un punto de conexión REST
  • Dependencia de jackson para serializar y deserializar JSON
  • La dependencia hibernate para interactuar con la base de datos
  • La dependencia de postgresql para conectarse a la base de datos PostgreSQL
  • Dependencia de quarkus-container-image-docker para construir una imagen de Docker

No es necesario especificar las dependencias de Azure porque ejecuta la aplicación localmente primero y, a continuación, implementa una versión en contenedor de ella en Azure Container Apps.

Nota:

Los comandos de este módulo usan la sintaxis de Bash. No los pegue sin modificarlos en PowerShell ni en Símbolo del sistema de Windows. En Windows, ejecute los comandos de Bash en la distribución de WSL configurada. Usa otro terminal de Bash local para las solicitudes HTTP mientras se ejecuta la aplicación.

Antes de generar el proyecto, compruebe que el entorno de Bash usa Java 17 y Maven 3.9.9:

java -version
mvn --version

El comando generador inicial usa tu Maven instalado. El wrapper de Maven del proyecto se crea después, por lo que no reemplaza este requisito previo. Seleccione las versiones necesarias de Java y Maven antes de continuar.

Desde el directorio en el que desea crear el proyecto, genere la aplicación to-do:

mvn -U io.quarkus:quarkus-maven-plugin:3.19.0:create \
    -DplatformVersion=3.18.4 \
    -DprojectGroupId=com.example.demo \
    -DprojectArtifactId=todo \
    -DclassName="com.example.demo.TodoResource" \
    -Dpath="/api/todos" \
    -DjavaVersion=17 \
    -Dextensions="resteasy-jackson, hibernate-orm-panache, jdbc-postgresql, io.quarkus:quarkus-container-image-docker"

Este comando crea un nuevo proyecto de Quarkus en un directorio denominado todo. Genera una estructura de directorios de Maven (src/main/java para el código fuente y src/test/java para las pruebas). Crea algunas clases de Java, algunas pruebas y algunos Dockerfiles. También genera un archivo pom.xml con todas las dependencias necesarias (Hibernate, RESTEasy, Jackson, PostgreSQL y Docker):

  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-orm-panache</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jackson</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-jdbc-postgresql</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-container-image-docker</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-arc</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-hibernate-orm</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

Nota:

Todas las dependencias del archivo pom.xml se definen en el BOM (lista de materiales) de Quarkus io.quarkus.platform:quarkus-bom.

Programar la aplicación

A continuación, cambie el nombre de la clase MyEntity.java generada a Todo.java (ubicado en la misma carpeta que el archivo TodoResource.java). Reemplace el código existente por el código Java siguiente. Usa la API de Persistencia de Java (jakarta.persistence.* paquete) para almacenar y recuperar datos del servidor PostgreSQL. También usa Hibernate ORM con Panache (heredando de io.quarkus.hibernate.orm.panache.PanacheEntity) para simplificar la capa de persistencia.

Use una entidad JPA (@Entity) para asignar el objeto Todo java directamente a la tabla de Todo postgreSQL. Después, el punto de conexión REST de TodoResource crea una nueva clase de entidad Todo y la persiste. Esta clase es un modelo de dominio que está mapeado en la tabla Todo. JPA crea automáticamente la tabla.

Al extender PanacheEntity se obtiene una serie de métodos genéricos de creación, lectura, actualización y eliminación (CRUD) para el tipo. Por lo tanto, puede hacer cosas como guardar y eliminar objetos Todo en una sola línea de código Java.

Agregue el código siguiente a la entidad Todo:

package com.example.demo;

import io.quarkus.hibernate.orm.panache.PanacheEntity;

import jakarta.persistence.Entity;
import java.time.Instant;

@Entity
public class Todo extends PanacheEntity {

    public String description;

    public String details;

    public boolean done;

    public Instant createdAt = Instant.now();

    @Override
    public String toString() {
        return "Todo{" +
                "id=" + id + '\'' +
                ", description='" + description + '\'' +
                ", details='" + details + '\'' +
                ", done=" + done +
                ", createdAt=" + createdAt +
                '}';
    }
}

Para administrar esa clase, actualice el TodoResource para que pueda publicar interfaces REST para almacenar y recuperar datos mediante HTTP. Abra la clase TodoResource y reemplace el código por lo siguiente:

package com.example.demo;

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriBuilder;
import jakarta.ws.rs.core.UriInfo;
import org.jboss.logging.Logger;

import java.util.List;

@Path("/api/todos")
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
public class TodoResource {

    @Inject
    Logger logger;

    @Inject
    UriInfo uriInfo;

    @POST
    @Transactional
    public Response createTodo(Todo todo) {
        logger.info("Creating todo: " + todo);
        Todo.persist(todo);
        UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder().path(todo.id.toString());
        return Response.created(uriBuilder.build()).entity(todo).build();
    }

    @GET
    public List<Todo> getTodos() {
        logger.info("Getting all todos");
        return Todo.listAll();
    }

    @GET
    @Path("/{id}")
    public Todo getTodo(@PathParam("id") Long id) {
        Todo todo = id == null ? null : Todo.findById(id);
        if (todo == null) {
            throw new NotFoundException();
        }
        return todo;
    }
}

El método POST devuelve el URI del nuevo elemento en el Location encabezado . El método GET del elemento responde a ese URI y devuelve HTTP 404 si no se encuentra el elemento. La protección null también controla los valores de ruta de acceso que RESTEasy convierte en un identificador NULL, sin pasar null a Panache.

Ejecución de la aplicación

Al ejecutar la aplicación en modo de desarrollo, Docker debe ejecutarse. Esto se debe a que Quarkus detecta que necesita una base de datos postgreSQL (debido a la dependencia de PostgreSQL quarkus-jdbc-postgresql declarada en el archivo pom.xml), descarga la imagen de Docker de PostgreSQL e inicia un contenedor con la base de datos. A continuación, crea automáticamente la tabla Todo en la base de datos.

Nota:

La versión fijada de la plataforma Quarkus 3.18.4 usa Testcontainers 1.20.4, cuyo cliente Java de Docker tiene como valor predeterminado la API 1.32. Docker Engine 29.x ya no acepta esa versión de API de forma predeterminada. Si usa Engine 29.x, ejecute docker version y confirme que el intervalo de API admitido del servidor incluye la versión 1.44. Solo si es así, ejecute el siguiente comando en este shell de Bash antes de iniciar Quarkus o las pruebas:

export JDK_JAVA_OPTIONS="${JDK_JAVA_OPTIONS:+$JDK_JAVA_OPTIONS }-Dapi.version=1.44"

Mantenga esta configuración en el mismo shell para los comandos de prueba y paquetes posteriores. JDK_JAVA_OPTIONS proporciona la propiedad del sistema Java para los procesos del iniciador de Java sin cambiar las versiones ancladas de Quarkus. Configurar DOCKER_API_VERSION por sí solo no configura esta versión del cliente de Docker de Java. No forzar la API 1.44 en un servidor cuyo intervalo admitido lo excluye; en su lugar, use una configuración de Docker y cliente compatibles validada por separado.

Asegúrese de que Docker se está ejecutando y accesible desde el entorno de Bash configurado. En el terminal donde generó el proyecto, entre en el directorio todo e inicie la aplicación:

cd todo
./mvnw quarkus:dev

Ejecute todos los comandos posteriores de Maven desde este directorio generado todo .

La aplicación Quarkus debe iniciarse y conectarse a la base de datos. Deberías ver la siguiente salida:

2025-02-28 08:38:33,418 INFO  [io.qua.dat.dep.dev.DevServicesDatasourceProcessor] (build-28) Dev Services for default datasource (postgresql) started - container ID is ce37977203b0
2025-02-28 08:38:33,421 INFO  [io.qua.hib.orm.dep.dev.HibernateOrmDevServicesProcessor] (build-6) Setting quarkus.hibernate-orm.database.generation=drop-and-create to initialize Dev Services managed database
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2025-02-28 08:38:35,278 INFO  [io.quarkus] (Quarkus Main Thread) todo 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.18.4) started in 5.367s. Listening on: http://localhost:8080

2025-02-28 08:38:35,280 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2025-02-28 08:38:35,280 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-postgresql, narayana-jta, resteasy, resteasy-jackson, smallrye-context-propagation, vertx]

--
Tests paused
Press [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>

Puede usar cURL para probar la aplicación.

En un segundo terminal local de Bash, cree un nuevo elemento to-do en la base de datos con el comando siguiente. Debería ver el registro en la consola de Quarkus:

curl --header "Content-Type: application/json" \
    --request POST \
    --data '{"description":"Take Quarkus MS Learn","details":"Take the MS Learn on deploying Quarkus to Azure Container Apps","done": "true"}' \
    http://127.0.0.1:8080/api/todos

Este comando debe devolver el elemento creado (con un identificador):

{"id":1,"description":"Take Quarkus MS Learn","details":"Take the MS Learn on deploying Quarkus to Azure Container Apps","done":true,"createdAt":"2025-02-26T07:27:30.093447Z"}

Cree una segunda tarea mediante el siguiente comando cURL:

curl --header "Content-Type: application/json" \
    --request POST \
    --data '{"description":"Take Azure Container Apps MS Learn","details":"Take the ACA Learn module","done": "false"}' \
    http://127.0.0.1:8080/api/todos

A continuación, recupere los datos mediante una nueva solicitud cURL:

curl http://127.0.0.1:8080/api/todos

Este comando devuelve la lista de elementos de tareas pendientes, incluidos los elementos que creó. Cada elemento también incluye createdAt; en el ejemplo siguiente se omiten esas marcas de tiempo para mayor brevedad:

[ 
  {"id":1,"description":"Take Quarkus MS Learn","details":"Take the MS Learn on deploying Quarkus to Azure Container Apps","done":true},
  {"id":2,"description":"Take Azure Container Apps MS Learn","details":"Take the ACA Learn module","done":false}
]

Prueba de la aplicación

Después de comprobar HTTP, vuelva al terminal que ejecuta Quarkus y presione Ctrl+C para detener el modo de desarrollo. Mantenga este shell de Bash abierto y permanezca en el directorio todo para el comando de prueba.

Para probar la aplicación, puede usar la clase TodoResourceTest existente. Necesita probar el punto de conexión REST. Para probar el punto de conexión, usa RESTAssured. Las pruebas conservan la comprobación de recopilación, siguen el Location devuelto por POST y comprueban los identificadores de elementos que faltan y no son válidos. Reemplace el código de la clase TodoResourceTest por el código siguiente:

package com.example.demo;

import io.quarkus.test.junit.QuarkusTest;
import io.restassured.response.Response;
import static io.restassured.RestAssured.given;
import static jakarta.ws.rs.core.HttpHeaders.CONTENT_TYPE;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;

@QuarkusTest
class TodoResourceTest {

    @Test
    void shouldGetAllTodos() {
        given()
                .when().get("/api/todos")
                .then()
                .statusCode(200);
    }

    @Test
    void shouldCreateATodo() {
        Todo todo = new Todo();
        todo.description = "Take Quarkus MS Learn";
        todo.details = "Take the MS Learn on deploying Quarkus to Azure Container Apps";
        todo.done = true;

        Response created = given().body(todo)
                .header(CONTENT_TYPE, APPLICATION_JSON)
                .when().post("/api/todos")
                .then()
                .statusCode(201)
                .extract().response();

        String location = created.getHeader("Location");
        assertNotNull(location);
        long createdId = created.jsonPath().getLong("id");

        Response retrieved = given()
                .when().get(location)
                .then()
                .statusCode(200)
                .extract().response();

        assertEquals(createdId, retrieved.jsonPath().getLong("id"));
        assertEquals(todo.description, retrieved.jsonPath().getString("description"));
    }

    @Test
    void shouldReturnNotFoundForMissingTodo() {
        given()
                .when().get("/api/todos/-1")
                .then().statusCode(404);
    }

    @Test
    void shouldRejectInvalidTodoIds() {
        given()
                .when().get("/api/todos/not-a-number")
                .then().statusCode(404);

        given().urlEncodingEnabled(false)
                .when().get("/api/todos/%20")
                .then().statusCode(404);
    }
}

La prueba create lee el identificador generado de la respuesta POST y compara ambos identificadores como valores long. La prueba de identificador no válido deshabilita la codificación automática de direcciones URL para %20 para que la solicitud contenga el espacio codificado deliberadamente.

Al probar la aplicación, Docker debe estar en ejecución y accesible desde el mismo entorno de Bash porque Quarkus inicia un servicio de desarrollo de PostgreSQL independiente para las pruebas. En el terminal donde detuvo Quarkus, pruebe la aplicación mediante este comando:

./mvnw clean test

Debería ver una salida parecida a esta:

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.demo.TodoResourceTest
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Mantenga abierto este shell de Bash para el ejercicio de base de datos. Establecerá AZ_POSTGRES_USERNAME, AZ_POSTGRES_PASSWORD y AZ_POSTGRES_JDBC_URL allí, luego reiniciará Quarkus desde ese shell. Detenga el proceso de desarrollo remoto con Ctrl+C antes de los pasos posteriores ./mvnw clean package y de implementación. Mantenga las variables y no repita la configuración de nombre de recurso y contraseña entre las unidades.

Un proceso hereda su entorno cuando se inicia. Exportar variables en otro terminal no actualiza un proceso de Quarkus ya en ejecución. Editar application.properties y depender de la recarga en vivo no es suficiente para el cambio a Azure; reinicie la aplicación desde el shell donde configuró las variables.