When we develop micro services, many times we need to develop it in different environments. For testing while developing we would like to have a local deployment where many things can be hardcoded and lean . For example, if we want to use a database we might want to choose H2 or Ignite for local development. For a real production environment depending on the cloud platform or cloud provider there could be platform specific code. It's better to keep the main application logic which is not platform dependent separate and write it as a separate module. Then write a separate module for each platform where the platform specific code goes. The platform specific modules will resule the module that contains the business logic of the application. To achieve that we need to learn how we can create a multi module spring boot application. I am using spring boots as an example because nowadays it has become the de facto standard. And I will show step by step how to build such a multi module maven project and we will refer to a scenario that will help us build the case.
Contents
The Scenario
We will create a web application using spring boot. We will create a base module that will have most of the code required to create REST controllers and provide a skeleton (interface) that the dependent modules have to implement. The dependent modules have to implement the interface and extend the code from the base module to create their REST controller(s).
You can use any IDE like eclipse or intelliJ. I have used intelliJ IDE for my own convenience. The project structure will look like as shown in the figure beside.
The IDE helps you create a multi module project, you can also take help of different archetypes present. I have not used any archetypes and just used the capability of intelliJ to create a multi module project.
Code
The pom.xml (main project)
The content of this pom is
<groupId>com.me.spring.mm</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<name>my-app</name>
<description>my multi module app</description>
<packaging>pom</packaging>
<modules>
<module>app-base</module>
<module>app-module1</module>
</modules>
The pom.xml(app-base)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/>
</parent>
<groupId>com.me.spring.mm</groupId>
<artifactId>app-base</artifactId>
<version>1.0-SNAPSHOT</version>
<name>app-base</name>
<description>base module</description>
<properties>
<java.version>1.8</java.version>
<app-version>1.0-SNAPSHOT</app-version>
</properties>
<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.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
The pom.xml (app-module1)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/>
</parent>
<groupId>com.me.spring.mm</groupId>
<artifactId>app-module1</artifactId>
<version>1.0-SNAPSHOT</version>
<name>app-module1</name>
<description>module1</description>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.me.spring.mm</groupId>
<artifactId>app-base</artifactId>
<version>${project.version}</version>
<type>jar</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.1</version>
</plugin>
</plugins>
</build>
Code in app-base module
The base controller that has the read code that will execute when the main controller in the dependent modules will be executed. This class has to be extended in the controllers by the dependent module. So you can see the common code resides in the base module.
package com.me.spring.mm;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.HashMap;
import java.util.Map;
public abstract class BaseController {
private final BaseMessenger baseMessenger;
public BaseController( BaseMessenger baseMessenger ) {
this. baseMessenger = baseMessenger;
}
@GetMapping("/message")
public ResponseEntity<String> getMessage() {
Map<String, Object> message = new HashMap<String, Object>();
message.put("custom_message", baseMessenger.message() );
return new ResponseEntity(message, HttpStatus.OK);
}
}
In the above class we have a reference to the BaseMessenger interface. The base module only defines the interface and the corresponding methods. The real implementation has to be provided by the dependent modules.
package com.me.spring.mm;
public interface BaseMessenger {
String message();
}
Code in app-module1 module
The class that contains the main method and is configured to be the spring boot application’s main class.
package com.me.spring.mm;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
public class Module1App extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run( Module1App.class, args);
}
}
Now we implement the interface of the app-base module in the Module1Messenger class and provide a concrete method implementation.
package com.me.spring.mm;
public class Module1Messenger implements BaseMessenger {
public String message() { return "Module1";}
}
To create an object of this class we will create a bean in a spring boot configuration class Module1Conf.
package com.me.spring.mm;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Module1Conf {
@Bean
public Module1Messenger module1Messenger() {
return new Module1Messenger();
}
}
Finally, we extended the BaseController controller of the app-base module and created a controller Module1Controller in the app-module1.Note that we have auto wired the Module1Messenger object created as a bean , and passed the reference to the parent class BaseController.
package com.me.spring.mm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Module1Controller extends BaseController {
private final Module1Messenger module1Messenger;
@Autowired
public Module1Controller ( Module1Messenger module1Messenger ) {
super(module1Messenger);
this. module1Messenger = module1Messenger ;
}
}
Finally the index.html file that will be delivered when the base path is hit
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>my multi module app - Module1</title>
</head>
<body> <h1>Hello from Module1</h1> </body>
</html>
In the application.properties file we just mention the port that has to be used by spring boot instead of the default port.
server.port = 8081
How to compile a multi module spring boot project using maven?
It's pretty simple, we need to go to the base directory of the project and issue the below command, for example
we will install the modules.
C:\<path>\myApp>mvn clean install
Here is an example of the output of a successful install.
[INFO] Scanning for projects...
[INFO] ----------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] app-base [jar]
[INFO] app-module1 [war]
[INFO] my-app [pom]
[INFO]
[INFO] ---------------------< com.me.spring.mm:app-base >--------------
[INFO] Building app-base 1.0-SNAPSHOT [1/3]
[INFO] --------------------------------[ jar ]-------------------------
. . .
[INFO] --------------------< com.me.spring.mm:app-module1 >------------
[INFO] Building app-module1 1.0-SNAPSHOT [2/3]
[INFO] --------------------------------[ war ]-------------------------
. . .
[INFO] --- maven-war-plugin:3.3.1:war (default-war) @ app-module1 ---
[INFO] Packaging webapp
. . .
[INFO] Processing war project
[INFO] Building war: <path>\myApp\app-module1\target\app-module1-1.0-SNAPSHOT.war
[INFO]
[INFO] --- spring-boot-maven-plugin:2.4.1:repackage (repackage) @ app-module1 ---
[INFO] Replacing main artifact with repackaged archive
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ app-module1 ---
. . .
[INFO]
[INFO] ----------------------< com.me.spring.mm:my-app >---------------
[INFO] Building my-app 1.0-SNAPSHOT [3/3]
[INFO] --------------------------------[ pom ]-------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ my-app ---
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ my-app
[INFO] Installing . . .
[INFO] ----------------------------------------------------------------
[INFO] Reactor Summary for my-app 1.0-SNAPSHOT:
[INFO]
[INFO] app-base ........................................... SUCCESS [ 2.763 s]
[INFO] app-module1 ........................................ SUCCESS [ 2.083 s]
[INFO] my-app ............................................. SUCCESS [ 0.195 s]
[INFO] ----------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ----------------------------------------------------------------
How to run the web application ?
Now we will go inside the module which has the main() method. As in our case the app-module1 has this we will go inside the app-module1 directory and issue the run command
<path>\myApp\app-module1>mvn spring-boot:run
You will see the application started and running. The output will look like this.
. . .
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.1)
2021-05-30 12:40:40.626 INFO 48632 --- [main] com.me.spring.mm.Module1App : Starting Module1App using Java 12.0.1 on ...\myApp\app-module1\target\classes started by dibyo in <path>\myApp\app-module1)
2021-05-30 12:40:40.628 INFO 48632 --- [main] com.me.spring.mm.Module1App : No active profile set, falling back to default profiles: default
2021-05-30 12:40:41.400 INFO 48632 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http)
2021-05-30 12:40:41.413 INFO 48632 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-05-30 12:40:41.413 INFO 48632 --- [main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.41]
2021-05-30 12:40:41.490 INFO 48632 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-05-30 12:40:41.490 INFO 48632 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 824 ms
2021-05-30 12:40:41.628 INFO 48632 --- [main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-05-30 12:40:41.693 INFO 48632 --- [main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page: class path resource [static/index.html]
2021-05-30 12:40:41.759 INFO 48632 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path ''
2021-05-30 12:40:41.768 INFO 48632 --- [main] com.me.spring.mm.Module1App : Started Module1App in 1.479 seconds (JVM running for 1.744)
Note that it shows the host, port, base path and path of the index.html
How to test the application?
Now go you a preferred browser and hit the endpoints
You will see Hello from Module1
http://localhost:8081/message
And it will return
{"custom_message":"Module1"}
Comments