Ktor & Annotations
Ktor is a framework for building asynchronous servers and clients in connected systems using the powerful Kotlin programming language. We will use Ktor here, to build a simple web application.
Let's go 🚀
update - 2024-10-21
Get the code​
Gradle Setup​
First, add the Koin dependency like below:
plugins {
id("com.google.devtools.ksp") version kspVersion
}
dependencies {
// Koin for Kotlin apps
implementation("io.insert-koin:koin-ktor:$koin_version")
implementation("io.insert-koin:koin-logger-slf4j:$koin_version")
implementation("io.insert-koin:koin-annotations:$koinAnnotationsVersion")
ksp("io.insert-koin:koin-ksp-compiler:$koinAnnotationsVersion")
}
Application Overview​
The idea of the application is to manage a list of users, and display it in our UserApplication
class:
Users -> UserRepository -> UserService -> UserApplication
The "User" Data​
We will manage a collection of Users. Here is the data class:
data class User(val name : String)
We create a "Repository" component to manage the list of users (add users or find one by name). Here below, the UserRepository
interface and its implementation:
interface UserRepository {
fun findUser(name : String): User?
fun addUsers(users : List<User>)
}
class UserRepositoryImpl : UserRepository {
private val _users = arrayListOf<User>()
override fun findUser(name: String): User? {
return _users.firstOrNull { it.name == name }
}
override fun addUsers(users : List<User>) {
_users.addAll(users)
}
}
The Koin module​
Use the @Module
annotation to declare a Koin module, from a given Kotlin class. A Koin module is the place where we define all our components to be injected.
@Module
@ComponentScan("org.koin.sample")
class AppModule
The @ComponentScan("org.koin.sample")
will help scan annotated classes from targeted package.
Let's declare our first component. We want a singleton of UserRepository
, by creating an instance of UserRepositoryImpl
. We tag it @Single
@Single
class UserRepositoryImpl : UserRepository
The UserService Component​
Let's write the UserService component to request the default user:
class UserService(private val userRepository: UserRepository) {
fun getDefaultUser() : User = userRepository.findUser(DefaultData.DEFAULT_USER.name) ?: error("Can't find default user")
}
UserRepository is referenced in UserPresenter`s constructor
We declare UserService
in our Koin module. We tag with @Single
annotation:
@Single
class UserService(private val userRepository: UserRepository)
HTTP Controller​
Finally, we need an HTTP Controller to create the HTTP Route. In Ktor is will be expressed through an Ktor extension function:
fun Application.main() {
// Lazy inject HelloService
val service by inject<UserService>()
// Routing section
routing {
get("/hello") {
call.respondText(service.sayHello())
}
}
}
Check that your application.conf
is configured like below, to help start the Application.main
function:
ktor {
deployment {
port = 8080
// For dev purpose
//autoreload = true
//watch = [org.koin.sample]
}
application {
modules = [ org.koin.sample.UserApplicationKt.main ]
}
}
Start and Inject​
Finally, let's start Koin from Ktor:
fun Application.main() {
install(Koin) {
slf4jLogger()
modules(AppModule().module)
}
// Lazy inject HelloService
val service by inject<UserService>()
service.saveDefaultUsers()
// Routing section
routing {
get("/hello") {
call.respondText(service.sayHello())
}
}
}
By writing the AppModule().module
we use a generated extension on AppModule
class.
Let's start Ktor:
fun main(args: Array<String>) {
// Start Ktor
embeddedServer(Netty, commandLineEnvironment(args)).start(wait = true)
}
That's it! You're ready to go. Check the http://localhost:8080/hello
url!