4 분 소요


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)

Setting Up the Project

  1. 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.

  2. Create gradle projects.

    • Each module is created as a gradle project.
    • In my case, I created module-api, module-core, and module-stream.
  3. 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.
  4. 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 -d command.

      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 -d command.
  • 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.kts about 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.kts as follows

    plugins{
    
    }
    
    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.kts as follows

    plugins{
    
    }
    
    dependencies{
    
    }
    
    

Working with Gradle on module-stream

  • Modify module-stream’s build.gradle.kts as follows

    plugins{
    
    }
    
    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 as com.wool.

Reference

댓글남기기