Select DataStore

RHOAR: Wildfly Swarm vs Spring Boot Microservices – Part 2

Recap

In Part 1, I implemented a simple database application using Wildfly Swarm and deployed it on Openshift. In this article, I am going to do the same but using Spring Boot instead so that you can see the difference between using different frameworks for your development. I shall also describe the use of spring-cloud-starter-kubernetes-config in accessing ConfigMap in Openshift deployment which is slightly different from I I read in the documentation.

At the end of this article, I shall summarise my view on the pros and cons of each framework in the context of ease of developing this simple database application. You may want to compare it with the Verti.x and Fuse Integration Services (FIS) implementation in my previous 2-part article on Vert.x (Part 1 and Part2). By the way, the FIS implementation is also using Spring Boot but it uses FUSE’s REST Domain Specific Language (DSL).

Spring Boot for Spring Developers

The original Spring Framework was introduced back in 2004 . It introduces dependency injection and inversion of control based on Plain Old Java Objects (POJOs) and quickly divides the Enterprise Java world into J2EE vs. Spring. Spring Boot was first introduced in 2014. Spring Boot’s primary goal is to provide faster and better getting started experience for developers. It favours convention over configuration (opinionated auto configuration), refrains from use of code generation and XML configuration. In addition, it packages an application as a fat jar which can be executed simply using the “java -jar” command.

The source code of this project is available on Github.

Spring Starters

According to the Spring Boot Project:

“...Spring Boot Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop-shop for all the Spring and related technology that you need without having to hunt through sample code and copy paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access just include the spring-boot-starter-data-jpa dependency in your project, and you are good to go...”

For example, when using maven, the inclusion of the jpa starter can be done simply by adding:

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

This makes dependency management much easier than before. Wildfly Swarm’s Fractions are the closest equivalence to Spring Starters.

Entity

When using Spring Boot, you don’t have to use Spring technologies exclusively. In building a RESTful interface to a database application (which is what this article is all about), the first technologies that come to mind for a seasoned Spring developer may be MVC and JdbcTemplate. They can certainly be used in building our application but we are going to use jaxrs and Jpa instead.

The entity (Customer) is the same as that used in our Wildfly Swarm implementation in Part 1.

@Entity

@Table(name = "CUSTOMER")
public class Customer {

 @Id
 private String customerId;
 private String vipStatus;
 private Integer balance;
 ...

CustomerRepository

This is the equivalence of the CustomerService in our Wildfly Swarm implementation. You will be amazed by how this class looks like despite providing all the CRUD (create/read/update/delete) data base operations.

package com.redhat.rhoar.springboot.customer;

 

import org.springframework.data.repository.CrudRepository;
import com.redhat.rhoar.springboot.customer.model.Customer;

public interface CustomerRepository extends CrudRepository<Customer, String> {
}

That’s it! All the CRUD methods invoked (defined in CrudRepository) will be routed into the base repository implementation of the data store of our choice provided by Spring Data, in our case Jpa. We don’t even have to write a single line of JPQL!

In the interface definition, we are using Customer as the entity class and its key (customerId) of type String.

RESTful Interfaces

The RESTfule Interface implementation (CustomerEndpoint.java) is also similar to that of Wildfly Swarm. The only differences are use of Spring annotations: @Component and @Autowired.

@Component
@Path("/")
public class CustomerEndpoint {

 
 @Autowired
  private CustomerRepository customerService;

 
 @GET
 @Path("/customer/{customerId}")
 @Produces(MediaType.APPLICATION_JSON)
 public Customer getCustomer(@PathParam("customerId") String customerId) {

  Customer customer = customerService.findOne(customerId);
  if (customer == null) {
    throw new NotFoundException();
  } else {
    return customer;
  }

}
...

 

Health Check

Again, we are providing health check (HealthCheckEndpoint.java) by using method provided by Spring (spring-boot-actuator, to be exact).

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-actuator</artifactId>
</dependency>

 

The implementation is simple:

@Component
@Path("/")
public class HealthCheckEndpoint {

 @Autowired

 private HealthEndpoint health;

 @GET
 @Path("/health")
 @Produces(MediaType.APPLICATION_JSON)
 public Health getHealth() {
  return health.invoke();
 }

}
...

Testing Using SpringRunner

As mentioned in the Wildfly Swarm article, the way to provide configuration parameters to applications running on Openshift is by using ConfigMaps. To use ConfigMap, we have to include in pom.xml the dependency:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-kubernetes-config</artifactId>
  <version>${version.spring-cloud-kubernetes}</version>
</dependency>

However, once you included this dependency, when you are running either Junit tests or the application locally using spring-boot:run, you will see an exception as shown:

ConfigMapPropertySource Exception
ConfigMapPropertySource Exception

Although the tests and application still work, it is best to eliminate this exception during execution. To do that, you have to include a file named “bootstrap.properties” and “boostrap-dev.properties” in the src/test/resources and /src/main/resources directories respectively with its content shown below:

spring.cloud.kubernetes.config.enabled=false

to disable kubernetes ConfigMapPropertySource.

I implemented 2 Junit tests: CustomerEndpointTest.java, CustomerRepositoryTest.java and HelthCheckEndpointTest.java. Here is a snippet of the test code:

@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class CustomerEndpointTest {


 @LocalServerPort
 private int port;

 // set up stop watches to time execution and overhead
 private static StopWatch totalSW = new StopWatch();
 private static StopWatch testSW = new StopWatch();
 private static String CUSTOMER_A04 = "{\"customerId\":\"A04\",\"vipStatus\":\"Platinum\",\"balance\":2200}";
 private static String CUSTOMER_A05 = "{\"customerId\":\"A05\",\"vipStatus\":\"Diamond\",\"balance\":5200}";

 @BeforeClass
 public static void beforeClass() throws Exception {
   totalSW.start();
   testSW.start();
   testSW.suspend();
 }

 @AfterClass
 public static void afterClass() throws Exception {
   totalSW.stop();
   testSW.stop();

   // display overall elapsed time and execution/overhead percentage
   System.out.println("********************************************************");

   System.out.println(String.format("Total Runtime: %10s", totalSW.toString()));

   System.out.println(String.format("Test Runtime : %10s or %.2f percent", testSW.toString()

, (double) testSW.getTime() / (double) totalSW.getTime()));

   System.out.println(String.format("Overhead : %10s or %.2f percent"

, DurationFormatUtils.formatDurationHMS((totalSW.getTime() - testSW.getTime()))

,(double) (totalSW.getTime() - testSW.getTime()) / (double) totalSW.getTime()));

   System.out.println("********************************************************");

 }

 @Before
 public void before() throws Exception {
   testSW.resume();
   RestAssured.baseURI = String.format("http://localhost:%d/", port);
 }

 @After
 public void after() throws Exception {
   testSW.suspend();
 }

 @Test
 public void testAddCustomer() throws Exception {

   given().header("Content-Type", "application/json").body(CUSTOMER_A04).post("/customer").then().assertThat().statusCode(200);

   given().header("Content-Type", "application/json").body(CUSTOMER_A05).post("/customer").then().assertThat().statusCode(200);

 }

…

Note that we are using @RunWith(SpringRunner.class) ie, SpringRunner instead of Arquillian when using Wildfly Swarm.

I also used Apache Stopwatch to time the execution. The screenshot is shown below:

 

Although the overhead (percentage-wise) is not that much better: 78 percent instead of 89% previously, the overall elapsed time is about one-halves that of Arquillian: 16 seconds compared to 33 seconds overall.

Openshift Deployment

The readiness and liveness tests are configured in the src/main/fabric8/deployment.yml shown below:

spec:
 template:
  spec:
   containers:
   - livenessProbe:
     failureThreshold: 2
     httpGet:
      path: "/health"
      port: 8080
      scheme: HTTP
     initialDelaySeconds: 60
     periodSeconds: 10
     successThreshold: 1
     timeoutSeconds: 1
    readinessProbe:
     failureThreshold: 3
     httpGet:
      path: "/health"
      port: 8080
      scheme: HTTP
     initialDelaySeconds: 20
     periodSeconds: 10
     successThreshold: 1
     timeoutSeconds: 1
    resources:
      limits:
      cpu: 1
      memory: 1Gi
     requests:
      cpu: 200m
      memory: 200Mi

 

I have decided that this time, I am going to use MySQL instead of PostgreSQL. And that I am going to use the Openshift console to create the database instead of using a template like I did for Wildfly Swarm in Part 1. To deploy to Openshift, follow the steps below:

export SPRINGBOOT_PRJ=springboot-service
oc login -u developer
oc new-project $SPRINGBOOT_PRJ

Switch to the Openshift console and select the springboot-service project and follow the screenshot sequence to create a MySQL Ephemeral database..

Add To Project
Add To Project
Select from Catalog
Select from Catalog

 

Select DataStore
Select DataStore
Fill In MySQL Parameters
Fill In MySQL Parameters

Enter customerdb as the name of the database.

After deploying the data base, you have to rsh into the pod, create the customer table and populate it using the mysql command line interface as follows:

rsh into MySQL Container
rsh into MySQL Container
Create Table and Populate 2 Rows
Create Table and Populate 2 Rows

We create a ConfigMap using the configuration defined in etc/application.properties. Spring Cloud Kubernetes retrieves the ConfigMap using Kubernetes API which requires view access hence the command to add the view role to the default service account.

oc policy add-role-to-user view -n $SPRINGBOOT_PRJ -z default
oc create configmap customer-service --from-file=etc/application.properties -n $SPRINGBOOT_PRJ
oc get configmap customer-service -o yaml -n $SPRINGBOOT_PRJ

mvn clean fabric8:deploy -Dmaven.test.skip=true -Popenshift -Dfabric8.namespace=$springboot_PRJ

After database and application deployment, you should see:

Containers Running
Containers Running

It should be noted that the application.properties file that is in src/main/resources contains only the name of the application:

spring.application.name=customer-service

While the etc/application.properties contains the Spring Boot application’s configuration (JPA, database, etc.) used to create the ConfigMap.

spring.application.name=customer-service
cxf.path=/
cxf.jaxrs.component-scan=true
cxf.jaxrs.classes-scan-packages=com.redhat.rhoar.springboot.customer.rest

 
#spring.profiles.active=test

 

# lets listen on all ports to ensure we can be invoked from the pod IP
server.address=0.0.0.0
management.address=0.0.0.0
server.contextPath=/

 

# lets use a different management port in case you need to listen to HTTP requests on 8080
management.port=8081

 

# disable all management endpoints except health
endpoints.enabled = false
endpoints.health.enabled = true

 

# mysql configuration
mysql.service.name=mysql
mysql.service.database=customerdb
mysql.service.username=jboss
mysql.service.password=jboss

 

#jpa
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=validate

 

# Database configuration
spring.datasource.url = jdbc:mysql://${${mysql.service.name}.service.host}:${${mysql.service.name}.service.port}/${mysql.service.database}
spring.datasource.username = ${mysql.service.username}
spring.datasource.password = ${mysql.service.password}
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

 

What I found is that Kubernetes Cloud will look for ConfigMap with the name defined by spring.application.name which is cutomer-service, the same name we use to create the ConfigMap. We do not even have to change the deployment.yml to add volumneMounts and volumes entries.

Interacting with Microservice

To interact with the microservice, we need to find out the route of the microservice. From the cosole, select the project “springboot-service” and select applications→ routes and jot down the Hostname. See screenshot below:

 

Find Route
Find Route

The issue the following ommands:

export SPRINGBOOT_URL=http://springboot-service-springboot-service.10.0.2.15.xip.io

 

curl -X GET "$SPRINGBOOT_URL/health"
 

curl -X GET "$SPRINGBOOT_URL/customer/A01"
 

curl -X GET "$SPRINGBOOT_URL/customers"
 

curl -i -H "Content-Type: application/json" -X POST \
-d '{"customerId":"A03","vipStatus":"Platinum","balance":2200}' \
"$SPRINGBOOT_URL/customer"

The responses of these commands have been captured below:

curl Responses
curl Responses

 

Conclusions

 

 

Wildfly Swarm Spring Boot
Application Jar file size 108M 42.8M
Difficulty Level Medium to Low – if you come from a JEE development background Medium to Low – if yu come from a Spring background.
Development Time Short – the CRUD operations need to be implemented. Short – making use of CrudRepository to get CRUD operations without writing any code.
Junit Testing Speed Slow – Arquillian is slow in setting up the environment to run. Running simple Junit test takes over 33 seconds to run Quick – SpringRunner takes less than ½ the time that Arquillian take to execute similar Junit tests
Familiarity JEE Spring
Threading Model Multiple threads Multiple Threads
Openshift Deployment Difficulty Level Medium – takes time to learn Openshift and configmap Medium – takes time to learn Openshift and configmap

 

In my opinion, Spring Boot has a tiny edge in ease of development simply because the Junit tests run faster than using Arquillian when using Wildfly Swarm. And that Spring Boot’s CrudRepository provides CRUD implementation without requiring the developer to write any code sways me in Spring Boot’s favour. And the resultant Uber jar file for Spring Boot is roughly one-halves the size of that of Wildfly Swarm. This is quite surprising as Wildfly Swarm advertises itself as a configurable app server where you only bring in the fractions you need to run your app, I expected its Uber jar to be smaller than that of Spring Boot’s as it uses Tomcat. But the reverse is true!