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 un terminale di propria scelta 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 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.

Al prompt dei comandi, generare l'attività di applicazione:

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

Questo comando crea un nuovo progetto Quarkus. 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. È stato anche generato un file pom.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 (bill of materials) di Quarkus io.quarkus.platform:quarkus-bom.

Codice dell'applicazione

Successivamente, rinominare la classe MyEntity.java generata in Todo.java (che si trova nella stessa cartella del fileTodoResource.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.POST;
import jakarta.ws.rs.Path;
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();
    }
}

Eseguire l'applicazione

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

Assicurarsi che Docker sia in esecuzione in locale nel computer ed eseguire l'applicazione dell’attività usando il comando seguente:

cd todo
./mvnw quarkus:dev    # On Mac or Linux
mvnw.cmd quarkus:dev  # On Windows

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

[io.qua.dat.dep.dev.DevServicesDatasourceProcessor] Dev Services for the default datasource (postgresql) started.
[io.qua.hib.orm.dep.HibernateOrmProcessor] Setting quarkus.hibernate-orm.database.generation=drop-and-create to initialize Dev Services managed database
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
[org.hib.eng.jdb.spi.SqlExceptionHelper] (JPA Startup Thread) SQL Warning Code: 0, SQLState: 00000

[org.hib.eng.jdb.spi.SqlExceptionHelper] (JPA Startup Thread) table "todo" does not exist, skipping
[org.hib.eng.jdb.spi.SqlExceptionHelper] (JPA Startup Thread) SQL Warning Code: 0, SQLState: 00000
[org.hib.eng.jdb.spi.SqlExceptionHelper] (JPA Startup Thread) sequence "hibernate_sequence" does not exist, skipping
[io.quarkus] (Quarkus Main Thread) todo 1.0.0-SNAPSHOT on JVM (powered by Quarkus) started in 4.381s. Listening on: http://localhost:8080
[io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
[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 [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>

Per testare l'applicazione, è possibile usare cURL.

In un terminale separato creare un nuovo elemento dell’attività 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":"2022-12-30T15:17:20.280203Z"}

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 delle attività, inclusa quella appena creata:

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

Per testare l'applicazione, è possibile usare la classe TodoResourceTest esistente. Deve testare l'endpoint REST. Per testare l'endpoint, usa RESTAssured. Sostituire il codice nella classe TodoResourceTest con il codice seguente:

package com.example.demo;

import io.quarkus.test.junit.QuarkusTest;
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 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;

        given().body(todo)
                .header(CONTENT_TYPE, APPLICATION_JSON)
                .when().post("/api/todos")
                .then()
                .statusCode(201);
    }
}

Per testare l'applicazione, è anche necessario che Docker Desktop sia in esecuzione, perché Quarkus rileva di aver bisogno del database PostgreSQL per i test. Testare l'applicazione usando questo comando:

./mvnw clean test    # On Mac or Linux
mvnw.cmd clean test  # On Windows

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: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------