Programming Styles Compared: Spring Framework vis-a-vis Eclipse MicroProfile Part 1
A developer takes a comparative look into the Spring and MicroProfile frameworks for microservices development.
Join the DZone community and get the full member experience.
Join For FreeIntroduction
In this article series, we will compare two open source microservices frameworks, Spring and Eclipse MicroProfile, for developing microservices. A simple microservice application will be developed twice, based on the same class design, once via the Spring framework and once via the Eclipse MicroProfile with Open Liberty runtime. Those separate versions of the same application will be built with Maven to create individual Docker images. We will then execute and test each version separately. With this exercise, our goal is to highlight some of the similarities and differences between the two technologies in terms of basic configuration, object annotations, build/execution instructions, and commands to create Docker images.
Microservices is an architectural style to develop component-based software applications tailored around business capabilities. A microservice is a small unit of application code and related configuration that can be quickly developed, tested, and moved to production. Below are some of the basic features of microservices. (Also see this article and this knowledge resource.)
Microservices can be brought from concept to production quickly.
Microservices provide visibility and transparency to their intrinsic state so that their application state and health can be easily monitored via external tools, e.g. Spring Boot Actuator.
Microservices support configuration via an external, centralized configuration service, e.g. Spring Cloud Configuration Server.
The development and maintenance philosophy behind a microservices architecture promotes the ‘build-it/run-it’ DevOps paradigm.
Microservices can register themselves with a service registry, e.g. Netflix Eureka so that they can be searched and located by their clients via discovery services.
Multiple instances of a microservice can be deployed to support fault tolerance. A microservice architecture enables client-side load balancing, e.g. using Netflix Ribbon, for clients of a microservice to access multiple instances of the microservice in a load-balanced manner. A microservice itself could utilize client-side load balancing while accessing other services it has a dependency on.
In addition, microservice architectures support:
integration with cluster configuration and coordination services, e.g. Apache ZooKeeper, for managing microservices in a clustered environment.
synchronization of message distribution and processing to allow asynchronous communication between microservices, e.g. via Spring Integration.
commonly used authentication and authorization technologies such as OpenID, OAuth, and SAML.
Spring is a popular open source framework to develop web services using the Java language. It mainly consists of a core container system with various add-on modules that provide programming services such as security, data access, messaging, and transaction management, to name a few. The Spring framework can be effectively utilized to develop microservices using Spring Boot and Spring Cloud, two sub-projects of the Spring framework. (Although a little dated, this tutorial and this discussion are still relevant.)
Eclipse MicroProfile is an Eclipse open-source project to define a programming model for developing microservice applications in an Enterprise Java environment. One of the main goals of the project is to ensure portability across different vendor runtimes of the applications built according to the Eclipse MicroProfile APIs. Another related focus of the project is to ensure the interoperability of those applications between different vendor runtimes. In this article, we will utilize IBM’s Open Liberty application server as the Eclipse MicroProfile runtime (see the project website for a list of vendor environments supporting Eclipse MicroProfile).
This article series is organized as follows. The next section, 'Class and Data Models,' describes the design model for a simple application that will be developed separately using Spring and Eclipse MicroProfile frameworks. The following section, 'Code Review,' gives a review of the Java code and configuration files. Here, we also review the Docker files for assembling the respective Docker images. That section is followed by Part 2, 'Running the Application,' where we demonstrate how to run and test each application. We then end the series, and Part 2, with 'Conclusions.'
The files discussed in this article can be downloaded from GitHub.
Class and Data Models
The Inventory Application is a web service with three operations regarding car inventories.
View existing car inventory.
Create new car inventory.
Update existing car inventory.
The application has a simple class model represented by the following UML diagram.
Figure. Class Model.
A rest package is the entry point for web service calls and consists of InventoryApplication, which performs basic application configuration, and InventoryResource, which generates the response for various REST (Representational State Transfer) calls.
The dao (data access objects) and dao.entities packages provide functionality for the data access layer. InventoryEntity is an object representation of an inventory record in a database table and InventoryRepository specifies methods to create, view, and update inventory records (an inventory record consists of a car brand and an integer indicating the inventory, e.g. {"brand":"BMW","inventory":"15"}
).
InventoryService in the service package serves as an intermediary between the rest and dao packages and interfaces with InventoryRepository to create, view, and update inventory records according to the requests coming from its client, InventoryResource.
The model package consists of Inventory, that has similar attributes to InventoryEntity. However, while InventoryEntity has persistence awareness, InventoryEntity is a plain Java bean.
The rest package both accepts input data and generates output data in JSON (JavaScript Object Notation) format. Data input from the web service calls are mapped to Inventory objects in the rest package and they are converted to InventoryEntity objects in the service package while forwarding the requests to the dao package. Conversely, a response from the dao package in the form of InventoryEntity objects is transformed back to a response in the form of Inventory objects in the service package.
The simple pattern above can be seen as an implementation of the model and controller components in the Model-View-Controller architectural style. We omit the View component as we assume it can be implemented separately via JavaScript and Dynamic HTML for execution in a web browser.
Please note that the above design is not intended to prescribe a pattern to develop microservices. It is simply a baseline to code the same inventory application using two distinct frameworks and then compare them with each other. Also, for simplicity, the design does not take into account some other important aspects of microservices such as health checks, service registry, and fault tolerance.
Data Model
The data model is very simple; it consists of a relational database table called CAR with two columns:
brand VARCHAR(25) PRIMARY KEY
inventory INT
Technology Components
We will create two separate implementations of the design. In one, we will use the Spring framework. In the other one, we will use the Eclipse MicroProfile. The main components of the Spring application are as follows:
Spring Boot 2.1.2
Spring Cloud Greenwich
Spring Data JPA 2.1.4
Spring ORM 5.1.4
The main components of the Eclipse MicroProfile application are as follows:
- Open Liberty MicroProfile API 2.1
JAX-RS 2.1
JSONP 1.1
CDI 2.0
JPA 2.2
Open Liberty Runtime 19.0.0.1
Both applications will use Derby Network Database version 10.14.2.0 with client version 10.10.1.1. Each application will be converted to a Docker image to be executed in Docker engine version 18.09.2 in MacOS environment.
Code Review
Following the design model above, the folder structure for the Spring application is as follows.
- Dockerfile
- pom.xml
- src
-- main
--- java
------- org
--------- springexamples
-------------- inventories
------------------------ dao
-------------------------- InventoryRepository.java
-------------------------- entities
---------------------------- InventoryEntity.java
------------------------ model
-------------------------- Inventory.java
------------------------ rest
-------------------------- InventoryApplication.java
-------------------------- InventoryResource.java
------------------------ service
-------------------------- InventoryService.java
--- resources
------ inventory-application.yaml
------ logback.xml
Similarly, the folder structure for the Eclipse MicroProfile application is as follows:
- Dockerfile
- pom.xml
- src
-- main
--- java
------- org
--------- olpexamples
-------------- inventories
------------------------ dao
-------------------------- InventoryRepository.java
-------------------------- entities
---------------------------- InventoryEntity.java
------------------------ model
-------------------------- Inventory.java
------------------------ rest
-------------------------- InventoryApplication.java
-------------------------- InventoryResource.java
------------------------ service
-------------------------- InventoryService.java
--- resources
------ META-INF
-------- beans.xml
-------- persistence.xml
--- webapp
------ WEB-INF
-------- web.xml
--- liberty
------ config
-------- server.xml
For both Spring and Eclipse MicroProfile applications, the package structure has been defined with the following in mind.
The dao package represents the database access layer.
The model package encapsulates beans that represent conceptual models of the application.
The service package encapsulates objects that perform service operations. This layer has the awareness of both the dao and model layers.
The rest package encapsulates the application entry point and the controller object that translates REST calls to commands to be performed by the service.
Java Code
InventoryEntity.java
This file represents an entity class corresponding to the CAR table. The code for Spring and Eclipse MicroProfile are very similar except that in the case of Eclipse MicroProfile we explicitly define the named queries findAll
and findById
. In the case of Spring, we will rely on the Spring Data JPA framework for the implicit definition of those queries.
Spring
package org.springexamples.inventories.dao.entities;
import java.io.Serializable;
import javax.persistence.*;
@Entity@Table(name = "car")
public class InventoryEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(nullable = false, length = 25, name = "brand")
protected String brand;
@Column(nullable = false, name = "inventory")
protected Integer inventory;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Integer getInventory() {
return inventory;
}
public void setInventory(Integer inventory) {
this.inventory = inventory; }
}
Eclipse MicroProfile
package org.olpexamples.inventories.dao.entities;
import java.io.Serializable;
import javax.persistence.*;
@Entity@Table(name = "car")
@NamedQueries(
{@NamedQuery(name = "InventoryEntity.findAll",
query = "SELECT e FROM InventoryEntity e"),
@NamedQuery(name = "InventoryEntity.findById",
query = "SELECT e FROM InventoryEntity e WHERE e.brand = :brand")}
)
public class InventoryEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(nullable = false, length = 25, name = "brand")
protected String brand;
@Column(nullable = false, name = "inventory")
protected Integer inventory;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Integer getInventory() {
return inventory;
}
public void setInventory(Integer inventory) {
this.inventory = inventory;
}
}
InventoryRepository.java
package org.olpexamples.inventories.dao.entities;
import java.io.Serializable;
import javax.persistence.*;
@Entity@Table(name = "car")
@NamedQueries(
{@NamedQuery(name = "InventoryEntity.findAll",
query = "SELECT e FROM InventoryEntity e"),
@NamedQuery(name = "InventoryEntity.findById",
query = "SELECT e FROM InventoryEntity e WHERE e.brand = :brand")}
)
public class InventoryEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(nullable = false, length = 25, name = "brand")
protected String brand;
@Column(nullable = false, name = "inventory")
protected Integer inventory;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Integer getInventory() {
return inventory;
}
public void setInventory(Integer inventory) {
this.inventory = inventory;
}
}
This class encapsulates operations against the CAR table, for finding all inventories, creating a new inventory, and updating an existing inventory.
In the case of our Spring application, this is an interface extending JpaRepository
in the Spring Data JPA framework. Because the JpaRepository
takes care of all the operations behind the scenes, we don't need to define any methods. In particular, to insert or update an inventory record, the JpaRepository
will provide a built-in save
method (see usage in InventoryService.java below).
In the case of Eclipse MicroProfile application, this class is custom built using javax.persistence.EntityManager. The setInventory
method inserts a new record or updates an existing record. The findAll
method returns all records whereas getExisting
finds and returns an existing inventory record.
Spring
package org.springexamples.inventories.dao;
import org.springexamples.inventories.dao.entities.InventoryEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@Transactional
public interface InventoryRepository extends
JpaRepository<InventoryEntity, Integer> {
}
Eclipse MicroProfile
package org.olpexamples.inventories.dao;
import org.olpexamples.inventories.dao.entities.InventoryEntity;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import java.util.List;
public class InventoryRepository {
@PersistenceContext
private EntityManager em;
public void setInventory(InventoryEntity inventory){
em.persist(inventory);
}
public List<InventoryEntity> findAll(){
return em.createNamedQuery("InventoryEntity.findAll",
InventoryEntity.class).getResultList();
}
public InventoryEntity getExisting(String brand){
try{
InventoryEntity existing =
em.createNamedQuery("InventoryEntity.findById",
InventoryEntity.class).
setParameter("brand", brand).getSingleResult();
return existing;
}catch(NoResultException e){
return null;
}
}
}
Inventory.java
This class represents a car inventory model. It has similar attributes to the InventoryEntity
class. For Spring and Eclipse MicroProfile applications this class has an identical implementation (except for the package definition).
Spring
package org.springexamples.inventories.model;
import javax.validation.constraints.NotNull;
public class Inventory {
@NotNull
private String brand;
private Integer inventory;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Integer getInventory() {
return inventory;
}
public void setInventory(Integer inventory) {
this.inventory = inventory;
}
}
Eclipse MicroProfile
package org.olpexamples.inventories.model;
import javax.validation.constraints.NotNull;
public class Inventory {
@NotNull
private String brand;
private Integer inventory;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Integer getInventory() {
return inventory;
}
public void setInventory(Integer inventory) {
this.inventory = inventory;
}
}
InventoryService.java
This class performs service layer operations on car inventories by invoking appropriate methods on InventoryRepository
. It carries out necessary transformations between the Inventory
and InventoryEntity
classes. The rest package directly interfaces with this class rather than InventoryRepository
. This class has the awareness of both Inventory
and InventoryEntity
classes and provides transformations between them.
The getAllInventories
method returns all inventories in the database and has an identical implementation in Spring and Eclipse MicroProfile applications. Similarly, transformToInventoryEntity
and transformToInventory
methods, which provide transformations between Inventory
and InventoryEntity
classes, are identical.
The only notable difference is with the setInventory
method, which defines inventory for a particular car brand. In the case of Spring, we call save
on InventoryRepository
, a built-in method supplied by JpaRepository. In the case of Eclipse MicroProfile, following our custom implementation, we try to obtain an existing inventory of the brand and if exists, we update it. Otherwise, we persist a brand new inventory.
Spring
package org.springexamples.inventories.service;
import org.springexamples.inventories.dao.InventoryRepository;
import org.springexamples.inventories.dao.entities.InventoryEntity;
import org.springexamples.inventories.model.Inventory;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
import java.util.stream.Collectors;
@Service
public class InventoryService {
protected InventoryRepository inventoryRepository;
@Autowired
public InventoryService(InventoryRepository inventoryRepository){
this.inventoryRepository = inventoryRepository;
}
public Collection<Inventory> getAllInventories(){
return inventoryRepository.findAll().stream()
.map(this::transformToInventory).collect(Collectors.toList());
}
public void setInventory(Inventory inventory){
inventoryRepository.save(transformToInventoryEntity(inventory));
}
private InventoryEntity transformToInventoryEntity(Inventory inventory){
InventoryEntity e = new InventoryEntity();
e.setBrand(inventory.getBrand());
e.setInventory(inventory.getInventory());
return e;
}
private Inventory transformToInventory(InventoryEntity entity){
Inventory inventory = new Inventory();
inventory.setBrand(entity.getBrand());
inventory.setInventory(entity.getInventory());
return inventory;
}
}
Eclipse MicroProfile
package org.olpexamples.inventories.service;
import org.olpexamples.inventories.dao.entities.InventoryEntity;
import org.olpexamples.inventories.model.Inventory;
import org.olpexamples.inventories.dao.InventoryRepository;
import javax.inject.Inject;
import java.util.Collection;
import java.util.stream.Collectors;
public class InventoryService {
@Inject
protected InventoryRepository inventoryRepository;
public Collection<Inventory> getAllInventories(){
return inventoryRepository.findAll().stream()
.map(this::transformToInventory).collect(Collectors.toList());
}
public void setInventory(Inventory inventory){
InventoryEntity existingEntity = inventoryRepository
.getExisting(inventory.getBrand());
if(existingEntity == null){
inventoryRepository
.setInventory(transformToInventoryEntity(inventory));
}else{
existingEntity.setInventory(inventory.getInventory());
inventoryRepository.setInventory(existingEntity);
}
}
private InventoryEntity transformToInventoryEntity(Inventory inventory){
InventoryEntity e = new InventoryEntity();
e.setBrand(inventory.getBrand());
e.setInventory(inventory.getInventory());
return e;
}
private Inventory transformToInventory(InventoryEntity entity){
Inventory inventory = new Inventory();
inventory.setBrand(entity.getBrand());
inventory.setInventory(entity.getInventory());
return inventory;
}
}
InventoryResource.java
This is a controller class that accepts REST calls and translates them to commands to be used by InventoryService
. The code here is very similar for both the Spring and Eclipse MicroProfile applications. The URI path cars
precedes any other segment defined at the method level. The methods getInventories
and setInventory
are mapped to the REST calls cars/inventories
and cars/setInventory
, respectively, and direct the associated call to InventoryService
.
In terms of differences, while the Spring application utilizes annotations from the org.springframework.beans.factory.annotation and org.springframework.web.bind.annotation packages, the Eclipse MicroProfile application utilizes annotations from the javax.ws.rs, cdi-api, and javax.inject packages.
Spring
package org.springexamples.inventories.rest;
import org.springexamples.inventories.model.Inventory;
import org.springexamples.inventories.service.InventoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collection;
@RestController
@RequestMapping(value = "/cars")
public class InventoryResource {
@Autowired
protected InventoryService service;
@Autowired
public InventoryResource(InventoryService service){
this.service = service;
}
@RequestMapping(value = "/inventories",
produces = { "application/json" },
method= {RequestMethod.GET})
public Collection<Inventory> getInventories(){
return service.getAllInventories();
}
@RequestMapping(value = "/setInventory",
consumes = { "application/json" }, method= {RequestMethod.POST})
public void setInventory(@RequestBody Inventory inventory){
service.setInventory(inventory);
}
}
Eclipse MicroProfile
package org.olpexamples.inventories.rest;
import org.olpexamples.inventories.model.Inventory;
import org.olpexamples.inventories.service.InventoryService;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Collection;
@RequestScoped
@Path("cars")
public class InventoryResource {
@Inject
protected InventoryService service;
@GET
@Produces(MediaType.APPLICATION_JSON)
@Transactional
@Path("inventories")
public Collection<Inventory> getInventories(){
return service.getAllInventories();
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Transactional
@Path("setInventory")
public Response setInventory(Inventory inventory){
service.setInventory(inventory);
return Response.status(Response.Status.NO_CONTENT).build();
}
}
InventoryApplication.java
This is the main entry point for the application. For Spring, this class performs various configuration tasks. In particular, @EntityScan
and @EnableJpaRepositories
designate the base packages for JPA entities and repositories, respectively. We also declare the InventoryRepository
instance, to be auto-injected by Spring, and define the service()
method to initialize the InventoryService
and return it. Here we also give the main entry method for the application and indicate the name of its configuration file (inventory-application.yaml, to be reviewed later).
For Eclipse MicroProfile, the InventoryApplication.java file is rather simple. It only declares "/" to be the context path for this application (the path defined here is appended to the context root defined in server.xml, to be discussed later).
Spring
package org.springexamples.inventories.rest;
import org.springexamples.inventories.dao.InventoryRepository;
import org.springexamples.inventories.service.InventoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
@EntityScan("org.springexamples.inventories.dao.entities")
@EnableJpaRepositories("org.springexamples.inventories.dao")
public class InventoryApplication {
@Autowired
protected InventoryRepository inventoryRepository;
public static void main(String[] args) {
System.setProperty("spring.config.name", "inventory-application");
SpringApplication.run(InventoryApplication.class, args);
}
@Bean
public InventoryService service(){
return new InventoryService(inventoryRepository);
}
}
Eclipse MicroProfile
package org.olpexamples.inventories.rest;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/")
public class InventoryApplication extends Application {
}
Auxiliary Files
Spring
logback.xml: This is a simple log configuration file. Details are omitted. Please see the git repository that contains all the files discussed in this article.
inventory-application.yaml: This configuration file is referenced by InventoryApplication.java as discussed above. The environment variables DB_HOST, DB_PORT, and SERVER_PORT correspond to the database server host, database server port, and server port number for the Spring application to listen to. Those environment variables can be defined in the shell or passed to Docker executable in command, as will be discussed below. The database user name and password are hardcoded in the file, which is acceptable for our purposes. However, in a real enterprise application, those would be supplied in a more secure way, e.g. as environment variables specific to development, QA, production environments.
spring:
application:
name: inventory-service
datasource:
url: jdbc:derby://${DB_HOST}:${DB_PORT}/CarDB;create=false
username: demo
password: demopwd
driver-class-name: org.apache.derby.jdbc.ClientDriver
jpa:
hibernate:
ddl-auto: none
# HTTP Server
server:
servlet:
context-path: /CarInventories
port: ${SERVER_PORT}
Eclipse MicroProfile
beans.xml: This is the beans archive descriptor as required by the CDI specification. This file could be used to employ interceptor, decorator, or alternative mechanisms specific to CDI. As we do not have any such mechanism in our simple application we have an empty beans element declaration.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
</beans>
web.xml: This is the standard Java EE web deployment descriptor. We supply an empty descriptor as no specific provisions are needed for our purposes.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
</web-app>
server.xml: This file is used to configure various aspects of Open Liberty server.
The
featureManager
element includes the individual Eclipse MicroProfile functional components used in our application.The
keystore
element is used for the keystore service configuration in server. See SSL Configuration Attributes for details.The
httpEndpoint
element helps to configure the HTTP endpoints for the Open Liberty server. The env.SERVER_PORT parameter indicates that the HTTP port will be set according to the SERVER_PORT environment variable, whereas the default.https.port parameter indicates that the HTTPS port will be supplied as a startup parameter to Open Liberty server (to be set in the pom.xml file, see below).The
webApplication
element is where we define the application context root via app.context.root parameter, which will be set in pom.xml and passed to Open Liberty during startup.The
library
element allows us to define libraries for the Derby database. The shared.resource.dir parameter corresponds to liberty/wlp/usr/shared/resources/ under the build directory where we copy the Derby library jars during the Maven build as configured via the copy-derby-dependency task in pom.xml. This folder will also need to be defined when the Docker executable is run, to be explained later.The
dataSource
element is used to define the data source. Here, the user id and password for the Derby network database are mentioned along with server hostname and port. The env.DB_HOST and env.DB_PORT parameters indicate that the server host and port will be passed as environment variables to Open Liberty server.The same comments on the database user name and password in the Spring application apply here.
<server description="Inventory Liberty server">
<featureManager>
<feature>jaxrs-2.1</feature>
<feature>jsonp-1.1</feature>
<feature>cdi-2.0</feature>
<feature>jpa-2.2</feature>
</featureManager>
<keyStore id="defaultKeyStore" password="ignoreit" />
<httpEndpoint host="*" httpPort="${env.SERVER_PORT}"
httpsPort="${default.https.port}" id="defaultHttpEndpoint"/>
<webApplication location="inventories.war"
contextRoot="${app.context.root}"/>
<!-- Derby Library Configuration -->
<library id="derbyJDBCLib">
<fileset dir="${shared.resource.dir}" includes="derby*.jar"/>
</library>
<!-- Datasource Configuration -->
<dataSource id="inventoryDatasource"
jndiName="jdbc/inventoryDatasource">
<jdbcDriver libraryRef="derbyJDBCLib" />
<properties.derby.client databaseName="CarDB" createDatabase="false"
serverName="${env.DB_HOST}" portNumber="${env.DB_PORT}"
user="demo" password="demopwd"/>
</dataSource>
</server>
persistence.xml: This file defines the persistence unit used in our application according to the Java Persistence API specification. Observe that it references the dataSource
element defined in server.xml above. Note that we use EclipseLink as the JPA provider for the Derby network database and hence the eclipselink
related property definitions.
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="jpa-unit" transaction-type="JTA">
<jta-data-source>jdbc/inventoryDatasource</jta-data-source>
<properties>
<property name="eclipselink.ddl-generation" value="create-tables"/>
<property name="eclipselink.ddl-generation.output-mode"
value="both" />
</properties>
</persistence-unit>
</persistence>
Dockerfile
Spring
This is as simple as it gets. Our image will be built on the openjdk:8-jdk-alpine image. The original archive will be copied to app.jar and run as an executable jar.
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Eclipse MicroProfile
The Docker image for Eclipse MicroProfile application will be built on open-liberty image. We create two links, servers and resources, in order to make command line instruction less verbose (to be seen later). The servers link is needed for Open Liberty server resources, which also contain all the libraries for Eclipse MicroProfile functional components. The resources link is needed for any additional libraries, e.g. derby driver and JPA provider.
FROM open-liberty
RUN ln -s /opt/ol/wlp/usr/servers /servers
RUN ln -s /opt/ol/wlp/usr/shared/resources /resources
ENTRYPOINT ["/opt/ol/wlp/bin/server", "run"]
CMD ["defaultServer"]
pom.xml
Spring
The Maven coordinates of our application are spring-examples:inventories:1.0-SNAPSHOT. We use the Spring version 2.1.2 and Spring Cloud Greenwich releases.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
</parent>
<groupId>spring-examples</groupId>
<artifactId>inventories</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<start-class>
org.springexamples.inventories.rest.InventoryApplication
</start-class>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
...
The pom.xml file continues with Spring, Spring Data Commons, Spring Cloud, and Spring Data JPA dependencies.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
...
Finally, we declare dependencies for the Derby database.
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.14.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.10.1.1</version>
</dependency>
</dependencies>
...
Lastly, we provide build plugins. The first one is the Spring Maven Plugin. The other one is the Dockerfile Maven Plugin from Spotify. Note that konuratdocker/spark-examples is the name of my personal Docker repository, to be replaced with yours if you intend to run those examples. For the Spring application, we name the Docker image inventory-service_spring.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<tag>inventory-service_spring</tag>
<repository>konuratdocker/spark-examples</repository>
<imageTags>
<imageTag>inventory-service_spring</imageTag>
</imageTags>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
Eclipse MicroProfile
The pom.xml file closely follows the conventions for the Open Liberty server (for examples, see this blog.) The Maven coordinates for our application, in this case, are olp-examples:inventories:1.0-SNAPSHOT
. Here we define CarInventories
as the name of our application (app.name tag) also used as the root context for the web application (the warContext tag). The usr packaging type indicates that the generated package does not include an Open Liberty server runnable.
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>olp-examples</groupId>
<artifactId>inventories</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>
UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<app.name>CarInventories</app.name>
<testServerHttpPort>9080</testServerHttpPort>
<testServerHttpsPort>9443</testServerHttpsPort>
<warContext>${app.name}</warContext>
<package.file>
${project.build.directory}/${app.name}.zip</package.file>
<packaging.type>usr</packaging.type>
</properties>
...
The dependencies are declared next. As indicated in this blog, the Open Liberty Bill of Materials dependency, referenced in the dependencyManagement
section, allows us to declare individual Open Liberty features without explicit reference to their versions. Each feature corresponds to an Eclipse MicroProfile functional component. We finally declare the Derby database dependencies.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.openliberty.features</groupId>
<artifactId>features-bom</artifactId>
<version>RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--Open Liberty features -->
<dependency>
<groupId>io.openliberty.features</groupId>
<artifactId>jaxrs-2.1</artifactId>
<type>esa</type>
</dependency>
<dependency>
<groupId>io.openliberty.features</groupId>
<artifactId>jsonp-1.1</artifactId>
<type>esa</type>
</dependency>
<dependency>
<groupId>io.openliberty.features</groupId>
<artifactId>cdi-2.0</artifactId>
<type>esa</type>
</dependency>
<dependency>
<groupId>io.openliberty.features</groupId>
<artifactId>jpa-2.2</artifactId>
<type>esa</type>
</dependency>
<!-- Derby -->
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.14.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.10.1.1</version>
</dependency>
</dependencies>
...
The build section consists of various plugins. The first one is the Maven Dependency Plugin used to copy Derby database libraries under a resources folder (recall from above that we had created a symbolic link in Dockerfile to reference the resources folder later on; that will be further discussed in Part 2).
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>copy-derby-dependency</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeArtifactIds>derby,derbyclient</includeArtifactIds>
<outputDirectory>${project.build.directory}/liberty/wlp/usr/shared/resources/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
...
Next, we continue with a plugin to deploy and manage our application within the Open Liberty server. For details see the original source. Here, observe that the previously declared default ports and the web application context root are passed to the Open Liberty server during startup.
<!-- Enable liberty-maven plugin -->
<plugin>
<groupId>net.wasdev.wlp.maven.plugins</groupId>
<artifactId>liberty-maven-plugin</artifactId>
<version>2.6.1</version>
<configuration>
<assemblyArtifact>
<groupId>io.openliberty</groupId>
<artifactId>openliberty-runtime</artifactId>
<version>RELEASE</version>
<type>zip</type>
</assemblyArtifact>
<configFile>src/main/liberty/config/server.xml
</configFile>
<packageFile>${package.file}</packageFile>
<include>${packaging.type}</include>
<bootstrapProperties>
<default.http.port>
${testServerHttpPort}
</default.http.port>
<default.https.port>
${testServerHttpsPort}
</default.https.port>
<app.context.root>
${warContext}
</app.context.root>
</bootstrapProperties>
</configuration>
<executions>
<execution>
<id>install-liberty</id>
<phase>prepare-package</phase>
<goals>
<goal>install-server</goal>
</goals>
</execution>
<!-- Create defaultServer -->
<execution>
<id>create-server</id>
<phase>prepare-package</phase>
<goals>
<goal>create-server</goal>
</goals>
</execution>
<execution>
<id>install-app</id>
<phase>package</phase>
<goals>
<goal>install-apps</goal>
</goals>
<configuration>
<appsDirectory>apps</appsDirectory>
<stripVersion>true</stripVersion>
<installAppPackages>
project
</installAppPackages>
</configuration>
</execution>
<execution>
<id>start-server</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start-server</goal>
</goals>
</execution>
<execution>
<id>stop-server</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop-server</goal>
</goals>
</execution>
<execution>
<id>package-app</id>
<phase>package</phase>
<goals>
<goal>package-server</goal>
</goals>
</execution>
</executions>
</plugin>
...
The final plugin we reference is Dockerfile Maven Plugin from Spotify, utilized in exactly the same way as in Spring application. The only difference is that here the Docker image is named inventory-service_ol
.
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<tag>inventory-service_ol</tag>
<repository>konuratdocker/spark-examples</repository>
<imageTags>
<imageTag>inventory-service_ol</imageTag>
</imageTags>
<buildArgs>
<JAR_FILE>
target/${project.build.finalName}.jar
</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
That's all for Part 1! Tune back in tomorrow when we test and run our two applications.
Opinions expressed by DZone contributors are their own.
Comments