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

Overview

Quarkus is a runtime environment created by Red Hat in 2019 to fill the gaps regarding cloud environment costs and constraints such as slow startup time, heavy memory consumption and mutable environments.

The Quarkus is a cloud-native framework with built-in Kubernetes integration. The kubernate is the main platform to deploy the enterprise application. Beside the Kubernate, the technologies involved in Quarkus are Java, GraalVM, and Reactive Systems. Also, it supports some Jakarta EE and MicroProfile specifications. It is microservice-oriented.

Benefits:

  • Integration with Docker and Kubernate: the application is in a docker container and run on Kubernetes
  • Quick startup time: no additional class loading, runtime scanning, or other process done by JVM
  • Low resident set size (RSS) memory: small, quick, and productive execution
  • Increased developer productivity
  • Fast-paced area of microservices and cloud-based applications
  • Better scalability of the application


Concepts behind the Quarkus

Reactive: React is the idea to response something when is called.

  • Reactive systems: Systems designed to react (e.g to a message, to a request). Principles: responsive, resilient, elastic (application to work with a variable number of instances) and message-driven.
  • Reactive streams: Stream processing with non-blocking backpressure (restriction of the number of messages that a subscriber can accept). It is a low-level contract (Publisher, Subscriber, Subscription, Processor).
  • Reactive programming: Programming model (based on Reactive streams) on which reactive systems depend on (e.g: validation, marshalling/unmarshallin JSON/XML, Injecting Dependencies, Qualifier, Interceptors).

Microprofile: Quarkus integrate Eclipse MicroProfile through SmallRye where are many microservices design patterns

  • CDI (Context and Dependency Injection): it is a MicroProfile specification and makes the components injectable, interceptable and manageable bean. It uses scope and event management. The characteristic of having a low coupled is achived by the interceptors, decorators and events. Quarkus not implement all the CDI specification. CDI is runtime based. Managed Beans are container-managed objects that support resource injection, life cycle management and interception. The Container manage the bean. In Quarkus, is not necessary to use the annotation @Inject when use the constructor to injection. It's possible to use @Qualifier to distinguish between beans to be instanciate. The annotations are: @Inject and @ApplicationScope.
  • JAX-RS: it is a specification that provides support for creating REST based web services.
  • JSON-B: specification or Jackson project to marshall and unmarshall JSON documents from/to Java objects.
        // Maven
        ./mvnw quarkus:add-extension -Dextensions="quarkus-resteasy-jsonb"
        // Gradle
        ./gradlew addExtension --extensions="quarkus-resteasy-jsonb"
        
  • JAX-B: specification to marshall and unmarshall XML documents from/to Java objects.
          ./mvnw quarkus:add-extension -Dextensions="quarkus-resteasy-jaxb"
          ./gradlew addExtension --extensions="quarkus-resteasy-jaxb"
          
  • JSON-P: it is a specification to allows JSON processing in Java.
  • Fault Tolerance
  • Health: provide information about their state to external viewers
  • Metrics: export monitoring data to management agents
  • OpenAPI: provides a Java API for to expose API documentation.
  • REST Client: type safe approach using proxies and annotations for invoking RESTful services over HTTP
  • JWT Auth: provides Role-Based Access Control (RBAC) using OpenID Connect (OIDC) and JSON Web Tokens (JWT).

Cloud Native Computing: build and run scalable applications in modern, dynamic environments.

Technologies: containers, microservices, serverless functions, service meshes and immutable infrastructure.


Startup

Scaffold

Quarkus gives a solid foundation to creating a project structure. You can use maven plugin to create a maven or gradle application, create by your IDE, and download from quarkus code page. So, you can create quarkus by command line or maven command.

  • Maven
          $ mvn io.quarkus:quarkus-maven-plugin:1.4.1.Final:create \
          -DprojectGroupId=org.acme \
          -DprojectArtifactId=getting-started \
          -DclassName="org.acme.quickstart.GreetingResource" \
          -Dpath="/hello"
          
  • Gradle
          $ mvn io.quarkus:quarkus-maven-plugin:1.4.1.Final:create \
          -DprojectGroupId=org.acme \
          -DprojectArtifactId=getting-started-with-gradle \
          -DclassName="org.acme.quickstart.GreetingResource" \
          -Dpath="/helloWithGradle" \
          -DbuildTool=gradle
          

You can list all the extensions and install using this:

./mvnw quarkus:list-extensions
./mvnw quarkus:add-extensions -Dextensions="jdbc-h2,hibernate-orm,hibernate-orm-panache,resteasy-jsonb"

Launch dev mode

Quarkus gives the support to review the results after some change in the poject without repackage and redeploy the application (hot deployment). The changes to be detexted are Java files, application configs, and static resources.

# for maven project
./mvnw compile quarkus:dev
# for gradle project
./gradlew compile quarkusDev

Restful Service

The Quarkus give support to create REST API by JAX-RS framework. It uses RESTEasy and Vert.x by default. When the project is created the endpoint 'hello' and the test is given as example.

@Path("/hello")
public class GreetingResource {
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }
}

@QuarkusTest
public class GreetingResourceTest {
    @Test
    public void testHelloEndpoint() {
        given()
          .when().get("/hello")
          .then()
             .statusCode(200)
             .body(is("hello"));
    }

}

The project needs 'quarkus-resteasy-jsonb' or 'quarkus-resteasyjackson' extension to has a json response.

$ ./mvnw quarkus:list-extensions
$ ./mvnw quarkus:add-extensions -Dextensions="resteasy-jsonb"

CORS

Quarkus also give support to Cross-Origin Resource Sharing (CORS) by use of quarkus.http.cors attribute inside of application.properties.

quarkus.http.cors=true
quarkus.http.cors.origins=http://www.test.com
quarkus.http.cors.methods=GET
quarkus.http.cors.headers=accept,authorization,content-type,x-requested-with

Route

For endpoints using reactive routes Quarkus uses 'io.vertx.ext.web.Router'. It can be done registering routes in classes or using annotation @Route. For extension is necessary to install it.

Quarkus HTTP is based on a nonblocking and reactive engine where event loop manage each request and handle the logic by worker theread (JAX-RS) I/O thread (reactive route).

// Install extension
$ ./mvnw quarkus:add-extension -Dextensions="quarkus-vertx-web"

// Using Class
public void routes(@Observes Router router) {
  router.get("/ok").handler(rc -> rc.response().end("OK from Route"));
}

// Using Annotation
@Route(path = "/declarativeok", methods = HttpMethod.GET)
public void routesWithAnnotation(RoutingContext routingContext){
  String myAttribute = routingContext.request().getParam("myAttribute");
  routingContext.response().end("Declarative OK: " + myAttribute);
}

The code you can see here.

Also, it's possible intercept request to manipulate the logic. I can be done with Verti.x or JAX-RS. This filter will be applied to JAX-RS resources and reactive routes.

public void filters(@Observes Filters filters) {
  filters.register(rc -> {
    rc.response()
      .putHeader("V-Header", "Header added by VertX Filter");
    rc.next();
  }, 10);
}

The code you can see here.

Filter

It's possible apply the filter only to JAX-RS using ContainerRequestFilter or ContainerResponseFilter.

@Provider
public class MyContainerResponseFilter implements ContainerResponseFilter {
    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
        responseContext.getHeaders().add("JAX-RS-Header", "New Header");
    }
}

The code you can see here.

If necessary, in terms of security, you can use SSL to use certificate:

// create the certificate inside your resources folder
keytool -genkey -keyalg RSA -alias selfsigned \
-keystore keystore.jks -storepass changeit \
-validity 360 -keysize 2048


// Configs in application.properties to use the certificate
quarkus.http.ssl-port=8443
quarkus.http.ssl.certificate.key-store-file=keystore.jks
quarkus.http.ssl.certificate.key-store-file-type=jks
quarkus.http.ssl.certificate.key-store-password=changeit

Event

The Event (javax.enterprise.event.Event) in Quarkus follow the observer pattern. The events in CDI are not treated asynchronously.

There are many ways to intercept the events and apply logic if necessary. One of that is when Quarkus fires CDI events at startup and shutdown of the application.

// Example to catch the event when start the application
void onStart(@Observes StartupEvent event) {
  LOGGER.info("Application starting...");
}
// Example to catch the event when stop the application
void onStop(@Observes ShutdownEvent event) {
   LOGGER.info("Application shutting down...");
}

The code you can see here.

Or after the creation (@PostConstruct) of a component. The logic happens after the constructor is called and after the injections. Or before the destruction (@PreDestroy).

Configuration

The Quarkus can use application.properties/yaml to access the properties for the application. Those properties can be accessed by the class org.eclipse.microprofile.config.inject.ConfigProperty. The properties can be unic or a list.

// properties
greeting.message=Hello World
greeting.suffix=!!, How are you???
%test.greeting.message=Hello World
%dev.greeting.message=Hello World
%prod.greeting.message=Hello World

// Accesss by annotation
@ConfigProperty(name = "greeting.message")
String message;

// Access by injection
@Inject
Config config;

// Access programatically
Config config = ConfigProvider.getConfig();
invoice.vatRate = config.getValue("invoice.vatRate", Float.class);

If no profile is declarad so the property is use as default production mode. If not, Quarkus recognize different profiles for the environment: test, dev, prod (%{profile}.config.key=value.)

The configuration can be done by:

  • System properties (-Dgreeting.message=Hello). System properties have more priority than environment variables
  • Environment variables (GREETING_MESSAGEE=hello)
  • Environment file named .env
  • External config directory (config/application.properties)
  • Resources src/main/resources/application.properties

PS: Eclipse MicroProfile Configuration has some default converters as boolean and Boolean that can has, e.g, true, 1, YES, Y, and ON to TRUE and FALSE to toher cases.

In the properties file is possible to define the log level as well.

quarkus.log.level=DEBUG
quarkus.log.file.enable=true
quarkus.log.category."io.undertow.request.security".level=TRACE
#restrict log lines from classes inside the package (and subclasses) 
quarkus.log.category."org.study".level=WARNING

Converter

The converters are very useful the get some data that comes from the endpoint and make it a known object.

//application.properties
greeting.percentage=25%

// Controller
@ConfigProperty(name = "greeting.percentage")
Percentage percentage;
@GET
@Path("/percentage")
@Produces(MediaType.TEXT_PLAIN)
public double percentage() {
  return percentage.getPercentage();
}

// Converter
@Priority(300) // Default value: 100; Quarkus Converter: 200
public class PercentageConverter implements Converter<Percentage> {
    @Override
    public Percentage convert(String value) {
        String numeric = value.substring(0, value.length() - 1);
        return new Percentage (Double.parseDouble(numeric) / 100);
    }
}

The code you can see here.

The converter needs to be registered to be recognized by the application:

  • create the file: META-INF/services/org.eclipse.microprofile.config.spi.Converter
  • Content: org.acme.quickstart.domain.percentage.PercentageConverter

Validation

Quarkust allows use annotations to validate input and output Values of the REST. That's the bean validation. Also, the validation can be customized or even programmatically

// Install
./mvnw quarkus:add-extension -Dextensions="quarkus-hibernate-validator"
./gradlew addExtension --extensions="quarkus-hibernate-validator"

@NotBlank
@Size(min = 4)
@Max(6)
private String serialNumber;

@POST
@Consumes(MediaType.APPLICATION_XML)
public Response addComputer(@Valid Computer computer) {
  computers.add(computer);
  return Response.ok().build();
}

@GET
@Produces(MediaType.APPLICATION_XML)
public @Valid List<Computer> getComputers() {
    return computers;
}

The code you can see here.

Qualifier

The Qualifiers are resources to distinguish a resource. With this Quarkus can decide which object should be injected whitout ambiguite.

So, you there are two classes that implement the same interface Quarkus needs to know which class should be instanciate. CDI define it by the annotation @Retention(RUNTIME) and @javax.inject.Qualifier. Here are some examples how to do it.

@Inject
@Named("en_US")
Locale en_US;

@Inject
@Named("es_ES")
Locale es_ES;

@Produces
public Locale getDefaultLocale() {
  return Locale.getDefault();
}

@Produces
@Named("en_US")
public Locale getEnUSLocale() {
return Locale.US;
}

@Produces
@Named("es_ES")
public Locale getEsESLocale() {
return new Locale("es", "ES");
}


// Custom Qualifiers
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
  public @interface SpainLocale {
}

@Produces
@SpainLocale
public Locale getSpainLocale() {
return new Locale("es", "ES");
}

@Inject
@SpainLocale
Locale spain;

The code you can see here.

Creating Interceptors

It is used to implement cross-cutting concerns, or manager data, e.g, to be handle in the same way. It involves an @InterceptorBinding (link the interceptor and the class/method to be intercepted), @AroundInvoke and @Interceptor.

@Inherited
@InterceptorBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface LogEvent {}

@LogEvent
@Interceptor
public class LogEventInterceptor {
    @AroundInvoke
    public Object logEvent(InvocationContext ctx) throws Exception {
        return ctx.proceed();
    }
}

@LogEvent
@GET
public void executeLog(Object order) {
    System.out.println("@@@@@@");
}

The code you can see here.

Tests

For the tests, there is the annotation @QuarkusTest. This annotation make the use of the quarkus test framework. It use the port 8081, the test profile is activated.

  1. The Quarkus application is automatically started once and the test execution is started.
  2. Each test is executed against this running instance.
  3. The Quarkus application is stopped.
//To execute
./mvnw clean compile test

// If you need change the port (application.properties):
quarkus.http.test-port=8083


References