General overview
This article is based on Git Submodules (Git tools) and real implementation in a production-grade application with several µ-monoliths calling each other via REST APIS. So, in this article, we’ll see a simplified example of a Spring RestTemplate modularization in the Api Client submodule.
Git Submodules allows you to modularize your application components into modules with some pros and cons.
PROS
- Versability to modify main and submodules code in the same (IDE) project and commit/push to each remote repository separately.
- Enforces the Single Responsibility Principle decoupling specialized code into submodules from parent projects. So, removes duplications.
- No need to create and publish a Nexus artifact jar file.
CONS
- As a submodule/library for several main projects, compatibility it’s a must. There are some workarounds that can be applied like branching incompatible versions and/or using several pom-parentproject.xml in the submodule as explained later.
In addition, a git merge-request pipeline in the submodule should run all parent git repository pipelines to cover possible compilation and integration errors.
Pre-requisites
- Git
- IDE (Optional)
- Maven 3.6
- JDK11
Initial setup
Git repositories
Examples created for this article:
How to do for new projects from scratch
Steps to set up NEW projects with a common git submodule:
- Create Git repositories for all projects and submodules
- Clone mss1 and mss2 repositories and add submodule in each:
git submodule init
git submodule add -b main https://github.com/davidgfolchApium/gitsubmodule-apiclient
Clone example
Once we have the git repositories created and linked with the submodule, we can just clone the parent repository this way:
mkdir git-submodules && cd git-submodules/
git clone --recurse-submodules https://github.com/davidgfolchApium/gitsubmodule-mss1
git clone --recurse-submodules https://github.com/davidgfolchApium/gitsubmodule-mss2
Maven dependencies
I’m going to explain the “child pom per parent module” strategy to solve the problem when parent projects have different library versions (Spring-Boot versions f.ex.).
As you can see in the parent mss1 pom.xml, it’s using spring-boot-starter-parent 2.6.7 and in the modules section it’s pointing to api-client api-client/pom-mss1.xml and the api implementation module (that is a maven module but not a git submodule):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.apiumhub.articles.git-submodules</groupId>
<artifactId>mss1-parent</artifactId>
<version>1.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>mss1 parent</name>
<description>Parent mss1 git project using api-client git submodule</description>
<modules>
<module>api</module>
<module>gitsubmodule-apiclient/pom-mss1.xml</module>
</modules>
<properties>
<java.version>1.11</java.version>
<maven-compiler-plugin-java.version>11</maven-compiler-plugin-java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.2.2.RELEASE</spring-boot.version>
<start-class>com.apiumhub.articles.gitsubmodules.Application</start-class>
<lombok.version>1.18.16</lombok.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
</properties>
In addition, defines common dependencies and versions.
In the api maven module we have the parent reference and the api-client dependency
<parent>
<groupId>com.apiumhub.articles.git-submodules</groupId>
<artifactId>mss1-parent</artifactId>
<version>1.1.0-SNAPSHOT</version>
</parent>
<artifactId>api</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.apiumhub.articles.git-submodules</groupId>
<artifactId>api-client-mss1</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
And the submodule api-client/pom-mss1.xml says that mss1 pom.xml is it’s parent pom. So, it heritages all from both parents: Spring Boot maven parent module -> git-sumodules-mss1, and there is no need to put versions:
<parent>
<groupId>com.apiumhub.articles.git-submodules</groupId>
<artifactId>mss1-parent</artifactId>
<version>1.1.0-SNAPSHOT</version>
</parent>
<artifactId>api-client-mss1</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
Decoupled org.springframework.web.client package
Checkout that the dependency of org.springframework.web.client package is only used (and should be only used) in the submodule. This completely decouples the implementations on this package in the base projects and it could be changed easily, for example, with a new WebFlux implementation of ApiClient.
In some cases like integration or feature tests, where you need to mock RestClient , should be also implemented in the api-client submodule test/src/.. folder to keep source code decoupled from main the mssX.
Working with submodules
We will have a project like that imported on intellij with the mmsX , api and api-client maven modules:
Just to show another example, committing changes in Intellij will look like this:
We can see the parent maven module & the child modules. In this case, all are using the main branch from git, so Intellij doesn’t show any branch information, otherwise, you will see branch info in place.
In the push operations, we just see the 2 git repos for this mss:
Running the application
Run both mss1 and mss2 as any Spring Boot application. In the main folder of each mss compile and run:
mvn clean install
cd api
mvn spring-boot:run
NOTE: application.properties -> server.port not working in base parent modules, because the RestTemplate @Bean is defined in the child maven submodule and Spring is trying to get the properties files from it’s classpath, so a ServerPortCustomizer.java is needed to set ports.
In the mss1 and mss2, we have the same endpoints:
http://localhost:8081/user/info
http://localhost:8081/bank/info
http://localhost:8081/api/client/exception
http://localhost:8082/user/info
http://localhost:8082/bank/info
http://localhost:8082/api/client/exception
As you can check on the implementation mss1 (in port 8081) is calling to mss2 (8082) via ApiClient on the /bank/info endpoint, and mss2/user/info is calling to mss1/user/info.