How do I use custom configuration in Ktor? - ktor

I'm digging the built-in configuration support, and want to use it (instead of just rolling my own alongside Ktor's), but I'm having a hard time figuring out how to do it in a clean way. I've got this, and it's working, but it's really ugly and I feel like there has to be a better way:
val myBatisConfig = MyBatisConfig(
environment.config.property("mybatis.url").getString(),
environment.config.property("mybatis.driver").getString(),
environment.config.property("mybatis.poolSize").getString().toInt())
installKoin(listOf(mybatisModule(myBatisConfig), appModule), logger = SLF4JLogger())
Thanks for any help!

Adding to the existing accepted answer. An implementation using ConfigFactory.load() could look like this (Without libs):
object Config {
#KtorExperimentalAPI
val config = HoconApplicationConfig(ConfigFactory.load())
#KtorExperimentalAPI
fun getProperty(key: String): String? = config.propertyOrNull(key)?.getString()
#KtorExperimentalAPI
fun requireProperty(key: String): String = getProperty(key)
?: throw IllegalStateException("Missing property $key")
}
So, theconfig class would become:
val myBatisConfig = MyBatisConfig(
requireProperty("mybatis.url"),
requireProperty("mybatis.driver"),
requireProperty("mybatis.poolSize").toInt())

Okay, I think I have a good, clean way of doing this now. The trick is to not bother going through the framework itself. You can get your entire configuration, as these cool HOCON files, extremely easily:
val config = ConfigFactory.load()
And then you can walk the tree yourself and build your objects, or use a project called config4k which will build your model classes for you. So, my setup above has added more configuration, but gotten much simpler and more maintainable:
installKoin(listOf(
mybatisModule(config.extract("mybatis")),
zendeskModule(config.extract("zendesk")),
appModule),
logger = SLF4JLogger())
Hope someone finds this useful!

You could also try this solution:
class MyService(val url: String)
fun KoinApplication.loadMyKoins(environment: ApplicationEnvironment): KoinApplication {
val myFirstModule = module {
single { MyService(environment.config.property("mybatis.url").getString()) }
}
val mySecondModule = module {}
return modules(listOf(myFirstModule, mySecondModule))
}
fun Application.main() {
install(DefaultHeaders)
install(Koin) {
loadMyKoins(environment)
SLF4JLogger()
}
routing {
val service by inject<MyService>()
get("/") {
call.respond("Hello world! This is my service url: ${service.url}")
}
}
}
fun main(args: Array<String>) {
embeddedServer(Netty, commandLineEnvironment(args)).start()
}

Related

Micronaut-Core: How to create dynamic endpoints

Simple question. Is it possible to create endpoints without #Endpoint?
I want to create rather dynamic endpoints by a file and depending on the content to its context.
Thanks!
Update about my idea. I would to create something like a plugin system, to make my application more extensible for maintenance and future features.
It is worth to be mentioned I am using Micronaut with Kotlin. Right now I've got fixed defined Endpoints, which matches my command scripts.
My description files will be under /src/main/resources
I've got following example description file how it might look like.
ENDPOINT: GET /myapi/customendpoint/version
COMMAND: """
#!/usr/bin/env bash
# This will be executed via SSH and streamed to stdout for further handling
echo "1.0.0"
"""
# This is a template JSON which will generate a JSON as production on the endpoint
OUTPUT: """
{
"version": "Server version: $RESULT"
}
"""
How I would like to make it work with the application.
import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.context.event.StartupEvent
import io.micronaut.context.event.ShutdownEvent
import io.micronaut.runtime.event.annotation.EventListener
#Singleton
class SampleEventListener {
/*var invocationCounter = 0
#EventListener
internal fun onSampleEvent(event: SampleEvent) {
invocationCounter++
}*/
#EventListener
internal fun onStartupEvent(event: StartupEvent) {
// 1. I read all my description files
// 2. Parse them (for what I created a parser)
// 3. Now the tricky part, how to add those information to Micronaut Runtime
val do = MyDescription() // After I parsed
// Would be awesome if it is that simple! :)
Micronaut.addEndpoint(
do.getEndpoint(), do.getHttpOption(),
MyCustomRequestHandler(do.getCommand()) // Maybe there is a base class for inheritance?
)
}
#EventListener
internal fun onShutdownEvent(event: ShutdownEvent) {
// shutdown logic here
}
}
You can create a custom RouteBuilder that will register your custom endpoints at runtime:
#Singleton
class CustomRouteBuilder extends DefaultRouteBuilder {
#PostConstruct
fun initRoutes() {
val do = MyDescription();
val method = do.getMethod();
val routeUri = do.getEndpoint();
val routeHandle = MethodExecutionHandle<Object, Object>() {
// implement the 'MethodExecutionHandle' in a suitable manner to invoke the 'do.getCommand()'
};
buildRoute(HttpMethod.parse(method), routeUri, routeHandle);
}
}
Note that while this would still feasible, it would be better to consider another extension path as the solution defeats the whole Micronaut philosophy of being an AOT compilation framework.
It was actually pretty easy. The solution for me was to implement a HttpServerFilter.
#Filter("/api/sws/custom/**")
class SwsRouteFilter(
private val swsService: SwsService
): HttpServerFilter {
override fun doFilter(request: HttpRequest<*>?, chain: ServerFilterChain?): Publisher<MutableHttpResponse<*>> {
return Flux.from(Mono.fromCallable {
runBlocking {
swsService.execute(request)
}
}.subscribeOn(Schedulers.boundedElastic()).flux())
}
}
And the service can process with the HttpRequest object:
suspend fun execute(request: HttpRequest<*>?): MutableHttpResponse<Feedback> {
val path = request!!.path.split("/api/sws/custom")[1]
val httpMethod = request.method
val parameters: Map<String, List<String>> = request.parameters.asMap()
// TODO: Handle request body
// and do your stuff ...
}

Alternative solution to injecting dispatchers to make the code testable

I run into a problem during writing tests for a viewModel. The problem occurred when I was trying to verify LiveData that is updated with channelFlow flow on Dispatchers.IO.
I created a simple project to show the issue.
There is a data provider class that is providing 10 numbers:
As it is, the numbers variable in the test is empty and the test fails. I know it is a problem with coroutine dispatchers.
val numbersFlow: Flow<Int> = channelFlow {
var i = 0
while (i < 10) {
delay(100)
send(i)
i++
}
}.flowOn(Dispatchers.IO)
a simple viewModel that is collecting data:
class NumbersViewModel: ViewModel() {
private val _numbers: MutableLiveData<IntArray> = MutableLiveData(IntArray(0))
val numbers: LiveData<IntArray> = _numbers
val dataProvider = NumbersProvider()
fun startCollecting() {
viewModelScope.launch(Dispatchers.Main) {
dataProvider.numbersFlow
.onStart { println("start") }
.onCompletion { println("end") }
.catch { exception -> println(exception.message.orEmpty())}
.collect { data -> onDataRead(data) }
}
}
fun onDataRead(data: Int) {
_numbers.value = _numbers.value?.plus(data)
}
}
and the test:
class NumbersViewModelTest {
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#get:Rule
var mainCoroutineRule = MainCoroutineRule()
private lateinit var viewModel: NumbersViewModel
#Before
fun setUp() {
viewModel = NumbersViewModel()
}
#Test
fun `provider_provides_10_values`() {
viewModel.startCollecting()
mainCoroutineRule.advanceTimeBy(2000)
val numbers = viewModel.numbers.value
assertThat(numbers?.size).isEqualTo(10)
}
}
There is a common solution with changing the main dispatcher for test usage but... is there any good solution for dealing with the IO one?
I found a solution with injecting dispatchers everywhere - similarly to how I would inject NumbersProvider using Hilt in a real app - and that enables injecting our test dispatcher when we need it. It works but now I have to inject dispatchers everywhere in the code and I don't really like that if it only serves to solve the testing problem
I tried another solution and created a Singleton which makes all the standard dispatchers available in the production code and which I can configure for tests (by setting every dispatcher to the test one). I like how the resulting source code looks more - there is no additional code in viewModels and data providers but there is this singleton and everyone shouting 'Don't use singletons'
Is there any better option to correctly test code with coroutines?

Room + Kotlin Flow not emitting result

i'm trying to fetch some data from api, and them store on room database, so the main data source is roomDatabase.
my repository code looks like:
suspend fun fetchData(): Flow<Response<List<Foo>>> {
val shouldRequestData = dao.getFoo().isEmpty()
return if (shouldRequestData) {
getFoo()
} else getLocalFoo()
}
override suspend fun getFoo(): Flow<Response<List<Foo>>> {
return ....... request done normally... inserting normally on database (and showing
on database inspector)
}
override suspend fun getLocalFoo(): Flow<Response<List<Foo>>> = flow {
dao.getFoo().transform<List<FooLocal>, Response<List<Foo>>> {
Response.Success(
it.map {
it.toDomainModel()
}
)
}
}
on Dao:
#Query("SELECT * FROM localdb")
fun getFoo(): Flow<List<Foo>>
and then collecting it normally on viewmodel...
The problem is: the data is not appearing.. how could i solve this? The non-flow version works :/
I already searched for this problem, but nothing seems to work.
Solved by putting this on getLocalFoo() ->
val result: Flow<Response<List<Foo>>> =
Transformations.map(dao.getFoo()) {
Response.Success(it?.map {
it.asDomainModel()
} ?: emptyList()
}.asFlow()
return result
I have found a solution to investing so much time.
Solution: Same Dao Object should be used when we insert details into the room database and get information from DB.
If you are using dagger hilt then
#Singleton annotation will work.
I hope this will solve your problem.

foreach a list of class constructors in kotlin

I'm pretty new to kotlin and I've been reading through the language docs to start picking it up.
I learn much better when I type out the examples, so I wanted to make a little example running codebase so I could follow along with the examples.
This is fine for writing each of examples in the main.kt file on it's own, running it, then blowing it away, but I'd like to create an example class for each section, create a list of the classes in main, and then foreach over them.
I created an interface which has a declaration for a common member function for running the examples:
interface ExampleCodeInterface {
/**
* Run the examples for the current Example class
*/
fun runExamples()
}
And defined an example class:
class CollectionExamples : ExampleCodeInterface{
fun listExample() {
val systemUsers: MutableList<Int> = mutableListOf(1,2,3)
val sudoers: List<Int> = systemUsers
fun addSudoer(newUser: Int) {
systemUsers.add(newUser)
}
fun getSysSudoers(): List<Int> {
return sudoers
}
addSudoer(4)
println("Total sudoers: ${getSysSudoers().size}")
getSysSudoers().forEach {
i -> println("Some useful info on user $i")
}
}
override fun runExamples() {
listExample()
}
}
The thing I'm not sure on is how to properly run this from main.kt
I know the class works because when I create a new instance and fire the method it works, but I can't quite figure out how to properly create a list of class constructors so that I can have a list of classes that all extend the ExampleCodeInterface that I can forEach through and fire the method:
fun main(args: Array<String>) {
val exampleClassList: List<ExampleCodeInterface> = listOf<ExampleCodeInterface>(CollectionExamples)
exampleClassList.forEach {
val exampleSet = it()
exampleSet.runExamples()
}
// val collectionExamples = CollectionExamples()
// collectionExamples.runExamples()
}
I've been trying to piece together the logic from the docs, but I think there are some details that I don't know yet.
Any help is appreciated!!

How to read a text file from resources in Kotlin?

I want to write a Spek test in Kotlin.
How to read an HTML file from the src/test/resources folder?
class MySpec : Spek(
{
describe("blah blah") {
given("blah blah") {
var fileContent: String = ""
beforeEachTest {
// How to read the file.html in src/test/resources/html/
fileContent = ...
}
it("should blah blah") {
...
}
}
}
}
)
val fileContent = MySpec::class.java.getResource("/html/file.html").readText()
No idea why this is so hard, but the simplest way I've found (without having to refer to a particular class) is:
fun getResourceAsText(path: String): String? =
object {}.javaClass.getResource(path)?.readText()
It returns null if no resource with this name is found (as documented).
And then passing in an absolute URL, e.g.
val html = getResourceAsText("/www/index.html")!!
another slightly different solution:
#Test
fun basicTest() {
"/html/file.html".asResource {
// test on `it` here...
println(it)
}
}
fun String.asResource(work: (String) -> Unit) {
val content = this.javaClass::class.java.getResource(this).readText()
work(content)
}
A slightly different solution:
class MySpec : Spek({
describe("blah blah") {
given("blah blah") {
var fileContent = ""
beforeEachTest {
html = this.javaClass.getResource("/html/file.html").readText()
}
it("should blah blah") {
...
}
}
}
})
Kotlin + Spring way:
#Autowired
private lateinit var resourceLoader: ResourceLoader
fun load() {
val html = resourceLoader.getResource("classpath:html/file.html").file
.readText(charset = Charsets.UTF_8)
}
Using Google Guava library Resources class:
import com.google.common.io.Resources;
val fileContent: String = Resources.getResource("/html/file.html").readText()
private fun loadResource(file: String) = {}::class.java.getResource(file).readText()
val fileContent = javaClass.getResource("/html/file.html").readText()
This is the way that I prefer to do it:
fun getResourceText(path: String): String {
return File(ClassLoader.getSystemResource(path).file).readText()
}
this top-level kotlin function will do the job in any case
fun loadResource(path: String): URL {
return Thread.currentThread().contextClassLoader.getResource(path)
}
or if you want a more robust function
fun loadResource(path: String): URL {
val resource = Thread.currentThread().contextClassLoader.getResource(path)
requireNotNull(resource) { "Resource $path not found" }
return resource
}
FYI: In all the above cases. getResource() is unsafe way of using nullable.
Haven't tried locally but I prefer this way:
fun readFile(resourcePath: String) = String::class.java.getResource(resourcePath)?.readText() ?: "<handle default. or handle custom exception>"
Or even as custom datatype function
private fun String.asResource() = this::class.java.getResource(resourcePath)?.readText() ?: "<handle default. or handle custom exception>"
and then you can call directly on path like:
// For suppose
val path = "/src/test/resources"
val content = path.asResource()
I prefer reading resources in this way:
object {}.javaClass.getResourceAsStream("/html/file.html")?.use { it.reader(Charsets.UTF_8).readText() }
Explenation:
getResourceAsStream instead getResource. The resource on classpath can be basically anywhere. e.g. packed inside another .jar file.
In these situations accessing resource via URL class returned from getResource method will fail. But accessing via method getResourceAsStream works in every situation.
object {} - This is not nice syntax, but it is not dependent on name of your class MyClass and works even in static (compenion object) block.
use to close stream - in most cases it is not necessary, but there can be some special classloaders, which may need it.
reader(Charsets.UTF_8) - UTF_8 is default encoding, but I prefer to be explicit. If you will encode your resource files in other encoding e.g. ISO-8859-2 you will not overlook it.
Another variation that handles null resource in place:
val content = object {}.javaClass
.getResource("/html/file.html")
?.let(URL::readText)
?: error("Cannot open/find the file")
// ?: "default text" // Instead of error()
You might find the File class useful:
import java.io.File
fun main(args: Array<String>) {
val content = File("src/main/resources/input.txt").readText()
print(content)
}