Should I use Quarkus
Welcome
As mentioned in a previous post, I’m working more with the RedHat stack.
Most of these tools are very interesting. Like Quarkus - another
Java framework. Not boring Java framework, Quarkus is fast, working on GralVM
instead of regular JVM. But the biggest advantage is native builds. We can create
an app, pack it into binary, and run it inside a container. That container could be used
with serverless without cold-start
issues. How cool is that? But…Today I will
be focused on regular OpenShift deployment and some coding.
Tools used in this episode
- Quarkus
- crc(OpenShift for workstations)
- httpie
- oc
Installing tools
I assume that you have some JDK or maybe even GralVM, I don’t know. But it will be nice to show something, even about that.
GralVM
Download
tar
with the latest release fromhttps://www.graalvm.org/downloads/
Unpack it
$ sudo mv graalvm-ce-java11-linux-amd64-20.2.0.tar.gz /opt $ cd /opt # I like have toools in /opt $ sudo tar -xvzf graalvm-ce-java11-linux-amd64-20.2.0.tar.gz
Edit your
PATH
export PATH=$JAVA_HOME/bin:$PATH export JAVA_HOME="/opt/graalvm-ce-java11-20.2.0"
Test it
$ java -version openjdk version "11.0.8" 2020-07-14 OpenJDK Runtime Environment GraalVM CE 20.2.0 (build 11.0.8+10-jvmci-20.2-b03) OpenJDK 64-Bit Server VM GraalVM CE 20.2.0 (build 11.0.8+10-jvmci-20.2-b03, mixed mode, sharing)
NOTE: Official docs
Maven
Download
tar
with the latest release fromhttps://maven.apache.org/download.cgi
Unpack it
$ sudo mv tar xzvf apache-maven-3.6.3-bin.tar.gz /opt $ cd /opt # I like have toools in /opt $ sudo tar -xvzf tar xzvf apache-maven-3.6.3-bin.tar.gz
Edit your
PATH
export PATH=$MVN_HOME/bin:$PATH export MVN_HOME="/opt/apache-maven-3.6.3"
Test it
$ mvn -v Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) Maven home: /opt/apache-maven-3.6.3 ...
NOTE: Official docs
CRC
WARNING: Accessing the CRC requires a Red Hat account.
Download tar from
https://developers.redhat.com/products/codeready-containers/overview
Unpack it
$ sudo mv tar xzvf crc-linux-amd64.tar.xz /opt $ cd /opt # I like have toools in /opt $ sudo tar -xvzf tar xzvf crc-linux-amd64.tar.xz
Link
crc
to/usr/local/bin/crc
sudo ln -sf /opt/crc/crc /usr/local/bin/crc
Setup crc
crc setup
NOTE: Official docs
Coding
OK, installing tools is boring. Maybe I need to prepare some Ansible or Bolt playbook for that. Fortunately, now we can focus on the more interesting part of this article.
Init project
mvn "io.quarkus:quarkus-maven-plugin:1.6.0.Final:create" \
-DprojectGroupId="dev.sky.users" \
-DprojectArtifactId="tutorial-app" \
-DprojectVersion="0.1" \
-DclassName="HelloResource" \
-Dpath="hello"
cd tutorial-app
Build it
We have a skeleton with UnitTests and dockerfiles. We can build a native app and pack it into a container(but the building process take some time)
./mvnw -DskipTests clean package \
-Pnative \
-Dquarkus.native.container-build=true \
-Dquarkus.native.container-runtime=podman # only if you're using Podman
Build container
Now we can build out the image
podman build -f src/main/docker/Dockerfile.native -t example/tutorial-app:0.1 .
$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/example/tutorial-app 0.1 97fd892c0412 4 seconds ago 137 MB
Run it
podman run -it --rm -p 8080:8080 example/tutorial-app:0.1
it was fast, isn’t it?
Test it
$ http :8080/hello
HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain;charset=UTF-8
hello
Do you want some tests? Run it
./mvnw clean test
Live coding - run and forget
That’s a very nice feature. We can run our app, modify it, and the application is reloaded after every saves. That makes development a bit faster than normal.
./mvnw quarkus:dev
Maybe add some basic configuration
OK, let’s add some stuff and open our code. Firstly let’s add some customized hello messages.
// src/main/java/com/redhat/developers/HelloResource.java
package dev.sky.users;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@Path("/hello")
public class HelloResource {
@ConfigProperty(name = "greeting")
String greeting;
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return greeting;
}
}
Add message to aplication.properties
# src/main/resources/application.properties
greeting=Hello from the dark side!
Test it manually again
$ http :8080/hello
HTTP/1.1 200 OK
Content-Length: 25
Content-Type: text/plain;charset=UTF-8
Hello from the dark side!
Add unittesting
As I said we have some out-of-box tests. But after the change, we need to modify it.
// src/test/java/com/redhat/developers/HelloResourceTest.java
package dev.sky.users;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class HelloResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("Hello from the dark side!"));
}
}
Run tests and observe
./mvnw clean test
Add some DB’s stuff
Let’s add some CRUD functions. It’s nice to make something more than greet. In the beginning, we need some new extensions.
./mvnw quarkus:add-extension -Dextension="quarkus-resteasy-jsonb, quarkus-jdbc-h2, quarkus-hibernate-orm-panache, quarkus-smallrye-openapi"
Add some H2 config
For this tutorial, we’re using H2 database. So it’s work only in non-container environment.
# src/main/resources/application.properties
...
quarkus.datasource.jdbc.url=jdbc:h2:mem:default
quarkus.datasource.db-kind=h2
quarkus.hibernate-orm.database.generation=drop-and-create
Add new fruit Entity
We nice some class to represent our Fruit object.
// src/main/java/com/redhat/developers/Fruit.java
package dev.sky.users;
import javax.persistence.Entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
@Entity
public class Fruit extends PanacheEntity {
public String name;
public String season;
}
We’re using PanacheEntity so we don’t need any Getter
or Setter
!
Add FruitResource
If we already have our sweet class we need to handle HTTP calls.
// src/main/java/com/redhat/developers/FruitResource.java
package dev.sky.users;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/fruit")
public class FruitResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Fruit> fruits() {
return Fruit.listAll();
}
}
Now let’s test our new endpoint
$ http :8080/fruit
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
[]
As we can expect we get an empty list. We didn’t provide any data, yet.
Add some POST endpoint in our FruitResource.java
But we want to add some data. Let’s start with POST request handling.
// src/main/java/com/redhat/developers/FruitResource.java
package dev.sky.users;
import java.util.List;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@Path("/fruit")
public class FruitResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Fruit> fruits() {
return Fruit.listAll();
}
@Transactional
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response newFruit(Fruit fruit) {
fruit.id = null;
fruit.persist();
return Response.status(Status.CREATED).entity(fruit).build();
}
}
Add some fruit
When our app support POST requests, we can send some data.
$ http POST :8080/fruit \
Content-Type:application/json \
name="Banan" \
season="Summer"
Check it
$ http :8080/fruit
HTTP/1.1 200 OK
Content-Length: 44
Content-Type: application/json
[
{
"id": 1,
"name": "Banana",
"season": "Summer"
}
]
Do you want some instant testing data? Add some sql’s
-- src/main/resources/import.sql
INSERT INTO Fruit(id,name,season) VALUES (nextval('hibernate_sequence'),'Mango','Spring');
INSERT INTO Fruit(id,name,season) VALUES (nextval('hibernate_sequence'),'Strawberry','Spring');
INSERT INTO Fruit(id,name,season) VALUES (nextval('hibernate_sequence'),'Orange','Winter');
INSERT INTO Fruit(id,name,season) VALUES (nextval('hibernate_sequence'),'Lemon','Winter');
INSERT INTO Fruit(id,name,season) VALUES (nextval('hibernate_sequence'),'Blueberry','Summer');
INSERT INTO Fruit(id,name,season) VALUES (nextval('hibernate_sequence'),'Banana','Summer');
INSERT INTO Fruit(id,name,season) VALUES (nextval('hibernate_sequence'),'Watermelon','Summer');
INSERT INTO Fruit(id,name,season) VALUES (nextval('hibernate_sequence'),'Apple','Fall');
INSERT INTO Fruit(id,name,season) VALUES (nextval('hibernate_sequence'),'Pear','Fall');
Now just restart your app - and results will be the following:
$ http :8080/fruit
HTTP/1.1 200 OK
Content-Length: 390
Content-Type: application/json
[
{
"id": 1,
"name": "Mango",
"season": "Spring"
},
{
"id": 2,
"name": "Strawberry",
"season": "Spring"
},
...
Add custom finder
OK, that was easy. Let’s complicate logic and add some filters to our app.
package dev.sky.users;
import java.util.List;
import javax.persistence.Entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
@Entity
public class Fruit extends PanacheEntity {
public String name;
public String season;
public static List<Fruit> findBySeason(String season) {
return find("season", season).list();
}
}
Update Endpoint to return QueryParam
package dev.sky.users;
import java.util.List;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@Path("/fruit")
public class FruitResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Fruit> fruits(@QueryParam("season") String season) {
if (season != null) {
return Fruit.findBySeason(season);
}
return Fruit.listAll();
}
@Transactional
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response newFruit(Fruit fruit) {
fruit.id = null;
fruit.persist();
return Response.status(Status.CREATED).entity(fruit).build();
}
}
And the result is
$ http :8080/fruit \
season==Summer
HTTP/1.1 200 OK
Content-Length: 137
Content-Type: application/json
[
{
"id": 5,
"name": "Blueberry",
"season": "Summer"
},
{
"id": 6,
"name": "Banana",
"season": "Summer"
},
{
"id": 7,
"name": "Watermelon",
"season": "Summer"
}
]
Deploy the app
I need to notice, that H2 not working in containers. Maybe in another articule, I will add Postgresql DB.
Prepper the app - add some extension
The best thing here is that we need to add only one extension.
./mvnw quarkus:add-extension -Dextensions="openshift"
Make sure that crc is running
crc status
# and
oc login -u kubeadmin -p <your token> https://api.crc.testing:6443
oc new-project quarkus
One command to deploy your app
./mvnw clean package -Dquarkus.kubernetes.deploy=true
Wow, that was great, we got ImageStream
, DeploymentConfig
,
Service
and Repicacontrolers
. In on command, without any YAML.
Test the app
oc expose svc/tutorial-app
# save route variable
HOST=$(oc get route tutorial-app -n default --template='{{ .spec.host }}')
# Test the app
$ http $HOST/hello
HTTP/1.1 200 OK
cache-control: private
content-length: 25
content-type: text/plain;charset=UTF-8
set-cookie: ff25dce5274413fb672a5a430cbde1ab=49a44cac2ad73eb3031d1718e1137501; path=/; HttpOnly
Hello from the dark side!
As you can see we build an app with unittests, deploy it to OpenShift without YAMLs. If we want to be really fast and skip playing with Quarkus, we can just type:
mvn "io.quarkus:quarkus-maven-plugin:1.6.0.Final:create" \
-DprojectGroupId="dev.sky.users" \
-DprojectArtifactId="tutorial-app" \
-DprojectVersion="0.1" \
-DclassName="HelloResource" \
-Dpath="hello"
cd tutorial-app
./mvnw quarkus:add-extension -Dextensions="openshift"
./mvnw clean package -Dquarkus.kubernetes.deploy=true
oc expose svc/tutorial-app
Of course, we need to have Maven and configured access to OCP cluster with oc. But in general, it could be fast and really impressive for presentation purpose.
Clean app
oc delete project quarkus
crc stop
Summary
This is an article based on quarkus-tutorial. I fix some errors, skip the Spring parts, and focus on the deployment part.
Instruction present in the article not working correctly for some reason, also some plugins versions are outdated.
For me, in general, it was a very interesting read. Which pushed me to write my own post with some fixes. What about Quarkus?
I will try to work more with this ecosystem. I’m not a Java programmer, but GralVM and all this native
stuff look
pretty awesome. Especially If I need to make some PoC or demo, just imagine the situation. You run 4 commands, without
any code writing, fixing etc. After that, you get a working app with one endpoint. Ready to show processes, pipelines etc.
And that’s very useful for me. Before that, I just copy my basic Golang app based on Echo framework, but as you know I need
to prepare some deployment steps. Now, something will do it for me. That means more time for gam… family :) Take care,
the next article will be about K8S with Linode.