The Quarkus framework is a Kubernetes native java stack, incredible quick start up times and build on strong standards like the Micro Profile. You might wanna give it a try if you use java for small footprint services running on Kubernetes, Openshift or similar.
Comparison with traditional frameworks
“Java is slow and a memory hog”.
That reputation makes java often seen as inferiour choice when it comes to small services running on the cloud. The rise in popularity of Kubernetes, smaller services and servless simply didn’t match anymore with a tomcat server requiring a minute to start and consuming 512MB of memory.Even worse Java did not respect cgroup settings until Java 8 which made it difficult to deploy on containers since it simply reserved all the resources available on the host. Using Java for cloud deployment became a hassle.
Traditional frameworks juggle lots of processing during startup. reading configuration, processing annotations, generating class metadata, open network sockets, … All those actions were executed during startup and runtime.
- the startup takes long, some stateful processes might even have to wait for completion
- same classloader is used for startup and runtime, making it difficult to garbage collect classes only used for the startup phase;
Quarkus optimized this by moving most of the startup processes to build time; Also called Ahead Of Time(AOT) compilation. Only few application centric actions remain in a quick startup phase. Quarkus also optimizes for cloud workloads providing metrics and analytics out of the box. Quarkus was built with the intention to run on containers. It pre-generates deployment descriptors and docker files. Tooling is available for most IDEs. Developing becomes save & refresh. There are modules available for all major community frameworks like spring, kafka, jaxb.
Analog to the spring-boot configuration code.quarkus.io offers a quick application configurator.
Additionally Quarkus supports native builds with GRAALVM. Since this is a topic on is own I exclude it from this post; In short it allows to create a native app i.e. for linux Kubernetes container and further improves resources usage and performance.
Bus Schedule application
The service demonstrates a simple use case to query an open available external API (in this case the NLB Bus Service API)) and exposing results through a REST endpoint. It offers an additional search endpoint to filter for route names.
You can find the complete project on github: quarkus-bus-schedule-api
Project scaffolding
For the beginning the easiest way is to configure a base project through code.quarkus.io. Quarkus offers an extension management through IDE + Maven and even their own Quarkus CLI tooling. Adding additional extensions later on is quick without the need to touch the pom.xml manually.
For this project the following extensions are required:
- quarkus-resteasy-reactive
- quarkus-rest-client-reactive
- quarkus-resteasy-reactive-jackson
- quarkus-minikube
- quarkus-kubernetes
- quarkus-container-image-jib
Quarkus provides pre-generated Dockerfiles and pre-generates deployment descriptors for Kubernetes/Minikube/…. It improves productivity not having to type out such boilerplate configuration.
Bootstrap
Starting quarkus in dev-mode provides a CLI console to control all runtime functionalities like test runner, logs, live-reload.
Quarkus enables live-reload by default; no restart necessary for any code change. You might still want to restart it if you add a new extension
./mvnw compile quarkus:dev
After the initial bootstrapping, starting the application takes less than a second.
2022-06-28 16:15:25,146 INFO [io.quarkus] (Quarkus Main Thread) code-with-quarkus 1.0.0-SNAPSHOT on JVM (powered by Quarkus 2.10.0.Final) started in 0.383s. Listening on: http://localhost:8080
DEV UI
Let’s have a look into the productivity boosting dev ui before we deploy the application to Minikube.
The dev ui (press d in the application CLI or go directly to http://localhost:8080/q/dev) is a dev web interface providing helpful productivity features. The interesting detail about the DEV UI is, that a developer can add their own functionality and metrics. Many extensions provide their own dev-tools here. RESTEasy shows and analyzes your REST endpoints for example. Every tile represents an extension. The console can be expanded on the bottom.
Implementation
We’ve got a a few models representing the data. They represent the Upstream (NLB) API.
/**
* Route
*
* bus route details
*/
public class Route {
@JsonProperty("routeId")
public String id;
@JsonUnwrapped
@JsonProperty("names")
public RouteNames names;
@JsonProperty("specialRoute")
public Boolean isSpecial;
}
//...
/**
* RoutesResponseWrapper
*
* parent object, wrapping route details
*/
public class RoutesResponseWrapper {
@JsonProperty("routes")
public Set<Route> routes;
}
/**
* RouteNames
*
* route names in different languages
*/
public class RouteNames {
@JsonProperty("routeName_e")
public String english;
@JsonProperty("routeName_c")
public String chinese;
}
The upstream API, requires an interface delegating requests to relevant endpoint URLs. The RouteMessageBodyReader extracts the response.
/**
* NLBApiService
*
* delegating to NLB api
*/
@RegisterRestClient
@RegisterProvider(RouteMessageBodyReader.class)
@Path("/")
public interface NLBApiService {
@GET
Uni<Set<Route>> routes();
}
The base URL is configured in the application.properties.
quarkus.rest-client."com.hkc.nlb.remote.NLBApiService".url=https://rt.data.gov.hk/v1/transport/nlb/route.php?action=list
# For detail insights into rest client logs
# quarkus.rest-client.logging.scope=request-response
# quarkus.rest-client.logging.body-limit=50
quarkus.log.category."org.jboss.resteasy.reactive.client.logging".level=DEBUG
A REST resource invokes the previously configured upstream API and forwards the results. In a real-world scenario this would be located in a service executing some business logic or transforming the upstream data.
/**
* BusScheduleResource
*
* API exposing bus schedule details
*/
@Path("routes")
public class BusScheduleResource {
NLBApiService NLBApi;
@Inject
BusScheduleResource(
@RestClient NLBApiService nlbService) {
this.NLBApi = nlbService;
}
/**
* list all routes
*
* @return all available routes
*/
@GET
public Uni<Set<Route>> all() {
return NLBApi.routes();
}
/**
* search for bus routes by name containing a text
*
* @param query search query must not be blank
* @return query result containing matching bus routes
*/
@GET
@Path("search")
public Uni<Set<Route>> search(@QueryParam("q") Optional<String> query) {
if (query.isEmpty() || query.get().isBlank()) {
throw new BadRequestException("must provide a query");
}
// if (Optional.ofNullable(query).empty().or)
final Predicate<Route> routeFilter = route -> route.names.english.toLowerCase().contains(query.get().toLowerCase());
final Comparator<Route> idComparator = (route1, route2) -> route1.id.compareTo(route2.id);
return NLBApi.routes()
.onItem()
.transform(
// preserve order to simplify test
(item) -> new LinkedHashSet<>(
item.stream()
.filter(routeFilter)
.sorted(idComparator)
.collect(Collectors.toList())));
}
}
Deployment
I briefly demonstrate the deployment to Minikube, which is a Kubernetes that can be setup quickly for development purposes. Checkout Minikube - Get Started for installation instructions. I will use Jib for building optimized containers. Jib optimizes the build process and just deploys layers that have changed. Depending on your needs you could choose other formats like S2I, Docker,…
First of all, install the extension for Minikube, if you haven’t include it yet in your initial configuration:
./mvnw quarkus:add-extension -Dextensions="io.quarkus:quarkus-minikube,io.quarkus:quarkus-kubernetes,io.quarkus:quarkus-container-image-jib"
Point docker commands to your Minikube installation. This is important, otherwise the local docker installation is used when building the application and Minikube won’t be able to locate the image:
minikube start
eval $(minikube -p minikube docker-env)
## running docker ps should now list the minikube docker container
Ahead of build the image, we can define the Kubernetes module name inside the application.properties:
quarkus.container-image.group=com-hkc-nlb
quarkus.container-image.name=bus-schedule-api
quarkus.kubernetes.name=bus-schedule-api
Trigger the image build:
./mvnw clean package -Dquarkus.container-image.build=true
docker images
# lists the new image
Finally, deploy to Minikube:
./mvnw clean package -Dquarkus.kubernetes.deploy=true
minikube service bus-schedule-api --url # show service url
If you like to checkout the kubernetes descriptors checkout target/kubernetes/minikube.yml or kubernetes.yml
---
apiVersion: v1
kind: Service
metadata:
annotations:
app.quarkus.io/build-timestamp: 2022-07-01 - 11:17:04 +0000
labels:
app.kubernetes.io/name: bus-schedule-api
app.kubernetes.io/version: 1.0.0-SNAPSHOT
name: bus-schedule-api
spec:
ports:
- name: http
nodePort: 30267
port: 80
targetPort: 8080
selector:
app.kubernetes.io/name: bus-schedule-api
app.kubernetes.io/version: 1.0.0-SNAPSHOT
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
app.quarkus.io/build-timestamp: 2022-07-01 - 11:17:04 +0000
labels:
app.kubernetes.io/name: bus-schedule-api
app.kubernetes.io/version: 1.0.0-SNAPSHOT
name: bus-schedule-api
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: bus-schedule-api
app.kubernetes.io/version: 1.0.0-SNAPSHOT
template:
metadata:
annotations:
app.quarkus.io/build-timestamp: 2022-07-01 - 11:17:04 +0000
labels:
app.kubernetes.io/name: bus-schedule-api
app.kubernetes.io/version: 1.0.0-SNAPSHOT
spec:
containers:
- env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: com-hkc-nlb/bus-schedule-api:1.0.0-SNAPSHOT
imagePullPolicy: IfNotPresent
name: bus-schedule-api
ports:
- containerPort: 8080
name: http
protocol: TCP.
Summary
Quarkus is an open source (Apache License version 2.0) java stack from Red Hat optimized to meet todays needs of distributed application architectures. Its strongest features are the tools to boost productivity and the performance like the interactive DEV UI or the deployment descriptor pre-generation. The first release of Quarkus was back in 2019. However since it builds on strong standards like the microprofile and even plays nice with spring-boot, I do expect it is here to stay.
An alternative framework is micronaut.io. It follows a similar goals and uses pre-computation to increase performance. It also supports native builds for GRAALVM.
Updates
2 July 2022
- update example usecase for code, added search for routes