I have some trouble with the 0.4.0 compiler plugin...
# koin
j
I have some trouble with the 0.4.0 compiler plugin (and 4.2.0-RC2 Koin and kotlin 2.3.0-RC3) and the @Provided annotation. if I have an interface MyInterface in module A, depend on it in module B, and then somehow provide the binding in my application module, I would think that if I use this, things should be well, as per the olden days:
Copy code
@Factory
class MyClass(@Provided private val myInterface: MyInterface)
Instead, I get
[Koin] Missing dependency MyInterface
If I turn off the compile check (
compileSafety.set(false)
) then my app compiles and runs just fine. I see here https://github.com/InsertKoinIO/koin-compiler-plugin/releases something on the @Provided annotation and if I try to mark my interface with @Provided, I get the same error. Also, if it would be an external dependency, how can you mark it as @Provided if it is a third party class?
Copy code
// example from github

  @Provided
  class ExternalService  // provided by framework at runtime

  @Singleton
  class MyService(val ext: ExternalService)  // no error
plus1 2
It also seems like the compile time check doesn't take into account modules on which the other modules depend: If I have gradle module A, B and C and B depends on A, and C depends on B, and I have a similar koin module structure, then C will get "Missing dependency" from whatever is declared in module A, unless I specifically depend on it. Error:
Copy code
// in gradle module b
@Module(includes = [ModuleA::class])
class ModuleB

// in gradle module c
@Module(includes = [ModuleB::class])
class ModuleC
No error:
Copy code
// in gradle module b
@Module(includes = [ModuleA::class])
class ModuleB

// in gradle module c
@Module(includes = [ModuleA::class, ModuleB::class])
class ModuleC
This would force me to make all modules depend on each other, which I would wish to avoid.
plus1 2
a
I think this is okay, since you are compiling a module as an independent unit, but it's not complete, it has a binding in a different module, to me this makes sense. I guess the compile safety makes sense on the end result, your app, where all the dependencies come together. It also felt wrong to me at the beginning.
j
If it is impossible I can definitely live with it 🙂 If it would be possible to have -> then I'd rather have it
a
Yes, but have what? Wrongfully reporting that your module is complete when its not?
j
Why would it be wrong? If I provide all the proper includes then hopeful me would hope that the chain is followed and we can see whether one of them provides what I need
Or in Dagger it checks it when you compile the actual application, even though you need to have dependencies on all the individual gradle modules as well for this to work
a
so dagger will give you the same error, if you dont provide the dependency. Just the way to ignore it is different, @BindOptionalOf.
j
I don't think dagger analyzes the individual modules, but the application as a whole, so I wouldn't need to use things like @provided in any way
a
Dagger checks the final graph/component automatically, and in koin you need to ask for it with compileSafety = true, or rather it's the default. I guess it can be done to do the same, to validate the graphs having the @KoinApplication annotation, instead of disabling compileSafety =false on incomplete modules, or using the @Provided annotation as a promise it will come later from somewhere else.
Then it will look more like dagger
a
Now with the latest version @Provided annotation works but I don't think this approach give us any benefit. First it adds a verbosity in class dependencies but the more important is that you don't have real compile safety. As an example if I have the following lib module
Copy code
// lib/src/main/kotlin/MyLib.kt
package com.example.mylib

import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Module
import org.koin.core.annotation.Provided
import org.koin.core.annotation.Single

@Module
@ComponentScan("com.example.mylib")
class LibModule

interface ExternalService {
    fun doSomething()
}

@Single
class MyService(@Provided val externalService: ExternalService) {
    fun doSomething() {
        externalService.doSomething()
    }
}
and the following koin application
Copy code
// app/src/main/kotlin/MyApp.kt
package com.example.myapp

import com.example.mylib.ExternalService
import com.example.mylib.LibModule
import com.example.mylib.MyService
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Configuration
import org.koin.core.annotation.KoinApplication
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single
import org.koin.plugin.module.dsl.startKoin

//@Single
class MyExternalService: ExternalService {
    override fun doSomething() {
        println("Hello from MyExternalService")
    }
}

@Module(includes = [LibModule::class])
@ComponentScan("com.example.myapp")
@Configuration
class AppModule

@KoinApplication
object MyApp

fun main() {
    val app = startKoin<MyApp>()

    val service = app.koin.get<MyService>()
    service.doSomething()
}
if I comment or forget to put@Single in MyExternalService I will not get any compile error and when I run the application I will get the following error
Copy code
Caused by: org.koin.core.error.NoDefinitionFoundException: No definition found for type 'com.example.mylib.ExternalService' on scope '['_root_']'.. Check or add definition for type 'com.example.mylib.ExternalService' in scope '_root_'.
Previously we could check our modules writing a test like that
Copy code
class ModuleTest {
    @Test
    fun checkKoinModule() {
        LibModule().module.verify(
            extraTypes = listOf(
                ExternalService::class
            ),
        )
    }
}
which was match better because we didn't have to anotate all our ExternalService declarations with @Provided It woud be better if we could have this extraTypes inside @Module annotation and koin compiler avoid to try to find this classes when it compiles the module, but do a full check when you have a startKoin somewhere
a
do you have a long list to white-list? I can double check that @Provided once, covers well the reuse
a
Before using ksp annotations my services was liked that
Copy code
@Single
class ProjectDefaultService(
    private val i18n: MessageResolver,
    private val projectRepository: ProjectRepository,
    private val userService: UserService,
    private val formService: FormService,
    private val organizationService: OrganizationService,
    private val organizationAssociateService: OrganizationAssociateService,
    private val stateFlowService: StateFlowService,
    private val cacheManager: CacheManager,
    private val entityUpdateEventPublisher: EntityUpdateEventPublisher,
    @Property("namespace")
    private val namespace: String,
) : ProjectService {

// implementation here

}
now with koin compiler in order to have compile safety I have to change them to this
Copy code
@Single
class ProjectDefaultService(
    @Provided private val i18n: MessageResolver,
    private val projectRepository: ProjectRepository,
    @Provided private val userService: UserService,
    @Provided private val formService: FormService,
    @Provided private val organizationService: OrganizationService,
    @Provided private val organizationAssociateService: OrganizationAssociateService,
    @Provided private val stateFlowService: StateFlowService,
    @Provided private val cacheManager: CacheManager,
    @Provided private val entityUpdateEventPublisher: EntityUpdateEventPublisher,
    @Property("namespace")
    private val namespace: String,
) : ProjectService {

// implementation here

}
Most of dependencies are interfaces and the implementation is in another module. Also, there are two different modules for each service implementation, one using repository to read/write data from database and one using rest client to read/write data from a another microservice. So, depending which mode I want to deploy my application I can choose which module will implement each service. If you want to have a clean architecture @Provided annotation does not help. You don't want your interfaces to depend in any framework (Spring or Koin) so adding specific framework annotations in your interfaces is not ideal solution. I would better if we could define the extra types in the module dsl or @Module class. This class is created only for koin framework and we can have anything relevant to koin their.
a
@Provided is not the solution here. If you have some way to reproduce, it will help accelerate the fix for it. 👍