This post and the code used this book as a refence.

The basic concepts you can see in Quarkus - Part 1.

Package Persistence FaultTolerance
Observability Kubernate Authentication and Authorization
Application Secrets Management Reactive Programming Model Conclusion


Package

You can package the application to create, e.g., (1) a CLI application. For that, it's necessary to implements QuarkusApplication and create the main class identified by the annotation @QuarkusMain.

public class GreetingMain implements QuarkusApplication{...}

@QuarkusMain
public class JavaMain {
  public static void main(String... args) {
    Quarkus.run(GreetingMain.class, args);
  }
}

// package
$ ./mvnw clean package -DskipTests
// Starting...
java -jar target/greeting-started-cli-1.0-SNAPSHOT-runner.jar Hi!
// Call
curl localhost:8080/hello
// Result
Hi!

Another porpose is (2) to create an runnable JAR file. For that, it's necessary to use Quarkus Maven plug-in.

// Maven
./mvnw clean package
// Gradle
./gradlew quarkusBuild

// target folder
getting-started-1.0-SNAPSHOT-runner.jar // runnable file (should be in the deploy)
getting-started-1.0-SNAPSHOT.jar        // dependencies
lib                                     // library  (should be in the deploy)

// running in JVM mode (running inside the JVM) -> recommended to containers
$ java -jar target/getting-started-1.0-SNAPSHOT-runner.jar

To build a container you can use Dockerfile.jvm. This file adds the lib directory and the runnable JAR.

// inside the file
COPY target/lib/* /deployments/lib/
COPY target/* -runner.jar /deployments/app.jar

// Create
./mvnw clean package
docker build -f src/main/docker/Dockerfile.jvm -t example/greetings-app .
// starting container
docker run -it --rm -p 8080:8080 example/greetings-app


Persistence

The persistence in some database is necesssary add the dependencies in POM file and the database configuration inside the application.properties. Here is an example using h2 database (in-memory). You Can configure more than one database. Fot that, you need to add an optional name to diferenciate the database.

// POM
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-hibernate-orm</artifactId>
</dependency>

// application.properties (Default)
quarkus.datasource.db-kind=h2
quarkus.datasource.username=sa
quarkus.datasource.password=sa
quarkus.datasource.jdbc.url=jdbc:h2:mem:quarkus-social
quarkus.datasource.jdbc.max-size=16
quarkus.hibernate-orm.database.generation=drop-and-create

// a second database if necessary - adding an optional name
quarkus.datasource.users.db-kind=h2
quarkus.datasource.users.username=user1
quarkus.datasource.users.url=jdbc:h2:mem:quarkususerdb
quarkus.datasource.users.max-size=16

An important feature is to ckeck the database health. You need to add an extension for it. Those extention do the check automatically. But it can be disabled.

// Install extension
$ ./mvnw quarkus:add-extension -Dextensions="quarkus-agroal,quarkus-smallrye-health"

// disabled check if necessary - application.properties
quarkus.datasource.health.enabled

// Endpoint to check created by quarkus-smallrye-health
/health/ready

Another important point when we talo about persistence is the transaction. Quarkus use the annotation @javax.transaction.Transactional from the extension quarkus-narayana-jta that is added when the persistence extensions are installed. This extensions add the annotation, TransactionManager and UserTransaction classes. The TransactionManager can be used if you need to manually roll back a transaction. The UserTransaction can be used if you need controler over the transaction (Methods: begin(), commit(), rollback(), setRollBackOnly(), getStatus(), setTrasanctio(int)). The transaction's context is propagated throught the calls inside the annotation. In the end, the trasanction do the commit.

@Transactional(Transactional.TxType.NEVER)
public String word() {
    return "Hi";
}

The same way, You can use JPA with persistence.xml. The persistence unit has priority over other configurations such as the quarkus.hibernate-orm.* properties. So, you have to choose which one to use. To use entities from different JARs you can use an empty META-INF/beans.xml.

Beside the DAO (using Entity Manager) or repository pattern, a good alternative in quarkus to the persistence is to use Panache, an API build on top of JPA. A complete example you can see here

  • Etension: quarkus-hibernate-orm-panache
  • Create a class with the annotation @javax.persistence.Entity, and extending from PanacheEntity or PanacheEntityBase.
  • Use PanacheEntityBase.findById(Object).
  • For reposotory you need implements PanacheRepository (CDI bean)

And giving more support regarding persistence, Quarkus allow the use of the Flyway at start up or programmatically. The Flyway is the alternative to migrate the database schema.

At start-up

  1. Add the Flyway extension.
  2. Add the JDBC driver for your database.
  3. Setup the datasource(s).
  4. Add migrations to src/main/resources/db/migration.
  5. Set the quarkus.flyway.migrate-at-start to true.

Programmatically

@Inject
Flyway flyway

// for a specific database
@FlywayDataSource


Fault Tolerance

Quarkus, throught the MicroProfile, give support to retrieve automaticaly any CDI element in order to recover from some failure. The important part of fault-tolerance strategy is a fallback logic if the recovery is not possible.

An important point is that fault tolerance can be anable or disable globally or individually.

To use the MicroProfile Strageies is necessary add extensions and annotate class or method.

./mvnw quarkus:add-extension -Dextensions="quarkus-smallrye-fault-tolerance"

@Path("/faulttolerance")
public class FaultToleranceResource {

    private static final Logger LOGGER = LoggerFactory.getLogger(FaultToleranceResource.class);

    // Test: curl http://localhost:8080/faulttolerance
    @GET
    public String faultTolerance(){
      LOGGER.info("Starting process...");
        return test();
    }

    // PS: This method has to be public
    @Retry(maxRetries = 3, delay = 1000)
    @Fallback(RecoverFaultToleranceFallback.class)
    public String test() {
      int x = 2 / 0;
        return "X:" + x;
    }
}

public class RecoverFaultToleranceFallback implements FallbackHandler<String> {
 @Override
 public String handle(ExecutionContext executionContext) {
    return "see you later, alligator";
 }
}

// Console
 curl http://localhost:8080/faulttolerance
see you later, alligator%         

If you need to set the timeout to avoid wait for a service you can use the annotation @Timeout(value = 2000).

Also, you can limit the number of request using the Bulkhead Pattern.

This code is here.


Observability

Quarkus gives an API to check the state of service from another machine. For that, it is necessary to install the extension, which makes available two endpoints: Liveness URL and Readiness URL. The first one return 200 (service is UP), 503 (service is DOWN) and 500 (server error). The second one return 200 (service is UP and readu to process the requests), 503 (service unavailabe to receive requests) and 503 (server error).

The health check to database is created automatically when the Quarkus is used to use it. It can be disabled by properties attibutes such as explained in Persistence topic.

// Install extension
./mvnw quarkus:add-extension -Dextensions="quarkus-smallrye-health"

// Check
curl localhost:8080/health/live
curl localhost:8080/health/ready

// Result
{
    "status": "UP",
    "checks": [
    ]
}

Also, it can be customized. You just need to implements the HealthCheck interface and set the class as Livenss or Readiness.

@ApplicationScoped
@Liveness
public class LivenessCheck implements HealthCheck {
    @Override
    public HealthCheckResponse call() {... }
}

The Observability also allows expose metrics about the services to be used by some tool. It can be customized as well.

// Install the extension
./mvnw quarkus:add-extension -Dextensions="quarkus-smallrye-metrics"
// Check
curl localhost:8080/metrics

Other important feature available is monitoring a distributed application with distributed tracing. It can detect issues between the services. It can be done by extension as "quarkus-smallrye-opentracing or quarkus-tracing; or it can be customized to add some specific information.


Integrating with Kubernate

Kubernates is a plataform to deploy the applications. To deploy the application is necessary a pod.

  • Pod: a working unit that represents a group of containers that are running in the same host machine and share resources like IP and ports
  • It's necessary to build a container of the servie.
  • Quarkus has extensions to Docker (quarkus-container-image-docker), Jib (quarkus-container-image-jib) and S2I (quarkus-container-image-s2i) to build and push the container

In Package you can see an example how to build a container with Docker and bellow how to push.

// Build and Push
./mvnw clean package -Dquarkus.container-image.push=true
// Only build
./mvnw clean package -Dquarkus.container-image.build=true

If you need to create automatically resources to Kubernate or OpenShift you can use the Quarkust extension:

// Install kubernate extention
./mvnw quarkus:add-extension -Dextensions="quarkus-kubernetes"
// Execute
./mvnw package
// Results: 
// (1) target/kubernetes/kubernetes.json and target/kubernetes/kubernetes.yaml
// (2) target/kubernetes/openshift.json and target/
kubernetes/openshift.yaml

Also, using the Kubernates extension, it is possible to use the ConfigMap to set some configurations read by the application by environment. Using MicroPorfile Config, the properties can be read using the @ConfigProperties annotation.


Authentication and Authorization

Quarkus has resourses to support this feature. One of them is the Authorization with MicroProfile JSON Web Token (JWT). It allows save a security context in REST, for instance. The MicroPrifile consume and validate the token that can transmit user name and the expiration info.

The section of the token are: Header (metadata), Claims (information), Signature. They are encoded to Base64. Here is an simple example shown only the mandatory attributes.

// extention
quarkus-smallrye-jwt

// Header
{
  "kid": "privateKey.pem" 
  "typ": "JWT",           
  "alg": "RS256"          
},
// Claim
{
  "sub": "me-jwt-rbac"       
  "aud": "jwt-rbac",         
  "upn": "me@quarkus.io",    
  "iss": "https://quarkus.io/using-jwt-rbac" 
  "groups": [
    "Tester"
    ]
  "exp": 2200814171,        
  "iat": 1570094171,        
  "jti": "a-123"            
}

// Token serialized (Separated by periods)
eyJraWQiOiJcL.3ByaXZhdGVLZX.joiSldUIiwiYWxnIjoiUlMyNTY


Application Secrets Management

Some data are confidential and need to be store in a secure place like Kubernate. Kubernate has an object called secret for that porpose. The data are encoded to Base64 and you can encrypt them. The Quarkus ggive support to that integration injecting the secret into container. It can be done by environment variable or volume.

// Create as env variable
kubectl create secret generic greeting-security --from-literal=github.api.key.token=a.b.c
// Use
@ConfigProperty(name = "github.api.key.token")
String githubToken;


Quarkus REST Clients

Now, to access external services the book gives some examples. One of them is using the client or MicroProfile Rest Client.

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
...

@Path("/now")
public class ClockResource {

  String clockHost = "http://example.com";
  private Client client = ClientBuilder.newClient();

  @GET
  @Path("{timezone}")
  public String getNow(@PathParam("timezone") String timezone) {
        client.target(clockHost)
                .path("api/json/{timezone}/now")
                .resolveTemplates("timezone", timezone)
                .request(MediaType.APPLICATION_JSON)
                .get(WorldClock.class);
    }
 }   

Also is possible to add attributes to the heater and send to the external service implementing the interface ClientHeadersFactory.


Developing Quarkus Application Using Spring APIs

Quarkus give support to the Spring libraries. You can use, for example, Spring DI and the application will have the same behave. The extenion to use Spring is "spring-di".

The book (Cap. 14) shows the equivalence between some annotations:

Spring CDI/MicroProfile
@Autowired @Inject
@Qualifier @Named
@Value @ConfigProperty
@Component, @Service, @Repository @Singleton
@Configuration @ApplicationScoped
@Bean @Produces
@RequestMapping @Path
@PathVariable @PathParam


Reactive Programming Model

The Quarkus support asynchronous Http endpoint that can be done using the extension quarkus-smallrye-reactive-streams-operators to use native Streams or MicroProfile Reactive specification. For SmallRyeMultiny you can use quarkus-resteasy-mutiny.

// Ex1
ReactiveStreams.of("a", "v").map(String::toUpperCase).toList().run().thenApply(list -> list.toString());

// Ex2
Multi.createFrom().items("a", "b");


Conclusion

It is a resource with many options and compatibilities. The decision about which one to use should be made after comparing it with Spring, for example, and seeing the benefits and team experience.

It is a really good book that gives an overview of the Quarkus. The knowledge should be complemented with the Quarkus documentation and experiences in projects.


References