Esercizio - Creare un'applicazione Quarkus

Completato

In questa unità si creerà un'applicazione Quarkus di base. Per avviare l'applicazione si userà Maven e per modificare il codice si userà un ambiente di sviluppo integrato (IDE) di propria scelta. Usare l'ambiente Bash configurato nei prerequisiti per eseguire il codice. Per avviare un database PostgreSQL locale, si usa Docker, in modo da poter eseguire e testare l'applicazione attività in locale.

Generare l'applicazione Quarkus usando Maven

Esistono diversi modi per generare una struttura di progetto Quarkus. È possibile usare l'interfaccia Web Quarkus, un plug-in IDE o il plug-in Quarkus Maven. Si userà ora il plug-in Maven per generare la struttura del progetto.

L'applicazione viene generata con diverse dipendenze:

  • La dipendenza resteasy per esporre un endpoint REST
  • La dipendenza jackson per serializzare e deserializzare JSON
  • La dipendenza hibernate per interagire con il database
  • La dipendenza postgresql per connettersi al database PostgreSQL
  • La dipendenza quarkus-container-image-docker per compilare un'immagine Docker

Non è necessario specificare le dipendenze di Azure, perché prima si esegue l'applicazione in locale e poi si distribuisce una versione in contenitori in App contenitore di Azure.

Nota

I comandi in questo modulo usano la sintassi Bash. Non incollarli invariati in PowerShell o nel Prompt dei comandi di Windows. In Windows eseguire i comandi Bash nella distribuzione WSL configurata. Usare un altro terminale Bash locale per le richieste HTTP durante l'esecuzione dell'applicazione.

Prima di generare il progetto, verificare che l'ambiente Bash usi Java 17 e Maven 3.9.9:

java -version
mvn --version

Il comando iniziale del generatore usa il Maven installato. Il wrapper Maven del progetto viene creato in seguito, quindi non sostituisce questo prerequisito. Selezionare le versioni necessarie Java e Maven prima di continuare.

Dalla directory in cui si vuole creare il progetto, generare l'applicazione 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"

Questo comando crea un nuovo progetto Quarkus in una directory denominata todo. Genera una struttura di directory Maven (src/main/java per il codice sorgente e src/test/java per i test). Crea alcune classi Java, alcuni test e alcuni Dockerfile. Genera anche un filepom.xml con tutte le dipendenze necessarie (Hibernate, RESTEasy, Jackson, PostgreSQL e 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

Tutte le dipendenze nel file pom.xml sono definite nella distinta base (BOM) di Quarkus io.quarkus.platform:quarkus-bom.

Scrivere il codice dell'applicazione

Rinominare quindi la classe MyEntity.java generata in Todo.java (che si trova nella stessa cartella del file TodoResource.java ). Sostituire il codice esistente con il codice Java seguente. Il codice usa Java Persistence API (pacchettojakarta.persistence.*) per archiviare e recuperare dati dal server PostgreSQL. Usa anche Hibernate ORM con Panache (che eredita da io.quarkus.hibernate.orm.panache.PanacheEntity) per semplificare il livello di persistenza.

Si userà un’entità (@Entity) per eseguire il mapping dell’oggettoTodo Java direttamente sulla tabella Todo di PostgreSQL. Quindi, l’endpoint REST TodoResource crea una nuova classe di entità Todo e la rende persistente. Questa classe è un modello di dominio con mapping alla tabella Todo. La tabella viene creata automaticamente da JPA.

Estendendo PanacheEntity si ottengono diversi metodi generici di creazione, lettura, aggiornamento ed eliminazione (CRUD, Create, Read, Update, Delete) per il tipo. In questo modo è possibile eseguire operazioni come il salvataggio e l'eliminazione di oggetti Todo in una sola riga di codice Java.

Aggiungere il codice seguente all’entità 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 +
                '}';
    }
}

Per gestire tale classe, aggiornare il TodoResource in modo che possa pubblicare interfacce REST per archiviare e recuperare i dati tramite HTTP. Aprire la classe TodoResource e sostituire il codice con il seguente:

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;
    }
}

Il metodo POST restituisce l'URI del nuovo elemento nell'intestazione Location . Il metodo GET dell'elemento serve tale URI e restituisce HTTP 404 se l'elemento non viene trovato. Null guard gestisce anche i valori di percorso che RESTEasy converte in un identificatore Null, senza passare null a Panache.

Eseguire l'applicazione

Quando si esegue l'applicazione in modalità di sviluppo, Docker deve essere in esecuzione. Questo perché Quarkus rileva che è necessario un database PostgreSQL (a causa della dipendenza quarkus-jdbc-postgresql PostgreSQL dichiarata nel file pom.xml ), scarica l'immagine Docker postgreSQL e avvia un contenitore con il database. Crea quindi automaticamente la tabella Todo nel database.

Nota

La piattaforma Quarkus 3.18.4 bloccata usa Testcontainers 1.20.4, il cui client Java Docker usa per impostazione predefinita l'API 1.32. Il motore Docker 29.x non accetta più quella versione dell'API per impostazione predefinita. Se si usa il motore 29.x, eseguire docker version e verificare che l'intervallo di API supportato dal server includa la versione 1.44. Solo se è così, eseguire il comando seguente in questa shell Bash prima di avviare Quarkus o i test:

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

Mantenere questa impostazione nella stessa shell per i comandi di test e di pacchettizzazione successivi. JDK_JAVA_OPTIONS fornisce la proprietà di sistema Java per i processi di avvio di Java senza modificare le versioni di Quarkus bloccate. L'impostazione DOCKER_API_VERSION da sola non configura questa versione del client Docker Java. Non forzare l'API 1.44 su un server il cui intervallo supportato la esclude; usare invece una configurazione Docker e client compatibile validata separatamente.

Assicurarsi che Docker sia in esecuzione e accessibile dall'ambiente Bash configurato. Nel terminale in cui è stato generato il progetto immettere nella directory todo e avviare l'applicazione:

cd todo
./mvnw quarkus:dev

Eseguire tutti i comandi Maven successivi da questa directory generata todo .

L'applicazione Quarkus verrà avviata e connessa al database. Verrà visualizzato l'output seguente:

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>

Per testare l'applicazione, è possibile usare cURL.

In un secondo terminale Bash locale creare un nuovo elemento to-do nel database con il comando seguente. Verrà visualizzato il log nella console di 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

Questo comando deve restituire l'elemento creato (con un identificatore):

{"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"}

Creare una seconda attività usando il comando cURL seguente:

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

Recuperare quindi i dati usando una nuova richiesta cURL:

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

Questo comando restituisce l'elenco degli elementi to-do, inclusi gli elementi che hai creato. Ogni elemento include createdAtanche ; nell'esempio seguente vengono omessi tali timestamp per brevità:

[ 
  {"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}
]

Testare l'applicazione

Dopo i controlli HTTP, tornare al terminale che esegue Quarkus e premere CTRL+C per arrestare la modalità di sviluppo. Mantenere aperta questa shell Bash e rimanere nella todo directory per il comando di test.

Per testare l'applicazione, è possibile usare la classe TodoResourceTest esistente. Deve testare l'endpoint REST. Per testare l'endpoint, usa RESTAssured. I test mantengono il controllo della raccolta, seguono il Location restituito da POST e controllano gli identificatori di elemento mancanti e non validi. Sostituire il codice nella classe TodoResourceTest con il codice seguente:

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);
    }
}

Il test di creazione legge l'identificatore generato dalla risposta POST e confronta i due identificatori come valori long. Il test dell'identificatore non valido disabilita la codifica automatica degli URL per %20 in modo che la richiesta contenga lo spazio deliberatamente codificato.

Quando si testa l'applicazione, Docker deve essere in esecuzione e accessibile dallo stesso ambiente Bash perché Quarkus avvia un servizio di sviluppo PostgreSQL separato per il test. Nel terminale in cui hai arrestato Quarkus, testare l'applicazione usando questo comando:

./mvnw clean test

Verrà visualizzato un output simile a questo:

[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] ------------------------------------------------------------------------

Mantenere aperta questa shell Bash per l'esercizio del database. Si imposta AZ_POSTGRES_JDBC_URL, AZ_POSTGRES_USERNAMEe AZ_POSTGRES_PASSWORD quindi si riavvia Quarkus da tale shell. Arrestare il processo di sviluppo remoto con CTRL+C prima delle successive fasi ./mvnw clean package e di distribuzione. Mantenere le variabili e non ripetere la configurazione del nome della risorsa e della password tra le unità.

Un processo eredita l'ambiente all'avvio. L'esportazione di variabili in un altro terminale non aggiorna un processo Quarkus già in esecuzione. Modificare application.properties e affidarsi al ricaricamento in tempo reale non sono sufficienti per il passaggio ad Azure; riavviare l'applicazione dalla shell in cui si impostano le variabili.