Quarkus truly appears to be the smartest possible solution, in our experience.
The Java platform is an industry leader when it comes to portability and extensibility. It is also known for its support, maintainability and a rich set of libraries. With its fantastic features, Java is considered by many to be the premier enterprise programming language in the world.
As the software industry migrated to the cloud, the superiority of Java was questioned. Deploying fat war files to application servers is not fun. Fortunately, frameworks like Spring Boot came to the rescue. But still, even Spring Boot applications consume a lot of resources and take a long time to start up, important limitations for technologies like container orchestration and serverless.
The question now is — is it possible to write slim, light, fast boot time, low-memory footprint Java applications? The answer is a definitive yes, and the solution is Quarkus.
Quarkus is a full-stack, cloud-native Java framework developed by Red Hat. It supports Java Virtual Machine (JVM) as well as native compilation. Quarkus is based on MicroProfile standard and some Jakarta EE standards. It has faster startup times and requires less memory. Additionally, from the beginning, Quarkus was designed to be a container-first framework. Thus, it makes Java an effective platform for serverless applications, cloud and Kubernetes environments.
Quick recap of this blog series
The last blog post in this series detailed how we started our StoreFront application’s cloud-native journey to Red Hat OpenShift. If you have not checked our blog series introduction yet, be sure to see “Our Cloud Native Journey to Red Hat OpenShift Using Quarkus.”
In this blog post, I will discuss how we implemented the StoreFront application’s Java microservices using Quarkus. In case you are joining late, this blog series details my team’s experience building the StoreFront application microservices using Quarkus and deploying them to Red Hat Openshift using a GitOps framework:
If interested, you can find additional information about the StoreFront application and Java microservices architectures in the following resources:
- StoreFront Reference Application and its GitHub repositories
- Microservices architecture on IBM Architecture Center
You can also browse the Quarkus guides to become a Quarkus expert.
Quarkus features for our microservices
In the StoreFront application’s Java microservices, we implemented RESTful APIs, Configuration, Service Invocation, Resilience, Security and Monitoring using Quarkus:
In this blog post, we will explore the following features for two microservices of the StoreFront application:
- RESTful APIs: JAX-RS, CDI, JSON-B, JSON-P, Open API
- Configuration: Config
- Service Invocation: Rest Client
- Resilience: Fault Tolerance, Health
- Monitoring: Metrics, Open Tracing
Build StoreFront microservices using Quarkus
Let us consider the Inventory microservice and the Catalog microservice of the StoreFront application. The Inventory microservice returns a list of all the antique computing devices that are available. This microservice uses a MySQL database as its datasource. The Catalog microservice uses Elasticsearch and serves as cache to the Inventory microservice:
RESTful APIs
Take a look at the APIs defined for the Catalog microservice:
-
JAX-RS can be used in Quarkus by simply annotating resources with the
@Path
annotation. @path(“/micro/items”) specifies the base path in the URL. Requests for this service will be routed via this base path. -
GET /micro/items
uses the GET HTTP method to return the list of items available in the inventory. -
GET /micro/items/{id}
uses the GET HTTP method to return the item available in the inventory based upon the item id. -
@Produces
automates the serialization of JSON. The JSON output produced by your services is generated automatically by analyzing the objects returned by your services. It sets the Content-Type header on the response toapplication/json
.
Configuration
To configure a Quarkus application, we can use the application.properties
file as the configuration source.
The application.properties
file for the Catalog microservice contains the following values:
And here is the Java code that reads the configuration and assigns it:
This is one way of configuring the data. There are other ways to do so that you can find here.
Service Invocation
Now that we’ve defined the RESTful APIs to access the Catalog microservice and done the necessary configurations, let us now look at the code to invoke the Inventory microservice to retrieve the latest list of inventory items.
First, create an interface that represents the remote service using JAX-RS annotations. Declare the APIs as part of this interface and annotate them with the org.eclipse.microprofile.rest.client.inject.RegisterRestClient
annotation.
To handle exceptions, define a ResponseExceptionMapper. To register the provider, use the @RegisterProvider
annotation on the interface.
So, we saw the Catalog microservice API definition, and our next step is to examine how we use the REST client:
In order to use the Rest Client, we should use the javax.inject.Inject
and org.eclipse.microprofile.rest.client.inject.RestClient
annotations.
And finally, we should then configure the Rest Client by adding the name of the property in the application.properties file. For this, to name the property, we should prepend the fully qualified name of the interface to the /mp-rest/url
keyword:
Resilience
Quarkus implements all its fault tolerance policies using SmallRye Fault Tolerance, which is an implementation of the MicroProfile Fault Tolerance specification. The fault tolerance specification defines the following recovery procedures:
- Circuit breaker: Provides a fail-fast approach in case of overload or non-availability.
- Bulkhead: The workload is limited to a microservice to prevent failures caused by concurrency or service overload.
- Fallback: If the annotated method cannot be executed, it executes an alternative method.
- Retry policy: Specifies the conditions for retrying an unsuccessful execution.
- Timeout: Specifies the maximum execution time before interrupting a request.
Let’s look into the Catalog microservice and see how we added resilience to the service:
So in the code above, the getInventory
method retries up to two times for, at most, 2,000 seconds. Even with this configuration, the request may still fail sometimes. And when the request fails, it is important to respond with something meaningful. In order to do that, an alternative method — fallbackInventory
— is defined and this menthod will be executed whenever an exception raises.
Along with the fault tolerance, making sure if an instance of service is still functioning properly is equally critical. For this, Quarkus uses the SmallRye Health specification. Service mechanisms communicate their current status to the orchestration mechanism, which then allows the system to act accordingly. For this purpose, two dedicated endpoints — /health/ready
and /health/live
— are used.
For the Catalog microservice, Liveness health check is defined as follows:
And the definition for the Readiness health check is as follows:
Let us validate the Liveness and Readiness health checks as follows:
Monitoring
Quarkus allows us to monitor the operation of our microservices by using metrics and distributed tracing.
First, let us look into the metrics. To generate metrics, we use the quarkus-smallrye-metrics extension and this, in turn, implements the MicroProfile Metrics specification. These metrics are exposed at the /metrics
base path and are available in two different formats — one is the JSON format and the other is the Prometheus text format.
Below are the custom metrics defined in the Catalog service:
We can access the overall metrics on the /metrics
endpoint. These are basically categorized into three different scopes. If you want to access them individually, below are the endpoint details:
- Base metrics can be accessed at
/metrics/base
, and these metrics include JVM statistics like the current heap sizes, garbage collection times, thread counts and other OS and host system information. - Vendor metrics can be accessed at
/metrics/vendor
and these metrics include vendor-specific data like OSGi statistics. - Application metrics can be accessed at
/metrics/application
and these include custom metrics defined by the application developer.
Below is a list of custom metrics on the /metrics/application
endpoint for the Catalog microservice:
For distributed tracing, Quarkus uses the quarkus-smallrye-opentracing specification. This specification implements the OpenTracing API which, in turn, is based on Jaeger for distributed tracing.
To view the call traces, access the Jaeger UI. One of the traces for the Catalog microservice is as follows:
For more details on how the Catalog service is built, have a look here and for more details on how the Inventory service is built, have a look here.
Next steps
Quarkus allows us to quickly and easily create solutions. Quarkus is way ahead of the game, offering a huge list of extensions for database access, messaging, REST APIs and so on. We can, therefore, be assured that cloud-native Java is a viable choice. Also, for those who are developing microservices and want to deploy them on Kubernetes, Quarkus is a good choice because it seamlessly integrates with Kubernetes.
The exemplar microservices are simple and can be quickly implemented with just few lines of code. The source code is available on GitHub. For instructions on how to run it, please refer to the Readme.md file.
The Quarkus guides were a breeze; they are easy to understand and making use of them eased our path.
Throughout this blog, we demonstrated how we used Quarkus to implement RESTful APIs, Configurations, Invocations, Resilience and Monitoring for our StoreFront application. But this is just the beginning — Quarkus can do much more.
In the next blog post, we will cover authentication and application security using Keycloak. Meanwhile, stay tuned and take a look at our cloud native reference implementation available here.