Exit e-book
Show all chapters
02
A deep look into the ZIO module structure
02. 
A deep look into the ZIO module structure
Mastering Modularity in ZIO with Zlayer
02

A deep look into the ZIO module structure

As you may already know, ZIO is designed around three type parameters:

ZIO[-R, +E, +A]

You may also remember that a nice mental model of the ZIO data type is the following:

R => Either[E, A]

This means a ZIO effect needs an environment of type R to run, meaning we need to fulfill this requirement in order to make the effect runnable. More concretely, this R type represents a dependency on a module or several modules that are needed for running the effect.  Therefore, let’s now discuss how modules are defined in ZIO.

About Services and Modules in ZIO

As mentioned in the ZIO documentation page: A service is a group of functions that deals with only one concern. Keeping the scope of each service limited to a single responsibility improves our ability to understand code, in that we need to focus only on one topic at a time without juggling too many concepts together in our head”.

The idea is that ZIO allows us to define modules and use them to create different application layers that rely on each other. This means each layer depends on the layers immediately below it, although it doesn’t know anything about their implementation details. This is a really powerful concept because it improves composability and testability (because you can easily change each of the module’s implementations without affecting other layers).

Now, if you are thinking about how to define these modules, ZIO provides us with a nice recipe to follow when defining a new module. This recipe should be familiar to object-oriented programmers:

zio modules

Don’t worry if this all seems too abstract at the moment because we are going to be applying this recipe to implement the Tic-Tac-Toe application later. The only important thing for now is to get to know ZLayer, a very important data type mentioned in this recipe, and which is related to another very important data type:  Has. So let’s discuss those now.

The Has data type

As mentioned in the ZIO documentation page:

  • Has[A] represents a dependency on a service A.
  • Has[A] and a Has[B] can be combined horizontally with the ++ operator for obtaining a Has[A] with Has[B], representing a dependency on two services.
  • The true power of the Has data type is that it is backed by a heterogeneous map from service type to service implementation, so when you combine Has[A] with Has[B], you can easily get access to the A and B services implementations.
  • We don’t usually need to create a Has directly, but we can do that through ZLayer.

 

The ZLayer data type

The ZLayer data type is an immutable value that contains a description for building an environment of type ROut, starting from a value RIn, possibly producing an error E during creation:

ZLayer[-RIn, +E, +ROut <: Has[_]]
  • Horizontally: To build a layer that has the requirements and provides the capabilities of both layers, we use the ++ operator.
  • Vertically: In this case, the output of one layer is used as the input for the subsequent layer, resulting in a layer with the requirement of the first and the output of the second layer. We use the >>> operator for this.

Again, don’t panic if this doesn’t make too much sense for you at the moment,  because we are going to be applying both the horizontal and vertical compositions when we implement the Tic-Tac-Toe application and everything will become clearer. As a side note, there are other additional operators for combining layers,  which we are going to be talking about later.

Finally, it’s worth mentioning that ZIO provides some type aliases for the ZLayer data type which are very useful when representing some common use cases. The good news is that the logic for defining these type aliases is practically the same as that applied for defining the ZIO type aliases (for reference, you can take a look at the Quick Introduction to ZIO section of this article where I talk about concurrency with ZIO STM). Here’s the complete list:

 

  • TaskLayer[+ROut] = ZLayer[Any, Throwable, ROut]: This means a TaskLayer[ROut] is a ZLayer that:
    • Doesn’t require an input (that’s why the RIn type is replaced by Any)
    • Can fail with a Throwable
    • Can succeed with an ROut
  • ULayer[+ROut] = ZLayer[Any, Nothing, ROut]: This means a ULayer[ROut] is a ZLayer that:
    • Doesn’t require an input
    • Can’t fail
    • Can succeed with an ROut
  • RLayer[-RIn, +ROut] = ZLayer[RIn, Throwable, ROut]: This means an RLayer[RIn, ROut] is a ZLayer that:
    • Requires an input RIn
    • Can fail with a Throwable
    • Can succeed with an ROut
  • Layer[+E, +ROut] = ZLayer[Any, E, ROut]: This means a Layer[E, ROut] is a ZLayer that:
    • Doesn’t require an input
    • Can fail with an E
    • Can succeed with an ROut
  • URLayer[-RIn, +ROut] = ZLayer[RIn, Nothing, ROut]: This means a URLayer[RIn, ROut] is a ZLayer that:
    • Requires an input RIn
    • Can’t fail
    • Can succeed with an ROut

Now, if you are wondering how to create ZLayers, stay tuned because we are going to be seeing how easy that is to do in the next section.

PREVIOUS
Chapter
01
NEXT
Chapter
03