Implementing Microservices with Java EE & Payara
For years, many companies have trusted in the Java EE platform to build their core applications because it offers them resiliency and security, among other characteristics. However, the Java EE platform has gotten the negative reputation of being a heavy-weight, complex framework, needing lots of configuration files, and being oriented towards monolithic applications. The architecture of microservices has changed the way we build our applications. The new trend is to build systems by creating many artifacts, or mini-applications. Each one focuses on a specific business logic, instead of the classic monolithic application with all the business logic in one application.
So here is the question: Can Java EE support lightweight, resilient applications based on a microservices architecture?
The answer is YES. Java EE is a good option for implementing microservices on our systems. We are going to see how it’s possible to create a microservice application using Java EE features and Payara Micro as the application server.
Why use Java EE for Microservices?
With Java EE the application server provides us with a stack of out-of-the-box technologies that we can use at any moment, so we can focus our efforts on the business logic from the beginning of our microservice. What’s more, all of its technologies follow the principle of Convention over Configuration, therefore, minimum to no settings are required to start coding. Finally, because the application server provides us with a stack of Java EE technologies, our resulting WAR file doesn’t have external or third party libraries (maybe just a few for specific needs) and its size will be minimal, usually less than 1mb. We don’t need to download half of the internet in dependencies just to create a small application (cough, cough, spring-framework, cough, cough).
Which Java EE Application Server Should I Use?
When it comes to Java EE application servers, their reputation is far from lightweight. Big, heavy monsters like Weblogic, Websphere and JBoss EAP come to mind. Their purpose is to deploy the traditional applications (that still exist and will continue to exist), but they aren’t an effective option for microservices. Fortunately, lightweight Java EE application servers like Tomee, WildFly and (my favorite) Payara, have appeared on the scene. Payara is a Java EE Application Server derived (forked) from Glassfish Server. When Oracle decided to stop giving commercial support to Glassfish Server, Payara was born. It’s called a Glassfish Server on steroids because, in addition to continuous bug fixes, it offers other features like:
- Automatic Clustering (via Hazelcast)
- Caching Tools
- Health Check Service
- and more…
Payara provides different profiles that can be used according to our needs:
- Payara Full: Provides the complete stack of technologies on the Java EE platform
- Payara Web: Provides the profile for developing web applications using Java EE
- Payara Micro: Provides a set of Java EE technologies oriented towards microservices development.
What does Payara Micro do?
Payara Micro is a JAR file that enables you to run WAR files from the command line without any installation. It’s designed to run Java EE applications in a modern containerized/virtualized infrastructure in the cloud. Some of Payara Micro’s main features:
- Less than 70mb in size and 30mb of memory when it’s running
- It comes with a stack of Java EE technologies:
- Servlets, JSP, Java Server Faces
- WebSockets
- JAX-RS (restful services), JSON-P
- CDI
- EJB lite, JTA, JPA
- Bean Validation, Interceptors, JBatch, JCache, Concurrency, etc
- Automatic clustering
- Command line, uber/fat jars, etc
The Payara Micro server will automatically cluster with other servers in the network. Your Java EE application can be elastically scaled horizontally by adding and removing containers based on demand.
How to use it?
As mentioned above, Payara Micro allows for WAR files to be run from the command line. It’s as simple as typing and executing the next sentence:
java -jar payara-micro.jar --deploy my_war.war
That sentence will run our web application on the Payara Micro server using the default configuration. However, it’s usually required to indicate custom settings according to the needs of our applications, so it’s possible to configure more features via the command line, for example:
--port sets the http port --sslPort sets the https port number --clusterName sets the Cluster Group Name --name sets the instance name --deploy specifies a war file to deploy --deploymentDir if set to a valid directory all war files in this directory will be deployed --domainConfig overrides the complete server configuration with an alternative domain.xml file --rootDir Sets the root configuration directory and saves the configuration across restarts --outputUberJar packages up an uber jar at the specified path based on the command line arguments and exits
There is also the option to start and configure the WAR programmatically.
Let’s Get Started
Now that we have read about some of the Java EE advantages for implementing microservices and an application server oriented towards microservices like Payara Micro, let’s make an example and see for ourselves how easy it is to build them. 1. Create a web application with Maven. We can check here that the only dependency we have is javaee-api and it’s provided. pom.xml
4.0.0 com.example devices 1.0 war devices javax javaee-web-api 7.0 provided
2. Creating the Device class. This is a model object to transfer information. Device.java
public class Device {
private int id;
private String name;
private String status;
private String type;
private String description;
public Device() {
}
//Getters and Setters
}
3. Creating some CDI managed bean classes to provide our business logic. SessionService.java
@Singleton
public class SessionService {
private final Set sessions = new HashSet<>();
public void addSession(Session session) {
sessions.add(session);
}
public void removeSession(Session session) {
sessions.remove(session);
}
public Set getSessions(){
return sessions;
}
}
DeviceService.java
@ApplicationScoped
public class DeviceService {
@Inject
private SessionService sessionService;
private final Set devices = new HashSet<>();
public List getDevices() {
return new ArrayList<>(devices);
}
public void addDevice(Device device) {
device.setId(new Random().nextInt());
devices.add(device);
JsonObject addMessage = createAddMessage(device);
sendToAllConnectedSessions(addMessage);
}
public void removeDevice(int id) {
Device device = getDeviceById(id);
if (device != null) {
devices.remove(device);
JsonProvider provider = JsonProvider.provider();
JsonObject removeMessage = provider.createObjectBuilder()
.add("action", "remove")
.add("id", id)
.build();
sendToAllConnectedSessions(removeMessage);
}
}
public void toggleDevice(int id) {
JsonProvider provider = JsonProvider.provider();
Device device = getDeviceById(id);
if (device != null) {
if ("On".equals(device.getStatus())) {
device.setStatus("Off");
} else {
device.setStatus("On");
}
JsonObject updateDevMessage = provider.createObjectBuilder()
.add("action", "toggle")
.add("id", device.getId())
.add("status", device.getStatus())
.build();
sendToAllConnectedSessions(updateDevMessage);
}
}
private Device getDeviceById(int id) {
for (Device device : devices) {
if (device.getId() == id) {
return device;
}
}
return null;
}
private JsonObject createAddMessage(Device device) {
JsonProvider provider = JsonProvider.provider();
JsonObject addMessage = provider.createObjectBuilder()
.add("action", "add")
.add("id", device.getId())
.add("name", device.getName())
.add("type", device.getType())
.add("status", device.getStatus())
.add("description", device.getDescription())
.build();
return addMessage;
}
private void sendToAllConnectedSessions(JsonObject message) {
for (Session session : sessionService.getSessions()) {
sendToSession(session, message);
}
}
private void sendToSession(Session session, JsonObject message) {
try {
session.getBasicRemote().sendText(message.toString());
} catch (IOException ex) {
sessionService.getSessions().remove(session);
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
}
protected void sendDevicesToSession(Session session){
for (Device device : this.getDevices()) {
JsonObject addMessage = createAddMessage(device);
sendToSession(session, addMessage);
}
}
4. Creating a WebSocket Endpoint using Java EE specification, to publish our logic and interact with the Javascript UI and the websocket client. To create a Web-socket endpoint in Java EE it is only necessary to create a simple class, annotate it with @ServerEndpoint, and create the methods to handle the websocket operations: onOpen, onClose, onError, onMessage. DeviceWebSocket.java
@ApplicationScoped
@ServerEndpoint("/actions")
public class DeviceWebSocket {
@Inject
private DeviceService deviceService;
@Inject
private SessionService sessionService;
@OnOpen
public void open(Session session){
sessionService.addSession(session);
}
@OnClose
public void close(Session session){
sessionService.removeSession(session);
}
@OnError
public void onError(Throwable error){
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, error);
}
@OnMessage
public void handleMessage(String message, Session session){
try (JsonReader reader = Json.createReader(new StringReader(message))) {
JsonObject jsonMessage = reader.readObject();
if ("add".equals(jsonMessage.getString("action"))) {
Device device = new Device();
device.setName(jsonMessage.getString("name"));
device.setDescription(jsonMessage.getString("description"));
device.setType(jsonMessage.getString("type"));
device.setStatus("Off");
deviceService.addDevice(device);
}
if ("remove".equals(jsonMessage.getString("action"))) {
int id = (int) jsonMessage.getInt("id");
deviceService.removeDevice(id);
}
if ("toggle".equals(jsonMessage.getString("action"))) {
int id = (int) jsonMessage.getInt("id");
deviceService.toggleDevice(id);
}
}
}
}
5. Creating a REST Service to publish our logic. DeviceRest.java
@RequestScoped
@Path("device")
public class DeviceRest {
@Inject
private DeviceService deviceService;
@GET
public List getDevices(){
return deviceService.getDevices();
}
}
6. The HTMLl, Javascript and CSS files used in this demo are placed in: https://github.com/adamgamboaGL/payara-micro-demo/tree/master/code/payarademo/src/main/webapp 7. Docker Images to deploy the demo application on a payara-micro server using Docker. This dockerfile inherits from a docker image that has a Linux SO and Java installed on it. Then it downloads and configures the payara-micro jar to run the demo application. Dockerfile: payara-micro
FROM adam/java
ENV PAYARA_ARCHIVE payara-micro
ENV INSTALL_DIR /opt
RUN curl -o ${INSTALL_DIR}/${PAYARA_ARCHIVE}.jar -L "https://s3-eu-west-1.amazonaws.com/payara.fish/Payara+Downloads/Payara+4.1.2.172/payara-micro-4.1.2.172.jar"
ENV PAYARA_HOME ${INSTALL_DIR}
ENV DEPLOYMENT_DIR ${PAYARA_HOME}
ENTRYPOINT java -jar ${PAYARA_ARCHIVE}.jar --deploy ${ARCHIVE_NAME}
WORKDIR ${INSTALL_DIR}
EXPOSE 8080 5900
This dockerfiles inherits from the payara-micro image and only deploys the demo application. Dockerfile: devices
FROM adam/payara-micro
MAINTAINER Adam Gamboa
COPY target/devices-1.0.war ${DEPLOYMENT_DIR}
ENV ARCHIVE_NAME devices-1.0.war
8. Finally, start to demo Java EE micro-service application (running in payara-micro on a Docker container). Compile the code.
mvn package
Build the docker image of payara-micro and the demo application.
docker build -t demo/devices .
Run the docker instance and start the demo application in the payara-micro server by using the following command line.
docker run -d -p 8080:8080 --name payara-demo demo/devices
Conclusions
The Java EE platform continues to evolve as a productive framework for application development and is flexible enough to support classic monolithic applications as well as microservices applications. In cloud environments like AWS, Oracle Cloud, Azure, etc, the bandwidth and size of the files are very important when an application is deployed. Using Java EE, we can have small WAR files for our microservices. What’s more, quoting the Java EE evangelist Adam Bien: “The smaller the WAR, the faster the deployment process.” And this can be extended, too. Having smaller WAR files also means less time and bandwidth when uploading your application to the cloud. Payara and Payara Micro seem to be good options for deploying microservices in the Java EE environment because of the features like automatic clustering, health check & monitoring services. If a company using Java EE wants to introduce a micro-service architecture they DO NOT need to change to other frameworks like “spring boot” and throw away all Java EE knowledge.
References