How to dynamically inject the implementation for ZIO with MacWire

Pascal Mengelt
2 min readDec 15, 2019

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).

Photo by Rahul Bhogal on Unsplash

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

https://gph.is/2pFFObV

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

Photo by Carl Nenzen Loven on Unsplash

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

--

--

Pascal Mengelt

Working for finnova.com in the Banking business. Prefer to work with Scala / ScalaJS.