Oefening: een Quarkus-toepassing maken

Voltooid

In deze les maakt u een eenvoudige Quarkus-toepassing. U gebruikt Maven om de toepassing en een IDE (Integrated Development Environment) van uw keuze te bootstrapen om de code te bewerken. Gebruik de Bash-omgeving die is geconfigureerd in de vereisten om de code uit te voeren. U gebruikt Docker om een lokale PostgreSQL-database te starten, zodat u de toepassing lokaal kunt uitvoeren en testen.

De Quarkus-toepassing genereren met behulp van Maven

Er zijn verschillende manieren om een Quarkus-projectstructuur te genereren. U kunt de Quarkus-webinterface, een IDE-invoegtoepassing of de Quarkus Maven-invoegtoepassing gebruiken. Laten we de Maven-invoegtoepassing gebruiken om de projectstructuur te genereren.

U genereert uw toepassing met verschillende afhankelijkheden:

  • De resteasy afhankelijkheid om een REST-eindpunt beschikbaar te maken
  • De jackson afhankelijkheid voor het serialiseren en deserialiseren van JSON
  • De hibernate afhankelijkheid voor interactie met de database
  • De postgresql afhankelijkheid om verbinding te maken met de PostgreSQL-database
  • De quarkus-container-image-docker dependency voor het bouwen van een Docker-afbeelding

U hoeft geen Azure-afhankelijkheden op te geven omdat u uw toepassing eerst lokaal uitvoert en vervolgens een containerversie ervan implementeert in Azure Container Apps.

Notitie

De opdrachten in deze module maken gebruik van de Bash-syntaxis. Plak ze niet ongewijzigd in PowerShell of Windows opdrachtprompt. Voer op Windows de Bash-opdrachten uit in de geconfigureerde WSL-distributie. Gebruik een andere lokale Bash-terminal voor HTTP-aanvragen terwijl de toepassing wordt uitgevoerd.

Controleer voordat u het project genereert of uw Bash-omgeving gebruikmaakt van Java 17 en Maven 3.9.9:

java -version
mvn --version

De initiële generatoropdracht maakt gebruik van uw geïnstalleerde Maven. De Maven-wrapper van het project wordt later gemaakt, dus deze vereiste wordt er niet door vervangen. Selecteer de vereiste Java- en Maven-versies voordat u doorgaat.

Genereer de to-do toepassing vanuit de map waarin u het project wilt maken:

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"

Met deze opdracht maakt u een nieuw Quarkus-project in een map met de naam todo. Er wordt een Maven-mapstructuur gegenereerd (src/main/java voor broncode en src/test/java voor tests). Er worden enkele Java-klassen, enkele tests en enkele Dockerfiles gemaakt. Er wordt ook een pom.xml bestand gegenereerd met alle benodigde afhankelijkheden (Hibernate, RESTEasy, Jackson, PostgreSQL en 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>

Notitie

Alle afhankelijkheden in het bestand pom.xml worden gedefinieerd in de Quarkus BOM (bill of materials) io.quarkus.platform:quarkus-bom.

Codeer de toepassing

Wijzig vervolgens de naam van de gegenereerde MyEntity.java-klasse in Todo.java (in dezelfde map als het TodoResource.java bestand). Vervang de bestaande code door de volgende Java-code. Het maakt gebruik van Java Persistence-API (jakarta.persistence.*-pakket) voor het opslaan en ophalen van gegevens van uw PostgreSQL-server. Het maakt ook gebruik van Sluimerstand ORM met Panache (overgenomen van io.quarkus.hibernate.orm.panache.PanacheEntity) om de persistentielaag te vereenvoudigen.

U gebruikt een JPA-entiteit (@Entity) om het Java-Todo-object rechtstreeks toe te wijzen aan de PostgreSQL-Todo-tabel. Het TodoResource REST-eindpunt maakt vervolgens een nieuwe Todo entiteitsklasse en blijft deze behouden. Deze klasse is een domeinmodel dat gemapt is op de Todo tabel. De tabel wordt automatisch gemaakt door JPA.

Als u PanacheEntity uitbreidt, krijgt u een aantal algemene CRUD-methoden (create, read, update en delete) voor uw type. U kunt bijvoorbeeld Todo objecten opslaan en verwijderen in slechts één regel Java-code.

Voeg de volgende code toe aan de entiteit 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 +
                '}';
    }
}

Om die klasse te beheren, werkt u TodoResource bij, zodat deze REST-interfaces kan publiceren voor het opslaan en ophalen van gegevens via HTTP. Open de TodoResource-klasse en vervang de code door het volgende:

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

De POST-methode retourneert de URI van het nieuwe item in de Location header. De get-methode voor het item bedient die URI en retourneert HTTP 404 als het item niet wordt gevonden. De null-guard verwerkt ook padwaarden die RESTEasy converteert naar een null-id, zonder null door te geven aan Panache.

De toepassing uitvoeren

Wanneer u de toepassing uitvoert in de ontwikkelmodus, moet Docker worden uitgevoerd. Dat komt doordat Quarkus detecteert dat u een PostgreSQL-database nodig hebt (vanwege de PostgreSQL-afhankelijkheid quarkus-jdbc-postgresql gedeclareerd in het pom.xml bestand), downloadt u de PostgreSQL Docker-installatiekopieën en start u een container met de database. Vervolgens wordt de Todo tabel in de database automatisch gemaakt.

Notitie

Het vastgepinde Quarkus 3.18.4-platform maakt gebruik van Testcontainers 1.20.4, waarvan de Java Docker-client standaard API 1.32 gebruikt. Docker Engine 29.x accepteert standaard die API-versie niet meer. Als u Engine 29.x gebruikt, voer docker version uit en controleer of het ondersteunde API-bereik van de server 1.44 bevat. Alleen als dit het geval is, voer de volgende opdracht uit in deze Bash-shell voordat u Quarkus start of tests uitvoert:

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

Houd deze instelling in dezelfde shell voor de test- en latere pakketopdrachten. JDK_JAVA_OPTIONS levert de Java-systeemeigenschap aan Java-launcherprocessen zonder de vastgezette Quarkus-versies te wijzigen. Alleen DOCKER_API_VERSION instellen configureert deze versie van de Java Docker-client niet. Forceer API 1.44 niet op een server waarvan het ondersteunde bereik het uitsluit; gebruik in plaats daarvan een afzonderlijk gevalideerde compatibele Docker- en clientconfiguratie.

Zorg ervoor dat Docker wordt uitgevoerd en toegankelijk is vanuit uw geconfigureerde Bash-omgeving. Voer in de terminal waar u het project hebt gegenereerd de todo map in en start de toepassing:

cd todo
./mvnw quarkus:dev

Voer alle volgende Maven-opdrachten uit vanuit deze gegenereerde todo map.

De Quarkus-toepassing moet worden gestart en verbinding maken met uw database. U ziet nu de volgende uitvoer:

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>

Als u de toepassing wilt testen, kunt u cURL gebruiken.

Maak in een tweede lokale Bash-terminal een nieuw to-do item in de database met de volgende opdracht. U ziet het logboek in de Quarkus-console:

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

Met deze opdracht moet het gemaakte item worden geretourneerd (met een id):

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

Maak een tweede to-do met behulp van de volgende cURL-opdracht:

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

Haal vervolgens de gegevens op met behulp van een nieuwe cURL-aanvraag:

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

Met deze opdracht wordt de lijst met to-do items geretourneerd, inclusief de items die u hebt gemaakt. Elk item bevat createdAtook; in het volgende voorbeeld worden deze tijdstempels weggelaten voor beknoptheid:

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

De toepassing testen

Nadat de HTTP-controles zijn uitgevoerd, gaat u terug naar de terminal met Quarkus en drukt u op Ctrl+C om de ontwikkelmodus te stoppen. Houd deze Bash-shell open en blijf in de todo map voor de testopdracht.

Als u de toepassing wilt testen, kunt u de bestaande TodoResourceTest-klasse gebruiken. Het REST-eindpunt moet worden getest. Om het eindpunt te testen, wordt RESTAssuredgebruikt. De tests behouden de verzamelingscontrole, volgen de door POST geretourneerde Location, en controleren ontbrekende en ongeldige item-id's. Vervang code in de klasse TodoResourceTest door de volgende code:

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

De aanmaaktest leest de gegenereerde id van het POST-antwoord en vergelijkt beide id's als long waarden. Met de ongeldige id-test wordt automatische URL-codering uitgeschakeld, zodat de aanvraag de bewust gecodeerde spatie bevat.

Wanneer u de toepassing test, moet Docker worden uitgevoerd en toegankelijk zijn vanuit dezelfde Bash-omgeving, omdat Quarkus een afzonderlijke PostgreSQL Dev Service start voor testen. Test de toepassing in de terminal waarin u Quarkus hebt gestopt met behulp van deze opdracht:

./mvnw clean test

U zou een uitvoer moeten zien die er ongeveer als volgt uitziet:

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

Houd deze Bash-shell geopend voor de databaseoefening. Daar stelt u AZ_POSTGRES_JDBC_URL, AZ_POSTGRES_USERNAME en AZ_POSTGRES_PASSWORD in en start Quarkus vervolgens opnieuw vanuit die shell. Stop het proces voor externe ontwikkeling met Ctrl+C vóór de latere ./mvnw clean package- en implementatiestappen. Behoud de variabelen en herhaal de resource-name en de wachtwoordconfiguratie niet tussen units.

Een proces neemt de omgeving over wanneer het wordt gestart. Bij het exporteren van variabelen in een andere terminal wordt een al actief Quarkus-proces niet bijgewerkt. Het bewerken van application.properties en vertrouwen op live reload is niet voldoende voor de overstap naar Azure; start de toepassing opnieuw vanuit de shell waar u de variabelen hebt ingesteld.