JLUPIN PLATFORM WEBSITE
tutorials
  • Rating:
  • Views: 350
  • Author: JLupin
  • Skill level: easy
  • Comments: 0

Overview

This tutorial will show you how to create servlet microservice for JLupin Next Server platform. What is servlet microservice? It is just a microservice which uses servlet container to serve its logic. JLupin Next Server is using Spring Boot to allow users to choose servlet container that they want.

Requirements

To complete this tutorial you will need JLupin Next Server version 1.4.0.4. You can download it from here.

Configure project

When you create service which will be available to other users of your system, you probably want to tell them how to use it. For native microservice you will create separate interfaces module. But servlet microservice probably won't be accessed with use of proxy objects but with simple json calls or will serve HTTP pages. To keep microservice structure similar for both types we will create implementation module for servlet microservice, but we won't create interfaces module.

We will use Maven feature called reactor. It is built-in mechanism for handling multi-module project. So we are going to start with standard Maven structure.

+--implementation
|  |
|  +--src
|  |  |
|  |  +--main
|  |     |
|  |     +--java
|  |     |
|  |     +--resources
|  |
|  +--pom.xml
|
+--pom.xml

Below pom files with proper configuration are presented.

pom.xml

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

    <artifactId>welcome-servlet-microservice</artifactId>
    <groupId>com.example.application</groupId>
    <version>1.0.0</version>
    <packaging>pom</packaging>

    <modules>
        <module>implementation</module>
    </modules>

    <properties>
        <maven.surefire.skipTests>false</maven.surefire.skipTests>

        <maven.failsafe.plugin.version>2.20</maven.failsafe.plugin.version>
        <maven.surefire.plugin.version>2.20</maven.surefire.plugin.version>

        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven.surefire.plugin.version}</version>
                <configuration>
                    <skipTests>${maven.surefire.skipTests}</skipTests>
                </configuration>
            </plugin>
        </plugins>

        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-failsafe-plugin</artifactId>
                    <version>${maven.failsafe.plugin.version}</version>
                    <executions>
                        <execution>
                            <id>integration-test</id>
                            <goals>
                                <goal>integration-test</goal>
                                <goal>verify</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <includes>
                            <include>**/Test*.java</include>
                            <include>**/*Test.java</include>
                            <include>**/*Tests.java</include>
                            <include>**/*TestCase.java</include>
                        </includes>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

implementation/pom.xml

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.7.RELEASE</version>
    </parent>

    <name>welcome-servlet-microservice</name>
    <groupId>com.example.application</groupId>
    <artifactId>welcome-servlet-microservice-implementation</artifactId>
    <packaging>war</packaging>
    <version>1.0.0</version>

    <repositories>
        <!-- Repository is also accessible using https connection: -->
        <!-- https://support.jlupin.com/maven2/ -->
        <repository>
            <id>jlupin-central</id>
            <name>jlupin</name>
            <url>http://support.jlupin.com/maven2/</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <!-- Repository is also accessible using https connection: -->
        <!-- https://support.jlupin.com/maven2/ -->
        <pluginRepository>
            <id>jlupin-central</id>
            <name>jlupin</name>
            <url>http://support.jlupin.com/maven2/</url>
        </pluginRepository>
    </pluginRepositories>

    <properties>
        <server.version>1.4.0.4</server.version>
        <jlupin.next.server.maven.plugin.version>1.0.2</jlupin.next.server.maven.plugin.version>
    </properties>

    <dependencies>
        <!-- JLupin dependencies -->
        <dependency>
            <groupId>com.jlupin</groupId>
            <artifactId>jlupin-client-assembly</artifactId>
            <version>${server.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.name}</finalName>
        <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.jlupin</groupId>
                <artifactId>jlupin-next-server-maven-plugin</artifactId>
                <version>${jlupin.next.server.maven.plugin.version}</version>
                <executions>
                    <execution>
                        <id>jlupin-zip</id>
                        <goals>
                            <goal>zip</goal>
                        </goals>
                        <configuration>
                            <additionalFilesDirectories>
                                <param>../additional-files</param>
                            </additionalFilesDirectories>
                        </configuration>
                    </execution>
                    <execution>
                        <id>jlupin-deploy</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>deploy</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

JLupin Maven repository is configured to get access to required artifacts. Also dependencies required for this tutorial and development are added. JLupin Next Server Maven Plugin is configured to zip microservice and deploy it. Also content of additional-files is added to created zip file. You need to create this directory on same level as implementation directory. Configuration files will be placed here. You should end up with structure:

+--additional-files
|
+--implementation
|  |
|  +--src
|  |  |
|  |  +--main
|  |     |
|  |     +--java
|  |     |
|  |     +--resources
|  |
|  +--pom.xml
|
+--pom.xml

Microservice implementation

At first create suitable package structure. In this tutorial com.example.application will be used as main package. Recommended structure (also used by this tutorial) is presented below.

+--additional-files
|
+--implementation
|  |
|  +--src
|  |  |
|  |  +--main
|  |     |
|  |     +--java
|  |     |  |
|  |     |  +--com
|  |     |     |
|  |     |     +--example
|  |     |        |
|  |     |        +--application
|  |     |           |
|  |     |           +--configuration
|  |     |           |
|  |     |           +--controller
|  |     |
|  |     +--resources
|  |
|  +--pom.xml
|
+--pom.xml

When you done with package structure create basic Spring configuration file under configuration package.

package com.example.application.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

@Configuration
@ComponentScan("com.example.application")
public class SpringConfiguration {
}

Ok, we have Spring configuration file created. But our microservice does nothing. Create simple REST controller which accepts name as request body and returns welcome message.

package com.example.application.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.Map;

@RestController
public class WelcomeController {
    @CrossOrigin
    @PostMapping(value = "/getWelcomeMessage")
    public Map<String, String> getWelcomeMessage(@RequestBody Map<String, String> map) {
        return Collections.singletonMap("message", "Hello " + map.get("name") + "!");
    }
}

Controller is ready. Now we need to create starter class for Spring Boot:

package com.example.application;

import com.example.application.configuration.SpringConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootApplicationStarter {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(SpringConfiguration.class, args);
    }
}

We used annotations to configure Spring container. But you can configure starter class the way you want. Now create servlet_configuration.yml file and setup your microservice - example below. Put this file inside additional-files directory.

SERVERS:
  HTTP:
    type: spring_boot
    httpPrimaryPort: -1
    httpSecondaryPort: -1
    waitForFinishExecuteAllRequests: true
    waitToShutdownThreadsOnStop: 5000
    springBootLoaderClassName: org.springframework.boot.loader.WarLauncher
    #contextName: webservices
  TRANSMISSION:
    readTimeout: 480000
    isWaitForFinishExecuteAllRequests: false
    waitToShutdownThreadsOnStop: 60000
    backlog: 0
    receiveBufferSize: 0
    isReuseAddress: false
    threadPoolSize: 8
    isLogPeriodicOnDebug: true
    isDestroyThreadOnTimeout: false
    threadExecutingTimeOut: 3600000
PROPERTIES:
  #jvmOptions1: '-Xms128M -Xmx256M -agentlib:jdwp=transport=dt_socket,address=12998,server=y,suspend=y'
  jvmOptions1: '-Xms128M -Xmx512M' #jvmOptions_2 - default the same as jvmOptions_1
  #jvmOptions2: '-Xms128M -Xmx256M'
  switchDelayTime: 1000
  connectionSocketTimeoutInMillis: 1000
  readTimeoutInMillis: 30000
  expectedCheckResponseTimeInMillis: 2000
  isKeepAlive: false
  isOOBInline: false
  isTcpNoDelay: false
  isReuseAddress: false
  sendBufferSize: 0
  receiveBufferSize: 0
  soLinger: 0
  trafficClass: 0
  #startProcessCommand: c:\\jvm\\bin\\java.exe -Xms128M -Xmx256M - for custom path to JVM
  waitForProcessInitResponseTimeInMillis: 90000
  waitForProcessStartResponseTimeInMillis: 90000
  waitForProcessDestroyResponseTimeInMillis: 30000
  isAllFilesToJVMAppClassLoader: true
  isStackDumping: true
  isArchiveOnStart: false
  startLogMode: INFO
INITIALIZING_LOGGER:
  #directoryPath: '/logs/server'
  #fileName: 'file_name'
  fileExtension: 'log'
  fileSizeInMB: 20
  maxFiles: 10

You should also configure logging system (Log4j for JLupin and Spring container, Logback for Spring Boot). Example configuration with asynchronous appender below.

Put this into log4j.xml file inside additional-files directory.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<!-- ===================================================================== -->
<!--                                                                       -->
<!--  Log4j Configuration                                                  -->
<!--                                                                       -->
<!-- ===================================================================== -->

<!--
   | For more configuration information and examples see the Jakarta Log4j
   | owebsite: http://jakarta.apache.org/log4j
 -->

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">

    <!-- ================================= -->
    <!-- Preserve messages in a local file -->
    <!-- ================================= -->

    <!-- A time/startCommandExecuteDate based rolling appender -->
    <appender name="FILE" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="File" value="../logs/server/microservice/welcome-servlet-microservice/microservice.log"/>
        <param name="Append" value="true"/>

        <!-- Rollover at the top of each hour -->
        <param name="DatePattern" value="'.'yyyy-MM-dd"/>
        <!--param name="MaxFileSize" value="200MB"/-->
        <!-- Keep one backup file -->
        <!--param name="MaxBackupIndex" value="10"/-->

        <layout class="org.apache.log4j.PatternLayout">
            <!-- The default pattern: Date Priority [Category] (Thread) Messagen -->
            <param name="ConversionPattern" value="%d %-5p [%c] (%t)  %m%n"/>
        </layout>
    </appender>

    <appender name="ASYNC_FILE" class="org.apache.log4j.AsyncAppender">
        <param name="BufferSize" value="1000"/>
        <appender-ref ref="FILE"/>
    </appender>

    <!-- ======================= -->
    <!-- Setup the Root category -->
    <!-- ======================= -->

    <root>
        <priority value="info"/>
        <appender-ref ref="ASYNC_FILE"/>
    </root>

</log4j:configuration>

And this into logback.xml file inside resources directory.

<configuration scan="true">

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d %-5p [%c] \(%t\) %m%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>../logs/server/microservice/welcome-servlet-microservice/microservice.log</file>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>../logs/server/microservice/welcome-servlet-microservice/microservice.%d{yyyy-MM-dd}.log
            </fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>

        <encoder>
            <pattern>%d %-5p [%c] \(%t\) %m%n</pattern>
        </encoder>
    </appender>

    <!-- <logger name="org.springframework" level="DEBUG"/> -->
    <root level="INFO">
        <appender-ref ref="FILE"/>
        <appender-ref ref="STDOUT"/>
    </root>

</configuration>

That's all. Now your microservice is ready to deploy.

Deploying microservice

Run server (start/start.sh or start/start.cmd in main server directory). Then run maven command to deploy your microservice:

mvn clean pre-integration-test --projects implementation --also-make

Check microservice's list to see your microservice (start/control.sh):

./control.sh microservice list
Zone    Node   Microservice                 Type      #Services Context
default NODE_1 currency-converter-chf       springApp 1         /jlns/zone/default/NODE_1/currency-converter-chf/
default NODE_1 currency-converter-eur       springApp 1         /jlns/zone/default/NODE_1/currency-converter-eur/
default NODE_1 currency-converter-gbp       springApp 1         /jlns/zone/default/NODE_1/currency-converter-gbp/
default NODE_1 exchange-rates               springApp 1         /jlns/zone/default/NODE_1/exchange-rates/
default NODE_1 portal-access                webApp    N/A       /jlns/zone/default/NODE_1/portal-access/
default NODE_1 welcome-servlet-microservice webApp    N/A       /jlns/zone/default/NODE_1/welcome-servlet-microservice/

You can see your microservice on the list. You can check its status to see if it is running.

./control.sh microservice status welcome-servlet-microservice
Zone    Node   Microservice                 ProcessID Status  Available
default NODE_1 welcome-servlet-microservice 15004     RUNNING yes

Integration tests

Now it is time to add some integration tests and check our microservice. We will put them into separate module called integration test. Let's add it with proper test package structure:

+--additional-files
|
+--implementation
|  |
|  +--[...]
|
+--integration-test
|  |
|  +--src
|  |  |
|  |  +--test
|  |     |
|  |     +--java
|  |     |  |
|  |     |  +--com
|  |     |     |
|  |     |     +--example
|  |     |        |
|  |     |        +--application
|  |     |
|  |     +--resources
|  |
|  +--pom.xml
|
+--pom.xml

integration-test/pom.xml

<?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>
    <parent>
        <artifactId>welcome-servlet-microservice</artifactId>
        <groupId>com.example.application</groupId>
        <version>1.0.0</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <artifactId>welcome-servlet-microservice-integration-test</artifactId>    

    <properties>
        <maven.surefire.skipTests>true</maven.surefire.skipTests>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.example.application</groupId>
            <artifactId>welcome-servlet-microservice-implementation</artifactId>
            <version>1.0.0</version>
            <classifier>classes</classifier>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>1.5.7.RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Add module into modules section inside top level pom.xml file:

[...]
    <modules>
        <module>implementation</module>
        <module>integration-test</module>
    </modules>
[...]

Because we are creating war for application and we want to test our microservice in seperate module we need to generate classes also for dependency purposes. Add this to implementation/pom.xml:

<plugin>
    [...]
    <build>
        [...]
        <plugins>
            [...]
            <plugin>        
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <attachClasses>true</attachClasses>
                    <classesClassifier>classes</classesClassifier>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Now create test class.

package com.example.application;

import com.example.application.controller.WelcomeController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(WelcomeController.class)
public class IntegrationTest {
   @Autowired
   private MockMvc mvc;

   @Test
   public void test() throws Exception {
      mvc.perform(post("/getWelcomeMessage").content("{\"name\":\"Piotr\"}").contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().json("{\"message\": \"Hello Piotr!\"}"));
   }
}

It is simple test for returned json content. The last thing is to configure logger (Log4j) for this tests. Add log4j.xml file to test resources.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<!-- ===================================================================== -->
<!--                                                                       -->
<!--  Log4j Configuration                                                  -->
<!--                                                                       -->
<!-- ===================================================================== -->

<!--
   | For more configuration information and examples see the Jakarta Log4j
   | owebsite: http://jakarta.apache.org/log4j
 -->

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">

    <!-- ================================= -->
    <!-- Preserve messages in a local file -->
    <!-- ================================= -->

    <!-- A time/startCommandExecuteDate based rolling appender -->
    <appender name="FILE" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out"/>

        <layout class="org.apache.log4j.PatternLayout">
            <!-- The default pattern: Date Priority [Category] (Thread) Messagen -->
            <param name="ConversionPattern" value="%d %-5p [%c] (%t)  %m%n"/>
        </layout>
    </appender>

    <appender name="ASYNC_FILE" class="org.apache.log4j.AsyncAppender">
        <param name="BufferSize" value="1000"/>
        <appender-ref ref="FILE"/>
    </appender>

    <!-- ======================= -->
    <!-- Setup the Root category -->
    <!-- ======================= -->

    <root>
        <priority value="info"/>
        <appender-ref ref="ASYNC_FILE"/>
    </root>

</log4j:configuration>

It is configured to write logs to standard out. Now you're ready to run tests.

mvn clean integration-test --projects integration-test --also-make
[INFO] Scanning for projects...
[...]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.application.IntegrationTest
[...]
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] welcome-servlet-microservice ....................... SUCCESS [ 13.976 s]
[INFO] welcome-servlet-microservice ....................... SUCCESS [  0.033 s]
[INFO] welcome-servlet-microservice-integration-test ...... SUCCESS [  5.069 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 19.562 s
[INFO] Finished at: 2017-10-10T14:30:02+02:00
[INFO] Final Memory: 39M/466M
[INFO] ------------------------------------------------------------------------

cURL

If you want you can call endpoint with cURL to check if everything works. Check on which port server is running:

./control.sh show servicePort welcome-servlet-microservice
servicePort: 20030

And call our microservice to check if everything works.

curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"Piotr\"}" localhost:20030/getWelcomeMessage

As a result you should see:

{"message":"Hello Piotr!"}

Done tutorial

You can download project which is result of making this tutorial from GitHub: https://github.com/jlupin/creating-servlet-microservice-jlns-1404

RATE & DISCUSS (0)

No comments found.