Exercise - Create a Quarkus application
In this unit, you create a basic Quarkus application. You use Maven to bootstrap the application and an integrated development environment (IDE) of your choice to edit the code. Use a terminal of your choice to run the code. You use Docker to start a local PostgreSQL database so you can run and test the application locally.
Generate the Quarkus application by using Maven
There are several ways to generate a Quarkus project structure. You can use the Quarkus web interface, an IDE plugin, or the Quarkus Maven plugin. Let's use the Maven plugin to generate the project structure.
You generate your application with several dependencies:
- The
resteasy
dependency to expose a REST endpoint - The
jackson
dependency to serialize and deserialize JSON - The
hibernate
dependency to interact with the database - The
postgresql
dependency to connect to the PostgreSQL database - The
docker
dependency to build a Docker image
You don't need to specify Azure dependencies because you run your application locally first and then deploy a containerized version of it to Azure Container Apps.
At a command prompt, generate the to-do application:
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"
This command creates a new Quarkus project. It generates a Maven directory structure (src/main/java
for source code and src/test/java
for tests). It creates some Java classes, some tests, and some Dockerfiles. It also generates a pom.xml file with all the needed dependencies (Hibernate, RESTEasy, Jackson, PostgreSQL, and 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>
Note
All the dependencies in the pom.xml file are defined in the Quarkus BOM (bill of materials) io.quarkus.platform:quarkus-bom
.
Code the application
Next, rename the generated MyEntity.java class to Todo.java (located in the same folder as the TodoResource.java file). Replace the existing code with the following Java code. It uses Java Persistence API (jakarta.persistence.*
package) to store and retrieve data from your PostgreSQL server. It also uses Hibernate ORM with Panache (inheriting from io.quarkus.hibernate.orm.panache.PanacheEntity
) to simplify the persistence layer.
You use a JPA entity (@Entity
) to map the Java Todo
object directly to the PostgreSQL Todo
table. The TodoResource
REST endpoint then creates a new Todo
entity class and persists it. This class is a domain model that's mapped on the Todo
table. The table is automatically created by JPA.
Extending PanacheEntity
gets you a number of generic create, read, update, and delete (CRUD) methods for your type. So you can do things like saving and deleting Todo
objects in just one line of Java code.
Add the following code to the Todo
entity:
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 +
'}';
}
}
To manage that class, update the TodoResource
so that it can publish REST interfaces to store and retrieve data by using HTTP. Open the TodoResource
class and replace the code with the following:
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();
}
}
Run the application
When you run the application in development mode, Docker needs to be running. That's because Quarkus detects that you need a PostgreSQL database (because of the PostgreSQL dependency quarkus-jdbc-postgresql
declared in the pom.xml file), downloads the PostgreSQL Docker image, and starts a container with the database. It then automatically creates the Todo
table in the database.
Make sure Docker is running locally on your machine and run the to-do application by using this command:
cd todo
./mvnw quarkus:dev # On Mac or Linux
mvnw.cmd quarkus:dev # On Windows
The Quarkus application should start and connect to your database. You should see the following output:
[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>
To test the application, you can use cURL.
In a separate terminal, create a new to-do item in the database with the following command. You should see the log in the 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
This command should return the created item (with an identifier):
{"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"}
Create a second to-do by using the following cURL command:
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
Next, retrieve the data by using a new cURL request:
curl http://127.0.0.1:8080/api/todos
This command returns the list of to-do items, including the items you created:
[
{"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}
]
Test the application
To test the application, you can use the existing TodoResourceTest
class. It needs to test the REST endpoint. To test the endpoint, it uses RESTAssured. Replace code in the TodoResourceTest
class with the following code:
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);
}
}
When you test the application, Docker Desktop needs to be running because Quarkus detects that it needs the PostgreSQL database for testing. Test the application by using this command:
./mvnw clean test # On Mac or Linux
mvnw.cmd clean test # On Windows
You should see output that looks similar to this:
[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] ------------------------------------------------------------------------