Spring

연습용 주문 서버 만들기 01 Kotlin/Spring 멀티모듈 프로젝트 구성

voider 2022. 12. 22. 22:38

연습용 주문 서버 만들기

Kotlin + Springboot로 주문 서버를 만드는 연습을 해보겠습니다.
최대한 간략하게 구성하기 위해 아래 네 개의 API만 만들겠습니다.

  • 아이디 발급 API
    • 편의상 회원가입 없이 아이디 발급 요청하면 임의의 아이디 발급
  • 메뉴 조회 API
  • 포인트 충전 API
  • 결제 API
  • 이번주 인기 메뉴 API

요구 사항

  1. 누구나 고유한 아이디를 발급 받을 수 있습니다.
  2. 누구나 메뉴를 조회할 수 있다. (메뉴 등록은 안 만들겠습니다.)
  3. 고유한 아이디에 포인트를 충전할 수 있다.
    • 한 아이디를 여러 명이서 쓸 수도 있기 때문에 동시성을 고려해보겠습니다.
  4. 고유한 아이디에 충전된 포인트로 결제를 할 수 있다.
    • 3번과 마찬가지로 한 아이디를 여러 유저가 사용할 수 있다고 가정하고 동시성을 고려하겠습다.
  5. 이번주 가장 많이 팔린 메뉴 상위 5개를 조회할 수 있다..

구현

사용 기술

Springboot3.0, Kotlin1.7, Kotlin-Gradle

구성

레이어 간 역할에 충실하기 위해 멀티 모듈 프로젝트로 구성하겠습니다. 만들면서 바꿀 수도 있을 것 같지만 필요한 모듈을 아래 네 가지 정도가 될 것 같습니다.

  • Web API
  • 비즈니스 로직 + DB
  • 스케쥴링
  • Redis
    바로 개발하는 데 필요한 Api, Core 모듈을 먼저 만들고 나머지는 필요할 때 만들겠습니다. 만들면서 구성이 바뀔 수도 있을 것 같습니다.

이 글에서 다룰 내용은 멀티 모듈 프로젝트 구성입니다. 일단 api, core 모듈 두 개만 만들어서 실행시켜보는 것까지 해보겠습니다. 아래와 같은 순서로 작성했습니다.

  1. 프로젝트 생성
  2. Api 모듈 추가
  3. Core 모듈 추가
  4. Root Gradle 설정
  5. 서브 모듈 Gradle 설정
  6. settings.gradle에 서브 모듈 추가

프로젝트 생성

IntelliJ에서 Spring Project를 하나 생성해줍니다. 아직 어떤 의존성도 없어도 되지만 저는 Configuration Processor만 추가했습니다.

멀티 모듈로 구성할 것이므로 루트 아래에 생성된 src 디렉터리 사용하지 않습니다. 그냥 지워줍니다.

Api 모듈 추가

루트 아래에 바로 새 모듈을 생성해줍니다.

order-api 라는 이름으로 새 모듈을 만듭니다.

Web 요청과 관련된 작업만 처리할 것이기 때문에 web 디펜던시만 추가합니다.

비즈니스 로직을 처리하는 core 모듈을 추가하겠습니다.

core 모듈 추가

order-core 라는 이름으로 MySQL과 테스트용 H2 디펜던시를 추가했습니다.

core 모듈은 비즈니스 로직을 처리하는 레이어와 데이터베이스에 접근하는 레이어를 관리합니다. 비즈니스 로직과 데이터에 접근하는 레이어를 분리하여 별도의 모듈로 만들 수도 있을 것입니다. 하지만 연습삼아 만드는 프로젝트이기 때문에 편의상 합쳐서 구성하겠습니다.

Root Gradle 설정

이렇게 멀티 모듈 프로젝트를 생성했으면 Gradle 설정을 해야 합니다. Gradle설정 전략으로는 여러 방식이 있는 것 같습니다. Root 디렉터리에 있는 Gradle 파일에서 subproject에 사용할 디펜던시를 한 번에 관리할 수도 있다고 합니다. 하지만 여기서는 Root 그레이들에서는 공통적으로 사용하는 디펜던시만 관리하고 각각 모듈에서 사용하는 디펜던시는 해당 모듈에 따로 추가하도록 하겠습니다.

일단 처음 root gradle 파일은 아래와 같은 상태입니다.

 

변경된 gradle 설정

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
      /*
        apply false를 한 이유
        루트 레벨에는 코드가 없기 때문에 불필요한 플로그인을 적용할 필요가 없음
        또한 apply false 안 하면 빌드 시 컴파일 에러 발생함.
      */
    id("org.springframework.boot") version "3.0.0" apply false
    id("io.spring.dependency-management") version "1.1.0"
    kotlin("jvm") version "1.7.21"
      // 위와 동일한 이유로 apply false
    kotlin("plugin.spring") version "1.7.21" apply false
    kotlin("plugin.jpa") version "1.7.21"
}

// 모든 하위 모듈에서 적용되는 설정
allprojects {
    group = "com.order"
    version = "0.0.1-SNAPSHOT"
    repositories {
        mavenCentral()
    }
}

java.sourceCompatibility = JavaVersion.VERSION_17

// 서브 모듈에서 사용하는 디펜던시
subprojects {
    apply(plugin = "org.jetbrains.kotlin.jvm")
    apply(plugin = "org.jetbrains.kotlin.plugin.jpa")
    apply(plugin = "org.jetbrains.kotlin.plugin.spring")
    apply(plugin = "org.springframework.boot")
    apply(plugin = "io.spring.dependency-management")

    dependencies {
        implementation("org.springframework.boot:spring-boot-starter-validation")
        implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
        implementation("org.jetbrains.kotlin:kotlin-reflect")
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

          // mockito는 사용하지 않을 것이므로 exclude.
        testImplementation("org.springframework.boot:spring-boot-starter-test") {
            exclude(module = "mockito-core")
        }

        annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
    }

    tasks.withType<KotlinCompile> {
        kotlinOptions {
            freeCompilerArgs = listOf("-Xjsr305=strict")
            jvmTarget = "17"
        }
    }

    tasks.withType<Test> {
        useJUnitPlatform()
    }

      // springboot 2.5버전부터 jar파일이 두 개 생성됨.
      // root 프로젝트는 실행 가능한 jar를 사용하지 않기 때문에 생성하지 않음
    // 오직 jar 하나만 생성하도록 설정.
    tasks.getByName("bootJar") {
        enabled = false
    }
    tasks.getByName("jar") {
        enabled = true
    }
}

서브 모듈 gradle 설정

메인 루트에 있는 gradle 설정을 마쳤으면 이제 서브 모듈에 있는 gradle을 설정해주면 됩니다.

order-api 모듈 gradle

기존 설정을 모두 지우고 필요한 디펜던시만 넣으면 됩니다. 기본 설정들을 root gradle에서 해주고 있기 때문에 정말 필요한 디펜던시만 추가할 수 있습니다.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation(project(":order-core"))
}


// root gradle 설정과 반대로
// api 모듈을 통해 프로젝트를 부트스트랩 시킬 것이므로,
// bootJar를 생성하고 일반 jar를 생성하지 않도록 했다.
tasks.getByName("bootJar") {
    enabled = true
}

tasks.getByName("jar") {
    enabled = false
}

order-core 모듈을 추가해줬는데, api 모듈에서 core 모듈로 요청을 위임해야 하기 때문에 의존성을 추가했습니다.

order-core 모듈 gradle

Core 모듈 디펜던시에는 JPA와 데이터베이스 커넥터를 추가해줍니다.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa:3.0.0")

    runtimeOnly("com.h2database:h2")
    runtimeOnly("com.mysql:mysql-connector-j")
}

이렇게 하면 gradle 설정은 끝입니다.

settings.gradle에 서브 모듈 추가

마지막으로 root 디렉터리에 settings.gradle.kts 파일에 서브 모듈 프로젝트 이름을 지정해줘야 합니다.

settings.gradle.kts

rootProject.name = "order-server"

include(
    "order-api",
    "order-core"
)

이제 gradle 빌드를 해주면 되는데, 그 전에 intelliJ gradle 탭을 보시면 자동으로 생성된 gradle 프로젝트들이 있습니다. 이것들을 모두 unlink 해준 다음 루트 gradle에서 다시 link 작업을 해주면 됩니다.

Gradle이 모두 빌드되면 아래와 같이 루트 모듈 아래 서브 모듈이 생성됩니다.

이제 멀티 모듈 구성이 끝났습니다.
api 모듈에 있는 부트스트랩 클래스를 실행시키면 서버가 정상 실행됩니다.

이상 멀티 모듈 구성 프로젝트 구성 끝입니다.
어떤 기능부터 작성할지 좀더 생각해보고 다음 글을 작성하겠습니다.

멀티 모듈을 구성하며 참고했던 자료를 남겨두겠습니다.
https://www.youtube.com/@Team_DODN/videos
멀티모듈 설계 이야기 with Spring, Gradle | 우아한형제들 기술블로그
SpringBoot 2.5↑ 빌드 시 2가지 jar가 생성되는 현상 (executable jar & plain jar) — ROOPRETELCHAM