티스토리 뷰
JetBrains에서 제작한 Ktor라는 프레임워크로 간단한 서버 어플리케이션 세팅을 하는 방법을 공유한다.
https://github.com/mopil/ktor-server
먼저 프로젝트 아키텍처와 기본설정들을 먼저 살펴본다.
먼저 Ktor Generator라는 웹사이트를 통해서 프로젝트를 생성하자.
Ktor는 스프링과 다르게 기본으로 제공해 주는 기능이 거의 없고 플러그인을 추가해서 커스터마이징 해야 한다.
일단 가장 기본이 되는 플러그인을 추가한다.
- Call Logging (로깅)
- Jackson (직렬화)
- Routing (API 핸들러, 스프링의 컨트롤러 개념)
- Status Pages (예외 처리, 스프링의 Exception Handler / Controller Advice)
- Swagger
- Exposed (ORM, 스프링의 JPA)
데이터베이스는 MySQL을 연동할 것인데 플러그인에는 없으므로 MySQL 드라이버는 gradle에 따로 추가해야 한다.
Ktor는 의존성 주입도 기본으로 제공하지 않기 때문에, Koin이라는 의존성 주입 라이브러리를 사용할 것이다. 우선 기본 플러그인에는 없으니 아래 gradle 설정을 참고하면 된다.
# build.gradle.kts
val ktorVersion: String by project
val kotlinVersion: String by project
val logbackVersion: String by project
val exposedVersion: String by project
val mysqlVersion: String by project
val hikariCpVersion: String by project
val koinVersion: String by project
plugins {
kotlin("jvm") version "1.8.22"
id("io.ktor.plugin") version "2.3.2"
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.22"
id("org.jlleitschuh.gradle.ktlint") version "11.5.0"
}
group = "com.example"
version = "0.0.1"
application {
mainClass.set("com.example.ApplicationKt")
val isDevelopment: Boolean = project.ext.has("development")
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
}
repositories {
mavenCentral()
}
dependencies {
implementation("io.ktor:ktor-server-core-jvm:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktorVersion")
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktorVersion")
implementation("io.ktor:ktor-server-config-yaml:$ktorVersion")
implementation("io.ktor:ktor-serialization-jackson-jvm:$ktorVersion")
implementation("io.ktor:ktor-server-call-logging-jvm:$ktorVersion")
implementation("io.ktor:ktor-server-swagger-jvm:$ktorVersion")
implementation("io.ktor:ktor-server-netty-jvm:$ktorVersion")
testImplementation("io.ktor:ktor-server-tests-jvm:$ktorVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion")
implementation("io.ktor:ktor-server-status-pages:$kotlinVersion")
implementation("mysql:mysql-connector-java:$mysqlVersion")
implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-java-time:$exposedVersion")
implementation("ch.qos.logback:logback-classic:$logbackVersion")
implementation("com.zaxxer:HikariCP:$hikariCpVersion")
implementation("io.insert-koin:koin-ktor:$koinVersion")
implementation("io.insert-koin:koin-logger-slf4j:$koinVersion")
}
application {
mainClass.set("ApplicationKt")
}
by project를 통해서 delegate 받는 값들은 루트 디렉토리에 위치한 gradle.properties에서 가져올 수 있다.
데이터베이스와 관련된 연동은 다른 글에서 더 자세히 다룰 예정이니 일단 넘어가자.
# application.conf
프로젝트를 초기에 생성하면 resources 디렉토리 내부에는 아무것도 없을 것이다.
로깅을 위한 logback.xml과 application.conf (앱 설정파일)을 생성해 준다.
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.eclipse.jetty" level="INFO"/>
<logger name="io.netty" level="INFO"/>
</configuration>
database {
datasource {
driver-class-name = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/test"
username = "root"
password: "1102"
max-pool-size = 10
}
reset-on-boot = true
set-dummy-data = true
}
environment = "dev"
application.conf는 스프링의 application.properties와 동일한 역할을 하는 파일이다.
Ktor에서는 conf와 yaml 두 확장자를 지원하는데 코드로 환경변수를 가져오려면 conf 파일로 설정하는 게 편하다 (yaml은 뭔가 잘 안 가져와진다)
conf 파일은 정해진 규칙은 없고, 개발자가 원하는 대로 커스터마이징 해서 사용하면 된다.
디폴트로 제공되는 환경변수들도 존재하는데 이는 다음 공식 홈페이지를 참조하자.
https://ktor.io/docs/configuration-file.html
# 프로젝트 디렉토리 구조
프로젝트 구조는 다음과 같이 설정했다.
- api (클라이언트 요청을 처리하는 라우터들을 정의)
- dto (request, response dto를 정의)
- util (request, response 전후처리 유틸 정의)
- common (공통설정, 공통 유틸)
- config (디비설정, 모듈 설정)
- util (공통 유틸)
- model
- domain (엔티티, 리포지토리 정의)
- service (비즈니스 로직 정의)
fun main() {
embeddedServer(Netty, port = 8080, module = Application::module)
.start(wait = true)
}
fun Application.module() {
configureDatabase()
configureLogging()
configureExceptionHandling()
configureSerialization()
configureDependencyInjection()
configureRouting()
}
Ktor는 플러그인(설정) 들을 이렇게 module 확장함수로 어플리케이션에 지정을 해줘야 한다. 이러한 설정 함수들은 common > config에 정의한다.
inline fun <reified T> T.logger(): Logger = LoggerFactory.getLogger(T::class.java)
fun Application.configureLogging() {
install(CallLogging) {
level = Level.INFO
filter { call -> call.request.path().startsWith("/") }
}
}
로깅 설정 예시
# (선택) ktlint 설정
다수가 협업하는 경우는 린트를 설정하여 코드의 규칙성을 어느 정도 확보할 수 있다.
설정방법은 다음 블로그를 참조할 것
이로써 프로젝트 초기 설정을 마쳤다. 다음 글에서는 MySQL을 연동해 볼 것이다.
(위에서 적용한 아키텍처는 필자 기준으로 설정한 것이므로 best practice가 아니며 참조용으로 활용해 주세요)