Skip to content

๐Ÿ“ฆ ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ ๊ฐ€์ด๋“œ โ€‹

๊ตฌ์กฐ ๊ฐœ์š” โ€‹

mo-it/
โ”œโ”€โ”€ gradle/libs.versions.toml  # ์˜์กด์„ฑ ๋ฒ„์ „ ๊ด€๋ฆฌ
โ”œโ”€โ”€ build.gradle.kts           # ๊ณตํ†ต Gradle ์„ค์ • (subprojects ๋ธ”๋ก)
โ”œโ”€โ”€ settings.gradle.kts        # ๋ชจ๋“ˆ ์ •์˜
โ”œโ”€โ”€ common/                    # ๊ณตํ†ต ์œ ํ‹ธ, Exception, Response DTO
โ”œโ”€โ”€ core/                      # ๋„๋ฉ”์ธ Entity, Service, Port Interface
โ”œโ”€โ”€ api/                       # REST Controller
โ”œโ”€โ”€ persistence/               # JPA Entity, Repository ๊ตฌํ˜„
โ””โ”€โ”€ app/                       # Spring Boot ์‹คํ–‰

๊ณตํ†ต Gradle ์„ค์ • โ€‹

๋ชจ๋“  ๋ชจ๋“ˆ์˜ ๊ณตํ†ต ์„ค์ •์€ ๋ฃจํŠธ build.gradle.kts ์˜ subprojects ๋ธ”๋ก์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

kotlin
// build.gradle.kts (๋ฃจํŠธ)
subprojects {
    apply(plugin = "org.jetbrains.kotlin.jvm")
    apply(plugin = "org.jetbrains.kotlin.plugin.spring")
    apply(plugin = "io.spring.dependency-management")

    // ๊ณตํ†ต ์˜์กด์„ฑ
    dependencies {
        "implementation"("com.fasterxml.jackson.module:jackson-module-kotlin")
        "implementation"("org.jetbrains.kotlin:kotlin-reflect")
        "testImplementation"("org.jetbrains.kotlin:kotlin-test-junit5")
    }

    // ๊ณตํ†ต ์„ค์ • (Java 21, JUnit ๋“ฑ)
    configure<JavaPluginExtension> {
        toolchain { languageVersion.set(JavaLanguageVersion.of(21)) }
    }

    tasks.withType<Test> { useJUnitPlatform() }
}

์žฅ์ : ๊ฐ ๋ชจ๋“ˆ์˜ build.gradle.kts๊ฐ€ ๋งค์šฐ ๊ฐ„๊ฒฐํ•ด์ง‘๋‹ˆ๋‹ค.


์˜์กด์„ฑ ๋ฒ„์ „ ๊ด€๋ฆฌ (Version Catalog) โ€‹

gradle/libs.versions.toml ์—์„œ ์˜์กด์„ฑ ๋ฒ„์ „์„ ์ค‘์•™ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

toml
[versions]
kotlin = "1.9.25"
spring-boot = "3.2.3"

[libraries]
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" }

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }

์‚ฌ์šฉ๋ฒ•:

kotlin
// ๋ชจ๋“ˆ build.gradle.kts
dependencies {
    implementation(libs.spring.boot.starter.web)
}

๋ชจ๋“ˆ๋ณ„ ์—ญํ•  โ€‹

๋ชจ๋“ˆ์—ญํ• ํŒจํ‚ค์ง€
common๊ณตํ†ต Exception, Response DTOcom.moit.common.*
core๋„๋ฉ”์ธ, ์„œ๋น„์Šค, ํฌํŠธ ์ธํ„ฐํŽ˜์ด์Šคcom.moit.core.*
apiREST Controller, ์š”์ฒญ/์‘๋‹ต DTOcom.moit.api.*
persistenceJPA Entity, Repository ๊ตฌํ˜„์ฒดcom.moit.persistence.*
appSpring Boot Main, ์„ค์ • ํŒŒ์ผcom.moit

์˜์กด์„ฑ ํ๋ฆ„ โ€‹

app
 โ”œโ”€โ”€ api          โ”€โ”€โ†’ core โ”€โ”€โ†’ common
 โ”œโ”€โ”€ persistence  โ”€โ”€โ†’ core โ”€โ”€โ†’ common
 โ””โ”€โ”€ core         โ”€โ”€โ†’ common

๊ทœ์น™:

  • ํ™”์‚ดํ‘œ ๋ฐฉํ–ฅ์œผ๋กœ๋งŒ ์˜์กด ๊ฐ€๋Šฅ
  • common์€ ๋‹ค๋ฅธ ๋ชจ๋“ˆ์— ์˜์กดํ•˜์ง€ ์•Š์Œ
  • core๋Š” persistence๋ฅผ ๋ชจ๋ฆ„ (์ธํ„ฐํŽ˜์ด์Šค๋งŒ ์ •์˜)

์ƒˆ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ๊ฐ€์ด๋“œ โ€‹

1. ์ƒˆ ๋„๋ฉ”์ธ ์ถ”๊ฐ€ (์˜ˆ: User) โ€‹

kotlin
// 1๏ธโƒฃ core/domain/User.kt - ๋„๋ฉ”์ธ ๋ชจ๋ธ
data class User(
    val id: Long?,
    val email: String,
    val name: String
)

// 2๏ธโƒฃ core/port/UserRepository.kt - ํฌํŠธ ์ธํ„ฐํŽ˜์ด์Šค
interface UserRepository {
    fun findById(id: Long): User?
    fun save(user: User): User
}

// 3๏ธโƒฃ core/service/UserService.kt - ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
@Service
class UserService(private val userRepository: UserRepository) {
    fun getById(id: Long) = userRepository.findById(id)
}

2. Repository ๊ตฌํ˜„ (persistence ๋ชจ๋“ˆ) โ€‹

kotlin
// persistence/entity/UserEntity.kt
@Entity
class UserEntity(
    @Id @GeneratedValue
    val id: Long? = null,
    val email: String,
    val name: String
)

// persistence/repository/UserJpaRepository.kt
interface UserJpaRepository : JpaRepository<UserEntity, Long>

// persistence/repository/UserRepositoryImpl.kt
@Repository
class UserRepositoryImpl(
    private val jpaRepository: UserJpaRepository
) : UserRepository {
    override fun findById(id: Long) = jpaRepository.findByIdOrNull(id)?.toDomain()
    override fun save(user: User) = jpaRepository.save(user.toEntity()).toDomain()
}

3. Controller ์ถ”๊ฐ€ (api ๋ชจ๋“ˆ) โ€‹

kotlin
// api/controller/UserController.kt
@RestController
@RequestMapping("/api/users")
class UserController(private val userService: UserService) {
    @GetMapping("/{id}")
    fun getById(@PathVariable id: Long) = ApiResponse.success(userService.getById(id))
}

์˜์กด์„ฑ ์ถ”๊ฐ€ โ€‹

1. libs.versions.toml์— ์ถ”๊ฐ€ โ€‹

toml
[libraries]
spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security" }

2. ๋ชจ๋“ˆ์—์„œ ์‚ฌ์šฉ โ€‹

kotlin
// build.gradle.kts
dependencies {
    implementation(libs.spring.boot.starter.security)
}

ํ™•์žฅ ๊ฐ€์ด๋“œ โ€‹

infra ๋ชจ๋“ˆ ์ถ”๊ฐ€ (Redis, Kafka ๋“ฑ) โ€‹

  1. settings.gradle.kts์— ์ถ”๊ฐ€:
kotlin
include("infra")
  1. infra/build.gradle.kts ์ƒ์„ฑ:
kotlin
dependencies {
    implementation(project(":core"))
    implementation("org.springframework.boot:spring-boot-starter-data-redis")
}
  1. core ๋ชจ๋“ˆ์— ์บ์‹œ ํฌํŠธ ์ •์˜, infra์—์„œ ๊ตฌํ˜„

๋นŒ๋“œ & ์‹คํ–‰ โ€‹

bash
# ์ „์ฒด ๋นŒ๋“œ
./gradlew clean build

# ์•ฑ ์‹คํ–‰
./gradlew :app:bootRun

# ํŠน์ • ๋ชจ๋“ˆ ๋นŒ๋“œ
./gradlew :core:build

# ํ…Œ์ŠคํŠธ ์‹คํ–‰
./gradlew test

FAQ โ€‹

Q. ๊ณตํ†ต ์„ค์ •์€ ์–ด๋””์„œ ๊ด€๋ฆฌํ•˜๋‚˜์š”?

A. ๋ฃจํŠธ build.gradle.kts์˜ subprojects ๋ธ”๋ก์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Q. ์˜์กด์„ฑ ๋ฒ„์ „์€ ์–ด๋””์„œ ๊ด€๋ฆฌํ•˜๋‚˜์š”?

A. gradle/libs.versions.toml ํŒŒ์ผ์—์„œ ์ค‘์•™ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Q. ์ˆœํ™˜ ์˜์กด์ด ๋ฐœ์ƒํ–ˆ์–ด์š”

A. ๋ชจ๋“ˆ ์ˆœ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”: common โ†’ core โ†’ api/persistence โ†’ app

Q. ์ƒˆ ๋ชจ๋“ˆ์„ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด?

A. settings.gradle.kts์— include ์ถ”๊ฐ€ ํ›„, build.gradle.kts ์ƒ์„ฑ

Q. ์™œ core์™€ persistence๊ฐ€ ๋ถ„๋ฆฌ๋ผ ์žˆ๋‚˜์š”?

A. core๋Š” ์ˆœ์ˆ˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง, persistence๋Š” DB ๊ธฐ์ˆ ์— ์ข…์†. ๋‚˜์ค‘์— DB ๋ณ€๊ฒฝ ์‹œ core๋Š” ์ˆ˜์ • ์—†์Œ