Exit e-book
Show all chapters
05
Magically reducing boilerplate in the TicTacToe object
05. 
Magically reducing boilerplate in the TicTacToe object
Mastering Modularity in ZIO with Zlayer
05

Magically reducing boilerplate in the TicTacToe object

In the previous section, we have seen how to prepare the environment for our application by combining ZLayers, using horizontal and vertical composition, and we needed to do that manually.

Yes, I know what you must be thinking now: I thought the whole purpose of using ZLayer instead of ZEnvironment for providing required dependencies to a ZIO effect was that we would not need to do manual wiring of those dependencies, but the whole process still looks pretty manual to me!

Well, the good news is that in ZIO 2.0 there is actually an automatic way of wiring all the ZLayers of our application that I haven’t shown you yet (ZIO 1.0 does not include this feature, so in that case you would need to use an external library written by Kit Langton, called ZIO Magic).

OK, so now let’s see how ZIO 2.0 can help us to reduce the boilerplate when preparing our environmentLayer:

private val environmentLayer: ULayer[RunLoop] =
 ZLayer.make[RunLoop](
  ControllerLive.layer,
  GameLogicLive.layer,
  ConfirmModeLive.layer,
  GameModeLive.layer,
  MenuModeLive.layer,
  OpponentAiLive.layer,
  ConfirmCommandParserLive.layer,
  GameCommandParserLive.layer,
  MenuCommandParserLive.layer,
  RunLoopLive.layer,
  TerminalLive.layer,
  ConfirmViewLive.layer,
  GameViewLive.layer,
  MenuViewLive.layer
 )

Wow! A great improvement, don’t you think? In ZIO 2.0 you just need to call ZLayer.make with a type parameter indicating the type of ZLayer you want to construct (in this case a ZLayer that returns a RunLoop), and after that you just need to provide all the layers that have to be wired, in any order you want, and that’s it! You don’t need to think about horizontal and vertical composition ever again! ZIO 2.0 will take care of that for you.

By the way, a nice feature of ZIO 2.0 is that, if you add a ZLayer.Debug.mermaid layer to the ZLayer.make call, like this:

private val environmentLayer: ULayer[RunLoop] =
 ZLayer.make[RunLoop](
  ControllerLive.layer,
  GameLogicLive.layer,
  ConfirmModeLive.layer,
  GameModeLive.layer,
  MenuModeLive.layer,
  OpponentAiLive.layer,
  ConfirmCommandParserLive.layer,
  GameCommandParserLive.layer,
  MenuCommandParserLive.layer,
  RunLoopLive.layer,
  TerminalLive.layer,
  ConfirmViewLive.layer,
  GameViewLive.layer,
  MenuViewLive.layer,
  ZLayer.Debug.mermaid
 )

You’ll get a nice tree representation of the dependency graph at compile time, including a Mermaid.js link containing a nice Mermaid diagram that you can even export as an image!

Mermaid diagram

And as a bonus, it turns out we can still reduce some boilerplate. Thanks to ZIO 2.0 we don’t really need to keep environmentLayer as a separate variable anymore. Let’s see how that would work, the original run method that uses environmentLayer looks like this:

val run = program.provideLayer(environmentLayer)

We called ZIO#provideLayer to provide the prepared environment to our program. What we can now do instead, is the following:

val run =
 program
  .provide(
   ControllerLive.layer,
   GameLogicLive.layer,
   ConfirmModeLive.layer,
   GameModeLive.layer,
   MenuModeLive.layer,
   OpponentAiLive.layer,
   ConfirmCommandParserLive.layer,
   GameCommandParserLive.layer,
   MenuCommandParserLive.layer,
   RunLoopLive.layer,
   TerminalLive.layer,
   ConfirmViewLive.layer,
   GameViewLive.layer,
   MenuViewLive.layer
)

We can call the ZIO#provide method directly in our program to provide the required ZLayers in any order.

PREVIOUS
Chapter
04
NEXT
Chapter
06