Skip to content

Scala ZIO: An approach to configuring applications with ZLayers.

Notifications You must be signed in to change notification settings

dtarima/zio-services-pattern

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 

Repository files navigation

The goal

Develop recommendations for organizing code using ZIO library. Components' interfaces should not have any dependencies to avoid leaking implementation details and to be able to have multiple implementations switchable without affecting clients' code.

The approach

ZIO's environment R is implemented as a map of service instances so environmental accessors (ZIO.access) are not free: on each call the service needs to be fetched from the map in runtime. Due to the overhead the common approach to constructing service instances is to use ZIO.fromServices passing dependent service instances directly to service constructors. It guarantees the best performance, but when there is a need to call another environmental method the required context would have to be re-created or the dependency specified in the method signature.

The approach presented here passes the environment to service constructors and initializes required services from the map as lazy vals in the class achieving both, performance and flexibility. The ability to provide environment might come handy when there is a need to call an external library or a utility method which requires it.

Here is a simplified implementation of a service with a direct call to a dependent service modderService and a call to the same service using its environmental accessor modder.mod:

object ProgramLive {
  val layer: URLayer[Core, Program] =
    ZLayer.identity[Core] map (env => Has(new ServiceImpl(env)))

  class ServiceImpl(protected val env: Core) extends Program.Service
      with CoreDep {
    def executeDirect(value: Int): IO[Error, Int] =
      modderSvc.mod(value, 7)

    def executeProvide(value: Int): IO[Error, Int] =
      modder.mod(value, 7).provide(env)
  }
}

where CoreDep is a helper initializing required service instances:

  trait CoreDep extends ModderDep

  trait ModderDep {
    protected val env: Core
    protected lazy val modderSvc: Modder.Service = env.get[Modder.Service]
  }

Creating the context using ZIO.provide method requires pushing, and later popping, the environment to/from a stack which in my tests takes around 1 microsecond. It's a substantial overhead for a tiny call, but in real program it's not critical. In rare cases we can always choose a non-environmental approach as an optimization.

About

Scala ZIO: An approach to configuring applications with ZLayers.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages