
이번 포스트에서는 스프링 부트와 코틀린을 이용하여 간단한 웹 애플리케이션을 구축한 과정을
소개합니다.
본 애플리케이션은 사용자 등록 및 로그인 기능을 포함하고 있으며,
데이터베이스와의 연동, 보안 설정, 그리고 API 문서화까지 다루고 있습니다.
먼저 build.gradle.kts 파일을 설정하여 프로젝트의 의존성 및 플러그인을 관리합니다.
주요 설정은 다음과 같습니다.
plugins {
id("org.springframework.boot") version "3.3.1"
id("io.spring.dependency-management") version "1.1.5"
id("org.jetbrains.kotlin.jvm") version "1.9.24"
kotlin("kapt") version "1.9.24"
id("org.jetbrains.kotlin.plugin.spring") version "1.9.24"
}
group = "org.jc"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(19))
}
}
repositories {
mavenCentral()
jcenter() // Optional, if needed
}
dependencies {
// Kotlin dependencies
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// Spring Boot and related dependencies
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
// Database
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.mysql:mysql-connector-j")
runtimeOnly("com.h2database:h2")
// Lombok
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
testCompileOnly("org.projectlombok:lombok")
testAnnotationProcessor("org.projectlombok:lombok")
// Dev Tools
developmentOnly("org.springframework.boot:spring-boot-devtools")
// ModelMapper
implementation("org.modelmapper:modelmapper:3.2.0")
// Thymeleaf and Layout Dialect
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect")
// JPA QueryDSL
implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
annotationProcessor("com.querydsl:querydsl-apt:5.0.0:jakarta")
kapt("com.querydsl:querydsl-apt:5.0.0:jakarta")
kapt("org.springframework.boot:spring-boot-configuration-processor")
// Swagger
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0")
// Security
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
testImplementation("org.springframework.security:spring-security-test")
// OAuth2
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
// JWT
implementation("io.jsonwebtoken:jjwt-api:0.12.3")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.3")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.3")
// Crawling Library
implementation("org.jsoup:jsoup:1.17.2")
// Excel Library
implementation("org.apache.poi:poi-ooxml:5.2.5")
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(19))
}
compilerOptions {
freeCompilerArgs.add("-Xjsr305=strict")
}
}
tasks.named<Test>("test") {
useJUnitPlatform()
}
이 설정 파일은 다양한 의존성과 플러그인을 포함하고 있으며,
Kotlin과 Spring Boot의 최신 버전을 사용합니다.
데이터베이스와의 연동, 보안 설정, API 문서화 등 모든 필요한 기능을 포함하고 있습니다.
@Controller
@RequestMapping("/member")
class MemberController(
private val memberService: MemberService,
private val rq: ReqData
) {
@PreAuthorize("isAnonymous()")
@GetMapping("/join")
fun showJoin(): String {
return "domain/member/member/join"
}
@Validated
data class JoinForm(
@field:NotBlank val username: String,
@field:NotBlank val password: String
)
@PreAuthorize("isAnonymous()")
@PostMapping("/join")
fun join(@Valid joinForm: JoinForm): String {
val joinRs = memberService.join(joinForm.username, joinForm.password, "")
return rq.redirectOrBack(joinRs, "/member/login")
}
@GetMapping("/login")
fun showLogin(): String {
return "domain/member/member/login"
}
}
@Service
@Transactional(readOnly = true)
class MemberService(
private val memberRepository: MemberRepository,
private val passwordEncoder: PasswordEncoder
) {
@Transactional
fun join(username: String, password: String, role: String): RespData<Member> {
val existingMember = findByUsername(username)
if (existingMember != null) {
return RespData.of("400-2", "이미 존재하는 회원입니다.")
}
val roleType = when (username) {
"system", "admin" -> M_Role.ADMIN.authority
else -> M_Role.MEMBER.authority
}
val member = Member().apply {
this.username = username
this.password = passwordEncoder.encode(password)
this.roleType = roleType
}
memberRepository.save(member)
return RespData.of(
"200",
"${member.username}님 환영합니다. 회원가입이 완료되었습니다. 로그인 후 이용해주세요.",
member
)
}
fun findByUsername(username: String): Member? {
return memberRepository.findByUsername(username)
}
fun count(): Long {
return memberRepository.count()
}
}
@Configuration
class AppConfig {
@Value("\${custom.tempDirPath}")
lateinit var tempDirPath: String
@Value("\${custom.genFile.dirPath}")
lateinit var genFileDirPath: String
@Value("\${custom.site.name}")
lateinit var siteName: String
@Value("\${custom.site.baseUrl}")
lateinit var siteBaseUrl: String
companion object {
private var resourcesStaticDirPath: String? = null
@JvmStatic
fun getResourcesStaticDirPath(): String {
if (resourcesStaticDirPath == null) {
val resource = ClassPathResource("static/")
try {
resourcesStaticDirPath = resource.file.absolutePath
} catch (e: IOException) {
throw RuntimeException(e)
}
}
return resourcesStaticDirPath!!
}
}
}
이번 프로젝트를 통해 스프링 부트와 코틀린을 사용하여 웹 애플리케이션을 개발하는 과정에서
많은 것을 배웠습니다.
스프링 부트의 자동 설정과 코틀린의 간결한 문법 덕분에 개발 속도가 크게 향상되었습니다.
또한, JWT와 OAuth2를 이용한 인증 및 인가, Swagger를 통한 API 문서화 등 다양한 기능을
적용해 보면서 실제로 어떻게 이러한 기술들이 협력하여
전체 시스템을 구축하는지 경험할 수 있었습니다.
각 기능별로 세부적인 설정과 구현이 필요했지만,
이를 통해 프로젝트의 전반적인 이해도를 높일 수 있었고,
향후 더 복잡한 시스템을 설계하고 구현하는 데 필요한 기초를 다질 수 있었습니다.
앞으로도 이러한 기술들을 더 깊이 탐구하고,
새로운 도전 과제를 통해 더 나은 개발자가 되도록 노력하겠습니다.