45 MINUTE EXERCISE
In this lab you will learn about building microservices using Thorntail.
Java EE applications are traditionally created as an ear or war archive including all dependencies and deployed in an application server. Multiple Java EE applications can and were typically deployed in the same application server. This model is well understood in development teams and has been used over the past several years.
Thorntail offers an innovative approach to packaging and running Java EE applications by
packaging them with just enough of the Java EE server runtime to be able to run them directly
on the JVM using *java -jar
. For more details on various approaches to packaging Java
applications, read this blog post.
Thorntail is based on WildFly and it’s compatible with MicroProfile, which is a community effort to standardize the subset of Java EE standards such as JAX-RS, CDI and JSON-P that are useful for building microservices applications.
Since Thorntail is based on Java EE standards, it significantly simplifies refactoring existing Java EE applications to microservices and allows much of the existing code-base to be reused in the new services.
The inventory-thorntail project has the following structure which shows the components of the Thorntail project laid out in different subdirectories according to Maven best practices:
This is a minimal Java EE project with support for JAX-RS for building RESTful services and JPA for connecting to a database. JAX-RS is one of Java EE standards that uses Java annotations to simplify the development of RESTful web services. Java Persistence API (JPA) is another Java EE standard that provides Java developers with an object/relational mapping facility for managing relational data in Java applications.
This project currently contains no code other than the main class for exposing a single RESTful application defined in InventoryApplication.
Examine 'com.redhat.cloudnative.inventory.InventoryApplication' class
in the /projects/labs/inventory-thorntail/src/main/java directory:
package com.redhat.cloudnative.inventory;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/api")
public class InventoryApplication extends Application {
}
Run the Maven build to make sure the skeleton project builds successfully. You should get a BUILD SUCCESS message in the build logs, otherwise the build has failed.
In CodeReady Workspaces, right-click on 'inventory-thorntail'
project in the project explorer then, click on 'Commands > Build > build'
Once built successfully, the resulting jar is located in the target/ directory:
$ ls /projects/labs/inventory-thorntail/target/*-thorntail.jar /projects/labs/inventory-thorntail/target/inventory-1.0-SNAPSHOT-thorntail.jar
This is an uber-jar with all the dependencies required packaged in the jar to enable running the application with java -jar. Thorntail also creates a war packaging as a standard Java EE web app that could be deployed to any Java EE app server (for example, JBoss EAP, or its upstream WildFly project).
Now let’s write some code and create a domain model and a RESTful endpoint to create the Inventory service:
Create a new Java class named Inventory in com.redhat.cloudnative.inventory package with the below code and following fields: itemId and quantity
In the project explorer in CodeReady Workspaces, right-click on 'inventory-thorntail > src > main > java > com.redhat.cloudnative.inventory'
and then click on 'New > Java Class'
. Enter 'Inventory'
as the Java class name.
package com.redhat.cloudnative.inventory;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import java.io.Serializable;
@Entity // (1)
@Table(name = "INVENTORY", uniqueConstraints = @UniqueConstraint(columnNames = "itemId")) // (2)
public class Inventory implements Serializable {
@Id // (3)
private String itemId;
private int quantity;
public Inventory() {
}
public String getItemId() {
return itemId;
}
public void setItemId(String itemId) {
this.itemId = itemId;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
@Override
public String toString() {
return "Inventory [itemId='" + itemId + '\'' + ", quantity=" + quantity + ']';
}
}
-
@Entity marks the class as a JPA entity
-
@Table customizes the table creation process by defining a table name and database constraint
-
@Id marks the primary key for the table
Note
|
You don’t need to press a save button! CodeReady Workspaces automatically saves the changes made to the files. |
Thorntail configuration is done to a large extent through detecting the intent of the developer and automatically adding the required dependencies configurations to make sure it can get out of the way and developers can be productive with their code rather than Googling for configuration snippets. As an example, configuration database access with JPA is composed of the following steps:
-
The io.thorntail:jpa dependency to pom.xml
-
The database driver (e.g. org.postgresql:postgresql) to pom.xml
-
The database connection details in /projects/labs/inventory-thorntail/src/main/resources/project-default.yml
Edit the 'pom.xml' file
and add the io.thorntail:jpa dependency to enable JPA:
[...]
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.thorntail</groupId>
<artifactId>jpa</artifactId>
</dependency>
<dependency>
<groupId>io.thorntail</groupId>
<artifactId>jaxrs-jsonp</artifactId>
</dependency>
<dependency>
<groupId>io.thorntail</groupId>
<artifactId>cdi</artifactId>
</dependency>
[...]
</dependencies>
<build>
[...]
Examine 'src/main/resources/META-INF/persistence.xml'
to see the JPA datasource configuration
for this project. Also note that the configurations uses META-INF/load.sql to import
initial data into the database.
Examine 'src/main/resources/project-default.yml'
to see the database connection details.
An in-memory H2 database is used in this lab for local development and in the following
labs will be replaced with a PostgreSQL database. Be patient! More on that later.
Thorntail uses JAX-RS standard for building REST services. In the project explorer in CodeReady Workspaces, right-click on 'inventory-thorntail > src > main > java > com.redhat.cloudnative.inventory'
and then click on 'New > Java Class'
. Enter 'InventoryResource'
as the Java class name.
package com.redhat.cloudnative.inventory;
import javax.enterprise.context.ApplicationScoped;
import javax.persistence.*;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
@Path("/inventory")
@ApplicationScoped
public class InventoryResource {
@PersistenceContext(unitName = "InventoryPU")
private EntityManager em;
@GET
@Path("/{itemId}")
@Produces(MediaType.APPLICATION_JSON)
public Inventory getAvailability(@PathParam("itemId") String itemId) {
Inventory inventory = em.find(Inventory.class, itemId);
return inventory;
}
}
The above REST service defines an endpoint that is accessible via HTTP GET at for example /api/inventory/329299 with the last path param being the product id which we want to check its inventory status.
Build and package the Inventory Service using Maven by right clicking on 'inventory-thorntail'
project in the project explorer
then, click on 'Commands > Build > build'
Important
|
Make sure inventory-thorntail project is highlighted in the project explorer |
Using CodeReady Workspaces and Thorntail maven plugin, you can conveniently run the application directly in the IDE and test it before deploying it on OpenShift.
In CodeReady Workspaces, click on the run icon and then on 'thorntail:run'
.
Tip
|
You can also run the Inventory Service in CodeReady Workspaces using the Commands Palette and then RUN > thorntail:run |
Once you see Thorntail is Ready in the logs, the Inventory Service is up and running and you can access the
inventory REST API. Let’s test it out using curl
in the Terminal window:
$ curl http://localhost:9001/api/inventory/329299 {"itemId":"329299","quantity":35}
You can also use the preview url that CodeReady Workspaces has generated for you to be able to test the service that is now running in the workspace directly in the browser. Append the path /api/inventory/329299 at the end of the preview url and try it in your browser in a new tab.
The REST API returned a JSON object representing the inventory count for this product. Congratulations!
In CodeReady Workspaces, stop the Inventory service by clicking on the run thorntail item in the Machines window. Then click the stop icon that appears next to run thorntail.
It’s time to build and deploy our service on OpenShift.
OpenShift {{OPENSHIFT_DOCS_BASE}}/architecture/core_concepts/builds_and_image_streams.html#source-build[Source-to-Image (S2I)^] feature can be used to build a container image from your project. OpenShift S2I uses the supported OpenJDK container image to build the final container image of the Inventory service by uploading the Thorntail uber-jar from the target folder to the OpenShift platform.
Maven projects can use the Fabric8 Maven Plugin to access the OpenShift S2I for building the application container image. This maven plugin is a Kubernetes/OpenShift client and uses the REST API to communicate with OpenShift and issue commands to build, deploy and launch the application as a pod.
To build and deploy the Inventory Service on OpenShift using the fabric8 maven plugin,
which is already configured in CodeReady Workspaces, right click on 'inventory-thorntail'
project in the project explorer
then, click on 'Commands > Deploy > fabric8:deploy'
Tip
|
fabric8:deploy
It will cause the following to happen:
|
Once this completes, your project should be up and running. OpenShift runs the different components of the project in one or more pods which are the unit of runtime deployment and consists of the running containers for the project.
Let’s take a moment and review the OpenShift resources that are created for the Inventory REST API:
-
Build Config: inventory-s2i build config is the configuration for building the Inventory container image from the inventory source code or JAR archive
-
Image Stream: inventory image stream is the virtual view of all inventory container images built and pushed to the OpenShift integrated registry.
-
Deployment Config: inventory deployment config deploys and redeploys the Inventory container image whenever a new Inventory container image becomes available
-
Service: inventory service is an internal load balancer which identifies a set of pods (containers) in order to proxy the connections it receives to them. Backing pods can be added to or removed from a service arbitrarily while the service remains consistently available, enabling anything that depends on the service to refer to it at a consistent address (service name or IP).
-
Route: inventory route registers the service on the built-in external load-balancer and assigns a public DNS name to it so that it can be reached from outside OpenShift cluster.
You can review the above resources in the {{OPENSHIFT_CONSOLE_URL}}[OpenShift Web Console^] or using oc describe command:
Tip
|
bc is the short-form of buildconfig and can be interchangeably used instead of it with the OpenShift CLI. The same goes for is instead of imagestream`, dc instead of deploymentconfig and svc instead of service`. |
$ oc describe bc inventory-s2i $ oc describe is inventory $ oc describe dc inventory $ oc describe svc inventory $ oc describe route inventory
You can see the exposed DNS url for the Inventory service in the {{OPENSHIFT_CONSOLE_URL}}[OpenShift Web Console^] or using OpenShift CLI:
$ oc get routes NAME HOST/PORT PATH SERVICES PORT TERMINATION inventory inventory-{{COOLSTORE_PROJECT}}.{{APPS_HOSTNAME_SUFFIX}} inventory 8080 None
Click on the OpenShift Route of 'Inventory Service'
from the {{OPENSHIFT_CONSOLE_URL}}[OpenShift Web Console^].
Then click on 'Test it'
. You should have the following output:
{"itemId":"329299","quantity":35}
Well done! You are ready to move on to the next lab.