Friday, February 20, 2015

Openshift: Build Spring Boot application on Wildfly 8.2.0 with Java 8

OpenShift DIY cartridge is a great way to test unsupported languages on OpenShift. But it is not scalable (you can vote for Scalable DIY cartridge here) which makes it hard to use with production grade Spring Boot applications. But what if we deployed Spring Boot application to WildFly Application Server? Spring Boot can run with embedded servlet container like Tomcat or much faster Undertow, but it also can be deployed to a standalone application server. This would mean that it can be also deployed to WildFly application server that is supported by OpenShift. Let’s see how easy is to get started with creating a Spring Boot application from scratch and deploy it to WildFly 8.2 on OpenShift.

Note: While browsing OpenShift documentation one can think that on WildFly 8.1 and Java 7 is supported (as of time of writing this blog post). But this is fortunately not true anymore: WildFly 8.2 and Java 8 will work fine and it is default in fact!. This was the first time when I was happy about documentation being outdated.

Update: If you are looking for a quick start, without the step by step walkthrough have a look here: Quick Start: Spring Boot and WildfFly 8.2 on OpenShift

Prerequisite

Before you can start building the application, you need to have an OpenShift free account and client tools (rhc) installed.

Create WildFly application

To create a WildFly application using client tools, type the following command:

rhc create-app boot jboss-wildfly-8 --scaling

jboss-wildfly-8 cartridge is described as WildFly Application Server 8.2.0.Final. Scaling option is used as it will be impossible to set it later (vote here)

When the application is created you should see username and password for an administration user created for you. Please store these credentials to be able to login to the WildFly administration console.

Template Application Source code

OpenShift creates a template project. The project is a standard Maven project. You can browse through pom.xml and see that Java 8 is used by default for this project. In addition, there are two non standard folders created: deployments, that is used to put the resulting archive into, and .openshift with OpenShift specific files. Please note .opensift/config. This is the place where WildFly configuration is stored.

Spring Boot dependencies

As the dependency management Spring IO Platform will be used. The main advantage of using Spring IO Platform is that it simplifies dependency management by providing versions of Spring projects along with their dependencies that are tested and known to work together. Modify the pom.xml by adding:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.spring.platform</groupId>
            <artifactId>platform-bom</artifactId>
            <version>1.1.1.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Now, Spring Boot dependencies can be added. Please note that since the application will be deployed to WildFly, we need to explicitly remove dependency on Tomcat.:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Configure the application

Initialize Spring Boot Application

Having all dependencies, we can add application code. Create Application.java in demo package. The Application class’s work is to initiate Spring Boot application, so it must extend from SpringBootServletInitializer and be annotated with @SpringBootApplication

package demo;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.web.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

}

@Entity, @Repository, @Controller

Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. For those who are not familiar with the project please visit: http://projects.spring.io/spring-data-jpa/.

Domain model for this sample project is just a Person with some basic fields:

@Entity
@Table(name = "people")
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @Column(name = "first_name")
    @NotEmpty
    protected String firstName;

    @Column(name = "last_name")
    @NotEmpty
    protected String lastName;

    @Column(name = "address")
    @NotEmpty
    private String address;

    @Column(name = "city")
    @NotEmpty
    private String city;

    @Column(name = "telephone")
    @NotEmpty
    @Digits(fraction = 0, integer = 10)
    private String telephone;

}

The Person needs a @Repository, so we can createa basic one using Spring’s Data repository. Spring Data repositories reduce much of the boilerplate code thanks to a simple interface definition:

@Repository
public interface PeopleRepository extends PagingAndSortingRepository<Person, Integer> {
    List<Person> findByLastName(@Param("lastName") String lastName);
}

With the domain model in place some test data can be handy. The easiest way is to provide a data.sql file with the SQL script to be executed on the application start-up.

Create src/main/resources/data.sql containing initial data for the people table (see below). Spring Boot will pick this file and run against configured Data Source. Since the Data Source used is connecting to H2 database, the proper SQL syntax must be used:

INSERT INTO people VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023');

Having Spring Data JPA repository in place, we can create a simple controller that exposes data over REST:

@RestController
@RequestMapping("people")
public class PeopleController {

    private final PeopleRepository peopleRepository;

    @Inject
    public PeopleController(PeopleRepository peopleRepository) {
        this.peopleRepository = peopleRepository;
    }

    @RequestMapping
    public Iterable<Person> findAll(@RequestParam Optional<String> lastName) {
        if (lastName.isPresent()) {
            return peopleRepository.findByLastName(lastName.get());
        }
        return peopleRepository.findAll();
    }
}

findAll method accepts optional lastName parameter that is bound to Java’s 8 java.util.Optional.

Start page

The project generated by OpenShift during project setup contain webapp folder with some static files. These files can be removed and index.html can be modified:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>OpenShift</title>
</head>
<body>
<form role="form" action="people">
    <fieldset>
        <legend>People search</legend>
        <label for="lastName">Last name:</label>
        <input id="lastName" type="text" name="lastName" value="McFarland"/>
        <input type="submit" value="Search"/>
    </fieldset>
</form>
<p>
    ... or: <a href="people">Find all ...</a>
</p>
</body>
</html>

It is just a static page, but I noticed that application will not start if there is not default mapping (/) or if returns code different than 200. Normally, there will be always a default mapping.

Configuration

Create src/main/resources/application.properties and put the following values:

  • management.context-path=/manage: actuator default management context path is /. This is changed to /manage, because OpenShift exposes /health endpoint itself that covers Actuator’s /health endpoint .

  • spring.datasource.jndi-name=java:jboss/datasources/ExampleDS: since the application uses Spring Data JPA, we want to bind to the server’s Data Source via JNDI. Please look at .openshift/config/standalone.xml for other datasources. This is important if you wish to configure MySql or PostgreSQL to be used with your application. Read more about connecting to JNDI Data Source in Spring Boot here: http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-connecting-to-a-jndi-datasource

  • spring.jpa.hibernate.ddl-auto=create-drop: create structure of the database based on the provided entities.

Deploying to OpenShift

The application is ready to be pushed to the repository. Commit your local changes and then push it to remote:

git push

The initial deployment (build and application startup) will take some time (up to several minutes). Subsequent deployments are a bit faster. You can now browse to: http://appname-yournamespace.rhcloud.com/ and you should see the form:

Clicking search with default value will get record with id = 3:

[
    {
        "id": 3,
        "firstName": "2693 Commerce St.",
        "lastName": "McFarland",
        "address": "Eduardo",
        "city": "Rodriquez",
        "telephone": "6085558763"
    }
]

Navigating to http://appname-yournamespace.rhcloud.com/people will return all records from the database.

Going Java 7

If you want to use Java 7 in your project, instead of default Java 8, rename .openshift/markers/java8 to .openshift/markers/java7 and changte pom.xml accordingly:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
    <maven.compiler.fork>true</maven.compiler.fork>
</properties>

Please note maven.compiler.executable was removed. Don’t forget to change the @Controller’s code and make it Java 7 compatible.

Summary

In this blog post you learned how to configure basic Spring Boot application and run it on OpenShift with WildfFly 8.2 and Java 8. OpenShift scales the application with the web proxy HAProxy. OpenShift takes care of automatically adding or removing copies of the application to serve requests as needed.

Resources

3 comments:

  1. remote: [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.3.2:compile (default-compile) on project boot: Compilation failure
    remote: [ERROR] Failure executing javac, but could not parse the error:
    remote: [ERROR] /bin/sh: /var/lib/openshift/554b13b0e0b8cdb2de000061/wildfly/usr/lib/jvm/jdk1.8.0_05/bin/javac: No such file or directory

    ReplyDelete
  2. There is an error in the pom file. You have to remove the part about explicitly setting the maven.compiler.executable.

    ReplyDelete
  3. Finally, after a lot of issues it works. So idf you want to deploy the appication on openshift and to be scalable do exactly what this tutorial said, and delete the maven.compiler.executable from pom.xml .

    But whenyou create the application, for me it only works with wildfly 8.2.1.Final. With other versions it didn't work for me. Thanks for this awesome tutorial :) !!!

    ReplyDelete