프레임워크(Framework)/Ktor

[Ktor] Kotlin + Ktor 환경에서 의존성 주입하는 방법 - Dependency Injection

잇트루 2023. 8. 21. 00:24
반응형

Intro

Ktor 프레임워크는 Spring과는 달리 의존성 주입(DI)에 대한 기능을 기본적으로 제공하지 않는다. 따라서 수동으로 의존성 주입 설정을 하거나, 의존성 주입 라이브러리를 사용해야 한다. 코틀린 DI 라이브러리는 대표적으로 Koin, Dagger 등이 존재한다. 이 외에도 다양한 라이브러리들이 존재하지만 일반적으로 많이 사용되는 Koin 라이브러리를 사용하여 의존성 주입을 하고자 한다.

 

의존성 주입은 애플리케이션의 구성 요소를 느슨하게 결합(Loose Coupling)하여 코드 유지보수성과 테스트 용이성을 향상한다.

 

 

의존성 추가

Koin 라이브러리를 사용하기 위해 다음과 같이 의존성을 추가한다.

build.gradle.kts

dependencies {
    ...

    implementation("io.insert-koin:koin-ktor:$koin_version")
    implementation("io.insert-koin:koin-logger-slf4j:$koin_version")

    ...
}
  • io.insert-koin:koin-ktor : Ktor 프레임워크에서 사용하기 위한 Koin 라이브러리로 koin-core가 함께 포함되어 있다.
  • io.insert-koin:koin-logger-slf4j : Koin의 로그를 기록할 수 있는 라이브러리로 slf4j를 기본으로 사용한다.

 

 

모듈 설정

Koin.kt

Ktor 애플리케이션에서 Koin을 사용하려면 Module을 구성하여 install 함수로 적용해야 한다.

fun Application.configureKoin() {
    install(Koin) {
        slf4jLogger()
        modules(appModule)
    }
}

val appModule = module {
    // 의존성 주입 설정
}
  • Application.configureKoin() 함수는 Ktor 애플리케이션에 Koin을 설정하는 역할을 한다. Application.kt의 모듈 구성 함수에서 호출하여 Koin을 설정할 수 있다.
  • install(Koin) { … } 블록은 Koin을 Ktor 애플리케이션에 설치하는 코드 블록이다. 로깅 설정과 Koin 모듈을 설정한다.
  • appModule은 애플리케이션에서 사용할 의존성들을 정의한다.

 

Application.Kt

위에서 설정한 configureKoin() 모듈을 애플리케이션에 활성화하기 위해 Application.module() 함수 내에서 호출한다.

fun main(args: Array<String>): Unit =
    io.ktor.server.netty.EngineMain.main(args)

fun Application.module() {
    ...
    configureKoin()
    ...
}

Ktor 애플리케이션에서 Koin 라이브러리 사용을 위한 설정을 했으니 이제 의존성 주입을 설정해 보자.

 

 

의존성 주입 설정

의존성 주입(DI: Dependency Injection)을 하기 위해 Router(Controller), Service, Repository을 구성해야 한다. 자세한 내부 로직은 생략하고 Member 도메인에 대한 구조를 구성해 보자.

 

MemberRouter.kt

Ktor에서 Rotuer는 스프링에서의 컨트롤러 역할을 하지만, 클래스가 아닌 확장함수로 구현되어 있다. 따라서 함수 내에서 by 키워드와 Koin 라이브러리에서 지원하는 inject() 함수를 사용하여 주입한다.

fun Route.memberRouter() {
    val memberService: MemberService by inject()

    route("/api/v1/members") {
        ...
    }
}

 

MemberService

코틀린 스프링 또는 자바 스프링과 같은 방식으로 생성자 주입을 할 수 있다.

MemberService.kt

interface MemberService {
    ...
}

MemberServiceImpl.kt

MemberService 구현 클래스의 생성자 파라미터에서 MemberRepository를 주입받는다.

class MemberServiceImpl(
    private val memberRepository: MemberRepository
) : MemberService {
    ...
}

 

MemberRepository

MemberRepository.kt

interface MemberRepository {
    ...
}

MemberRepositoryImpl.kt

class MemberRepositoryImpl : MemberRepository {
    ...
}

 

appModule 설정

각 계층에서 의존성 주입을 정상적으로 사용하기 위해서는 앞에서 구성한 Koin의 모듈 설정하는 부분의 appModule에서 설정하여 관리해야 한다.

fun Application.configureKoin() {
    install(Koin) {
        slf4jLogger()
        modules(appModule)
    }
}

val appModule = module {
    single<MemberRepository> { MemberRepositoryImpl() }
    single<MemberService> { MemberServiceImpl(get()) }
}

single

single은 Koin 라이브러리에서 제공하는 함수로 의존성 주입 시점에 싱글톤으로 관리하도록 지정하는 것이다. 사용할 클래스 또는 인터페이스를 등록하여 해당 인스턴스를 싱글톤으로 관리하도록 설정할 수 있다.

 

single<MemberRepository> { MemberRepositoryImpl() }

  • MemberRepository의 구현체인 MemberRepositoryImpl 인스턴스를 싱글톤으로 관리하며, 필요할 때마다 동일한 인스턴스가 주입되어 사용된다.

 

single<MemberService> { MemberServiceImpl(get()) }

  • 마찬가지로 MemberService의 구현체인 MemberServiceImpl 인스턴스를 싱글톤으로 관리한다.
  • MemberServiceImpl에는 MemberRepositoryImpl가 등록되어 있기 때문에 해당 인스턴스를 get()으로 주입받아 사용한다.
반응형