Don Mitchell
04/23/2025, 12:47 PMmain
or in koin
module definitions or class initializers/constructors? Example from koin
init (as you'll see I'm questioning the original author's overuse and non-trivial computations)
single {
val redis: RedisConnection = get(CORE_REDIS_CONNECTION)
val redisHealth = HealthDetail(
url = redis.uris.first().toString(),
healthy = runCatching {
// ugh, this is ugly. too much work in koin and unclear what the coroutine context is.
// this may deadlock coroutines as this won't release the context until it completes.
runBlocking(<http://Dispatchers.IO|Dispatchers.IO>) {
redis.withSuspendConnection { connection ->
connection.ping()
}
}
true
}.getOrDefault(false),
responseTimeMs = 0
)
Health(database = get(MYSQL), stage = Stage.fromEnvironment(), redisHealth = redisHealth)
}
CLOVIS
04/23/2025, 2:45 PMCLOVIS
04/23/2025, 2:46 PMsuspend
. If you put Koin in that, you must make sure it can suspend
.CLOVIS
04/23/2025, 2:51 PMHealth
, another option is to make the Health.redisHealth
a Deferred<HealthDetail>
and initialize it lazily from GlobalScope
;
single {
// …
val redisHealth = GlobalScope.async(<http://Dispatchers.IO|Dispatchers.IO>, start = LAZY) {
HealthDetail(
url = redis.uris.first().toString()
healthy = redis.withSuspendConnection { it.ping() }
),
responseTimeMs = 0
}
Health(…, redisHealth = redisHealth)
}
Here, GlobalScope
is ok because the job is started lazily (so no leak happens if it's never initialized) and it returns a Deferred
(so the caller can control cancellation).Zach Klippenstein (he/him) [MOD]
04/23/2025, 3:08 PMThe framework should provide a way to initialize data in a suspending fashion.I don't have many strong feelings about DI frameworks but I probably wouldn't want my DI framework to grow an async loading, repository-ish appendage. Dependency injection can be complex enough of a job as-is, and async loading can also get quite nuanced. This looks to me like it wants a service class that's responsible for making the async calls, give it whatever suspending/flow/other API it needs, then just inject a singleton of that everywhere and leave the consumers to call the loading API as necessary.
initialize it lazily fromGlobalScope
GlobalScope
is also definitely something to avoid, if you're using it to avoid runBlocking
your code isn't much better.CLOVIS
04/23/2025, 3:11 PMDependency injection can be complex enough of a job as-is, and async loading can also get quite nuanced.IMO, if you're going to use a DI framework instead of using regular variables, I hope that it solves the complex issues of loading things. Otherwise, I don't really see the point.
CLOVIS
04/23/2025, 3:11 PMkevin.cianfarini
04/23/2025, 3:15 PMCLOVIS
04/23/2025, 3:16 PMlazyAsync {}
where the block is computed in the scope of the first coroutine to await it (so no need to specify a scope at creation time).Zach Klippenstein (he/him) [MOD]
04/23/2025, 5:43 PMdo you have other ideas of how to solve this?I gave one
Zach Klippenstein (he/him) [MOD]
04/23/2025, 5:47 PMif you're going to use a DI framework instead of using regular variables, I hope that it solves the complex issues of loading things. Otherwise, I don't really see the point.Again, I completely disagree. DI frameworks imo mostly only have value in huge codebases with huge dependency graphs where manually doing constructor injection gets untenable. That's their job, and imo should be their only job. Libraries for loading things (Picasso, Coil, Store) are quite complex to solve their own problems, I wouldn't want to conflate those two jobs. Glide is a counterexample of a library that does both (image loading + DI), and ew.
Don Mitchell
04/23/2025, 5:51 PMkevin.cianfarini
04/23/2025, 6:07 PM