Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Programming Styles Compared: Spring Framework vis-a-vis Eclipse MicroProfile Part 1

DZone 's Guide to

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.

· Microservices Zone ·
Free Resource

Introduction

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 OpenIDOAuth, 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.

Class Model

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:

The main components of the Eclipse MicroProfile application are as follows:

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 findAlland  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

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 JpaRepositorywill 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. 

Topics:
eclipse microprofile ,microservice architecture ,open liberty ,microservices ,spring tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}