[Spring Boot] Multi Module
Spring BootMulti-module single project
Organize projects independent of each other into modules and combine them into one project.
Modules
Independent components that exist within a large system
The system constituting the program is separated into functional units and made independent.
Responsibilities and roles are clear.(Single Liability Principle)
It has minimal dependence.(Prevent unnecessary movements and spaghetti codes)
Multi-module
Package collection
Groups that can reuse associated packages and resources
Advantages
Reusable
- Ensure the identity of the domain structure and rules.
- Required features can be taken from other modules and used.
- Minimize duplicate code.
Increase productivity
- Since the functions are separated for each module, it is possible to predict the extent to which the functions affect.
- No understanding of the entire project is required.
- There is no need to know which controller and service the domain model leads to.
- When a bug or change occurs, build redistribute only the corresponding module without the need to build the entire project.(Reduce build time)
Minimize dependency
- Since the degree of coupling is low, the influence of the change may be minimized.
Easy to change structure
- When you change the structure to MSA (Microservice Architecture), you just have to make each module a separate server.
- It is easy to change to Monolithic Architecture.
Accessibility
- You don't have to run IDE on a project-by-project basis.
Weakness
Configuration file and package dependency management are required.
- Care should be taken in the use of common modules.
- The application may become heavy due to unnecessary dependence in the reference module.
기본 구조
root project
├── app
│ └── src
│ └── main
│ ├── kotlin
│ │ └── com.dev.app
│ │ ├── AppApplication.java
│ │ ├── controller
│ │ │ └── UserController.java
│ │ └── service
│ │ └── UserService.java
│ └── resources
│ └── application.yml
├── batch
│ └── src
│ └── main
│ ├── kotlin
│ │ └── com.dev.batch
│ │ ├── BatchApplication.java
│ │ └── batchjob
│ │ └── SimpleBatchJob.java
│ └── resources
│ └── application.yml
└── domain
└── src
└── main
├── kotlin
│ └── com.dev.domain
│ ├── domain
│ │ └── User.java
│ └── repository
│ └── UserRepository.java
└── resources
└── application.yml
Organize Client, App, and Batch into modules that were divided into projects
root project
├── app
├── client
├── batch
└── domain
Example
1. Create Root Project (Spring Initializr, Kotlin, Gradle)
[root] build.gradle
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.7.5" apply false
id("io.spring.dependency-management") version "1.0.15.RELEASE" apply false
kotlin("jvm")
kotlin("plugin.spring") apply false
kotlin("plugin.allopen") apply false
kotlin("plugin.noarg") apply false
kotlin("kapt") apply false
}
allprojects {
group = "com.example"
version = "0.0.1-SNAPSHOT"
repositories {
mavenCentral()
}
}
subprojects {
apply {
plugin("java")
plugin("kotlin")
plugin("kotlin-jpa")
plugin("org.springframework.boot")
plugin("io.spring.dependency-management")
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
}
configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = JavaVersion.VERSION_17.toString()
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
}
project(":dev-domain") {
apply(plugin = "kotlin-spring")
apply(plugin = "kotlin-kapt")
}
project(":dev-app") {
apply(plugin = "kotlin-spring")
dependencies {
implementation(project(":dev-domain"))
}
}
- pugins: set apply false so that plugins are not applied immediately to the subproject
- allprojects: apply to all projects including rootProject
- subprojects: Apply to subprojects included in setting.gradle
- project: Setting dependencies for specific subprojects included in setting.gradle
- Sometimes build.gradle uses compile(v6) and api(v7) to inherit the dependence of the module, which is not recommended because it increases the dependence.
[root] gradle.properties
kotlinPluginVersion=1.6.21
[root] settings.gradle
pluginManagement {
val kotlinPluginVersion: String by settings
plugins {
kotlin("jvm") version kotlinPluginVersion
kotlin("plugin.spring") version kotlinPluginVersion
kotlin("plugin.allopen") version kotlinPluginVersion
kotlin("plugin.noarg") version kotlinPluginVersion
kotlin("kapt") version kotlinPluginVersion
}
}
rootProject.name = "multi-module"
2. Deleting unnecessary src folders
Root projects are responsible for managing modules
3. Create Domain Module
When creating a Domain Module, you need to think about whether to have only JpaRepository or QueryDsl.
- JpaRepository only: I need an intermediate layer module that connects to each apiYou can have minimal dependency.
- Create JpaRepository + QueryDsl: queries that depend on a particular module are brought together, causing the Domain Module to become bloated.
If multiple infrastructures are used (e.g., RDBMS + Redis), they can be written as independent modules to keep the principle of single responsibility, and a Domain Service Module with independent modules is created and used.
[dev-domain] build.gradle
description = "Domain Module"
allOpen {
annotation("javax.persistence.Entity")
annotation("javax.persistence.Embeddable")
annotation("javax.persistence.MappedSuperclass")
}
noArg {
annotation("javax.persistence.Entity")
annotation("javax.persistence.Embeddable")
annotation("javax.persistence.MappedSuperclass")
}
dependencies {
implementation("org.springframework.boot:spring-boot-configuration-processor")
implementation("com.querydsl:querydsl-jpa")
kapt("com.querydsl:querydsl-apt::jpa")
implementation("org.springframework.boot:spring-boot-starter-jdbc")
implementation("mysql:mysql-connector-java")
}
tasks.getByName<org.springframework.boot.gradle.tasks.bundling.BootJar>("bootJar") {
enabled = false
}
tasks.getByName<Jar>("jar") {
enabled = true
}
Modules that do not require Main Class are set to generate only plain jar that does not contain dependencies.
4. Create App Module
[dev-app] build.gradle
description = "App Module"
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
developmentOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
[dev-app] Main Class
Set the entry and repository bean on the Domain Module to be found because the parent package was created differently for each module.
- Method 1: Using @ComponentScan
@SpringBootApplication
@ComponentScan(basePackages = ["com.dev.domain"])
class DevAppApplication
- Method 2: @EntityScan, @EnableJpaRepositories. You must write a package twice.
@SpringBootApplication
@EntityScan(basePackages = ["com.dev"])
@EnableJpaRepositories(basePackages = ["com.dev"])
class DevAppApplication
5. Confirm submodule
[root] settings.gradle
...
rootProject.name = "multi-module"
include("dev-domain")
include("dev-app")
The module is automatically set.
6. Module-specified build
Submodules are also built.
$ ./gradlew :dev-app:build -x test
Reference
https://techblog.woowahan.com/2637/
https://cjw-awdsd.tistory.com/55
https://kotlinworld.com/324
https://jinhanchoi1.medium.com/spring-boot-gradle-plugin-multi-module-build-bootjar-false-5e53a1f6224c
https://hyeon9mak.github.io/woowahan-multi-module/
https://do-study.tistory.com/122
https://kotlinworld.com/317
https://bokyung.dev/2022/03/23/kotlin-gradle-querydsl/
https://velog.io/@cha-sung-soo/Multi-Module-사용-이유
https://wildeveloperetrain.tistory.com/183
https://www.inflearn.com/questions/636766/멀티-모듈에-대한-질문이-있습니다