스프링부트 멀티모듈 구성하기(1) - 프로젝트 구조만들기 Spring Boot Multi-Module Setup (1) - Creating Project Structure
Before Starting the Project
While working on project sprints 1 and 2 using SpringBoot, I ended up creating some really basic but fundamental features.
This ongoing project could consist of one API server, one or more Consumer Applications, and potentially a batch server in the future.
Since the project was moving quickly, I set up the backend API server and Stream application server separately.
The features worked well and the sprint report went smoothly, but when I looked at the source code, I noticed entities were being duplicated.
I thought about applying multi-module to share entities commonly and use other unique features in each server.
Rather than looking at all the settings at once, I wanted to try things one by one and verify functionality while approaching the overall structure.
While reading, you might wonder “why is it like that?” - I understand because I had a lot of concerns while writing this too.
Rather than saying “each part does its appropriate role,” I wanted to show how to configure modules, and hopefully it’ll be useful to think about and apply when developing later.
Configuring the Project
-
Project Structure

- SpringBoot-Multimodules - module-api - module-core - module-stream- module-api
- API Server
- Creates and retrieves current users via API calls
- Retrieves orders received via API calls to provide to the Admin page
- module-core
- Defines Entities for querying in the API server
- Defines Entities for use in the Stream server
- module-stream
- Stream Server
- Uses Kafka to send API requests to the order topic (Producer)
- Receives orders sent to the Stream and saves them to DB (Consumer)
- module-api
Setting Up the Project
-
Create SpringBoot-Multimodules.
-
SpringBoot-Multimodules can be created as a Spring application or as a gradle project.

-
I created it with SpringBoot and received the basic dependencies.
dependencies { // springboot implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") developmentOnly("org.springframework.boot:spring-boot-devtools") // kotlin implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") // DB implementation("org.postgresql:postgresql:42.3.3") // test testImplementation("org.springframework.boot:spring-boot-starter-test") } -
Since SpringBoot-Multimodules is a project that manages individual modules, dependencies are managed in each module.
-
-
Create gradle projects.
- Each module is created as a gradle project.
- In my case, I created
module-api,module-core, andmodule-stream. 
-
Clean up the folders.
- Although SpringBoot-Multimodules is the root project, since its main job is dependency management, delete all subfolders under src
- Only
module-api,module-core,module-stream, and the gradle folder remain in SpringBoot-Multimodules.
-
Install the database locally. (In my case, I used docker-compose to run postgresql)
-
Place docker-compose.yml in the root and run with the
docker-compose up -dcommand.version: "3.9" services: postgres: image: postgres:14-alpine container_name: multimodule-postgres ports: - "9876:5432" volumes: - .postgresql/:/var/lib/postgresql/data - ./local-db/init_schema.sql:/docker-entrypoint-initdb.d/1-schema.sql environment: - POSTGRES_PASSWORD=password1234 - POSTGRES_USER=wool - POSTGRES_DB=wooldb - Create a local-db folder and write init_schema.sql.
create schema springtest; - Run with the
docker-compose up -dcommand.
-
- After completing this, the folder structure looks like the image below

Working with Gradle
- Since multiple projects are combined into one, we need to inform Gradle, our build system.
Working with Gradle on the Root Project (SpringBoot-Multimodule)
-
Inform the root project’s
settings.gradle.ktsabout the modules// settings.gradle.kts rootProject.name = "SpringBoot-Multimodules" include("module-stream") include("module-api") include("module-core") - After completing work on the root project, modify
build.gradle.kts. -
My configuration is as below - refer to the source and check how it differs for each project to apply accordingly.
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { id("org.springframework.boot") version "2.7.5" id("io.spring.dependency-management") version "1.0.15.RELEASE" kotlin("jvm") version "1.6.21" kotlin("plugin.spring") version "1.6.21" apply false kotlin("plugin.jpa") version "1.6.21" apply false } java.sourceCompatibility = JavaVersion.VERSION_17 allprojects { group = "com.example" version = "0.0.1-SNAPSHOT" repositories { mavenCentral() } } subprojects { apply(plugin = "java") apply(plugin = "io.spring.dependency-management") apply(plugin = "org.springframework.boot") apply(plugin = "org.jetbrains.kotlin.plugin.spring") apply(plugin = "kotlin") apply(plugin = "kotlin-spring") //all-open apply(plugin = "kotlin-jpa") dependencies { // springboot implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") developmentOnly("org.springframework.boot:spring-boot-devtools") // kotlin implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") // DB implementation("org.postgresql:postgresql:42.3.3") // test testImplementation("org.springframework.boot:spring-boot-starter-test") } dependencyManagement { imports { mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) } dependencies { dependency("net.logstash.logback:logstash-logback-encoder:6.6") } } tasks.withType<KotlinCompile> { kotlinOptions { freeCompilerArgs = listOf("-Xjsr305=strict") jvmTarget = "17" } } tasks.withType<Test> { useJUnitPlatform() } configurations { compileOnly { extendsFrom(configurations.annotationProcessor.get()) } } } // module-api and consumer depend on module-core project(":module-api") { dependencies { implementation(project(":module-core")) } } project(":module-stream") { dependencies { implementation(project(":module-core")) } } // core configuration project(":module-core") { val jar: Jar by tasks val bootJar: BootJar by tasks bootJar.enabled = false jar.enabled = true }
Working with Gradle on module-core
-
Modify module-core’s
build.gradle.ktsas followsplugins{ } allOpen { annotation("javax.persistence.Entity") annotation("javax.persistence.Embeddable") annotation("javax.persistence.MappedSuperclass") } noArg { annotation("javax.persistence.Entity") // Apply no arg plugin only to classes with @Entity annotation("javax.persistence.Embeddable") annotation("javax.persistence.MappedSuperclass") } dependencies{ }
Working with Gradle on module-api
-
Modify module-api’s
build.gradle.ktsas followsplugins{ } dependencies{ }
Working with Gradle on module-stream
-
Modify module-stream’s
build.gradle.ktsas followsplugins{ } dependencies{ implementation("org.springframework.kafka:spring-kafka") implementation("org.springframework.boot:spring-boot-starter-data-jpa") }
Wrapping Up the Setup
- The setup took quite a while. I did a lot of trial and error.
- The important point is that when creating packages and modules, the “package structure” must be the same.
- For example, if you create a package as
com.wool, other modules must also be created ascom.wool.
- For example, if you create a package as
댓글남기기