How to dynamically inject the implementation for ZIO with MacWire
In my last Blog I tried to decouple the Program from its Implementation — see here.
This is a follow up to the question: How to wire the implementation dynamically on application start (defined in a configuration file).
Configuration
Here we take PureConfig that we already used in the last blog.
The configuration is described in a Case Class:
case class MyConfig(compsImpl: String)
The accordingapplication.conf
looks then:
comps-impl = "pme123.zio.comps.yaml.YamlComps"
Because it can throw an Exception, we provide a function that wraps the creation of MyConfig in a ZIO.effect. As we us the default (application.conf), ConfigSource.default is all we need.
def config[T]: Task[T] = ZIO.effect(ConfigSource.default.loadOrThrow[T])
Dependency Injection
First we need a simple Dependency Injection library. I pick MacWire, as I wanted to check it out anyway and libraries from SoftwareMill never let me down;).
Following the instructions on Accessing wired instances dynamically, we can now provide a simple function that creates an Instance T from a Class name. Again wrapped in a ZIO.effect just in case.
Create the Environment
This is the way we provided the Implementation statically in a zio.App:
program.provide(
new Console.Live with HoconComps {}
)
And here the problems start. There is no way we can inject HoconComps, as it is a Trait. Or does anyone know?
This means we have to refactor our whole project and use the standard way of ZIO handling dependencies.
What we do here is providing an implementation that delegates the work to the actual Service; a real Implementation that we can inject. It is actually the same Type (Components.Service) as we have implemented in the Live Trait.
With this, we can now provide our environment:
Adjust the Implementations
The two implementations extend now directly the Service:
class HoconComps extends Components.Service[ComponentsEnv]
So the only change is to move both functions, and we are done . See here the Commit.
Conclusion
It was not possible to have dynamic dependency injection with my first solution.
New we implement just a Service not the whole Module. This sounds ok, despite the additional indirection.
However that has one big disadvantage: We have to define the environment for all Service implementations. So for example if one of them wants to use Random, it is not possible, as we only provide Console.
References
The Project to this Blog: zio-comps-module
ZIO: https://zio.dev
PureConfig: https://pureconfig.github.io
MacWire: https://github.com/softwaremill/macwire
Let me know if you have questions or stuff that can be done better!
Check also out: How you can use the same Test for different Implementations