Don't autowire no-arg constructors, or make this configurable?
Citing @mbore:
Actually I'm still not sure if we want to support no parameters constructors. It may cause difficult to debug mistakes like "forgot to pass filled config", for example:
import cats.effect._
import cats.effect.unsafe.implicits.global
class MutableConfig() {
var port: Option[Int] = None
var host: Option[String] = None
}
class Service(cfg: MutableConfig) {
println(s"[${cfg.host}]:[${cfg.port}]")
}
object Main extends App {
def loadConfig(): Resource[IO, MutableConfig] = Resource.pure {
val mc = new MutableConfig()
mc.host = Some("xyz")
mc.port = Some(8080)
mc
}
val cfg = loadConfig()
val service = autowire[Service]()
service.allocated.unsafeRunSync()._1
}
it works and prints [None]:[None]. I see that it may reduce boilerplate in some cases, but I'm not sure if it's worth to risk.
Something to consider - I think no-arg implementations might be common, but the argument with configuration is also a valid one.
Maybe we could somehow make this configurable - either support no-arg constructors or not? Or better, maybe the configuration could include packages from which we want to autowire, or a blacklist of packages which should never be autowired?
However, it's challenging to provide such configuration at compile-time - I suppose it would need to be somehow available at the type level?
Adding compile-time configuration might be also useful to define a default trait implementation that should be wired. To do so, we may introduce a tag useClass[A] or useImplementation[A] that would be passed in autowire dependencies list.
It would be worth considering additional parameters list for configuration tags, then we could use it as follows:
autowire[ServiceA](useImplementation[ServiceB], useEmptyConstructors)(serviceC, serviceD)