include(module) from mutiple modules leads to ConflictResolutionException
Ravenow opened this issue · comments
class CoreModule() extends ConfigModuleDef {
make[AppConfig].from(CoreModule.unsafeConfig)
}
class Configs() extends ConfigModuleDef {
include(CoreModule())
makeConfig[ApplicationConfig]("application")
}
class S1() extends ModuleDef {
include(new Configs())
make[Service1]
}
class S2() extends ModuleDef {
include(new Configs())
make[Service2]
}
class AppModule() extends ModuleDef {
include(new S1())
include(new S2())
make[ServiceApp]
}
such modules chain will lead to ConflictResolutionException when you try to create ServiceApp, mutiple applicationConfigs are here
Sample code here:
https://github.com/Ravenow/distage-sample
Repro Scastie: https://scastie.scala-lang.org/auv4lZ4pQC6blOr09gIifg
Workaround Scastie: https://scastie.scala-lang.org/syIPLY28RpqVVXXX98JjWw
@Ravenow
Workaround: make bindings in Configs()
module unique by using an object:
object Configs extends Configs()
And including using include(Configs)
instead of include(new Configs())
https://scastie.scala-lang.org/syIPLY28RpqVVXXX98JjWw
Another workaround
Use distage-plugins
instead of hierarchical module includes - https://izumi.7mind.io/distage/distage-framework.html#plugins
Plugin machinery guarantees that every module is only instantiated once, so all bindings are unique.
Explanation
Why does that happen?
We have hacks to consider class constructor bindings to always be equal to each other, so you can have multiple duplicate bindings for the same class constructor:
def x = new ModuleDef { make[A] }
assert((x ++ x) == x) // true
This does not apply to makeConfig
.
Why not? Because you can use custom pureconfig codecs or have different implicit scopes. There's no exact guarantee that two makeConfig[X]
bindings will use the same pureconfig instance.
However, there is probably a way to do this properly, it's just something that was left for later.
Thanks for explanation. Making config modules as objects helps.