ZIO's practicalities

ZIO stuff we should all be familiar with

ZManaged

(...) a data structure that encapsulates the acquisition and the release of a resource.

ZManaged[-R, +E, +A]

While creating an A you will need to provide an environment R and you may have an error of type E

ZManaged[-R, +E, +A]

While creating an A you will need to provide an environment R and you may have an error of type E

ZManaged[-R, +E, +A]

While creating an A you will need to provide an environment R and you may have an error of type E

ZManaged
  .make {
    for {
      config <- ZIO.environment[DynamoConfig]
      client <- ZIO.effect {
        DynamoDbAsyncClient
          .builder()
          .region(Region.of(config.signingRegion))
          .endpointOverride(config.endpoint)
          .build()
      }
    } yield client
  } { client =>
    ZIO.effectTotal(client.close())
  } : ZManaged[DynamoConfig, Throwable, DynamoDbAsyncClient]

An example in our code

See Main.scala in subv2 service

Has

(...) express an effect's dependency on a service of type A. For example, RIO[Has[Console.Service], Unit] is an effect that requires a Console.Service service.

 

Essentially: a way to combine different values.

val repo: Has[Repo.Service] = Has(new Repo.Service{})
val logger: Has[Logger.Service] = Has(new Logger.Service{})

val mix: Has[Repo.Service] with Has[Logger.Service] = repo ++ logger

Tag

Common in some signatures.

Evidence that a type A is a service (there is some X = Has[A])

ZLayer

ZLayer[-RIn, +E, +ROut <: Has[_]] is a recipe to build an environment of type ROut, starting from a value RIn, possibly producing an error E during creation.

ZLayer

ZLayer[-RIn, +E, +ROut <: Has[_]] is a recipe to build an environment of type ROut, starting from a value RIn, possibly producing an error E during creation.

trait ZIO[R,E,A] {
  // simplified signature
  def provideLayer[E, R0, R](layer: ZLayer[R0, E, R]): ZIO[R0, E, A]
  
}

val layer: ZLayer[DynamoConfig, Error, DynamoAsync] = ???

val operation: ZIO[DynamoAsync, Error, DynamoGetResponse] = ???

val operation2: ZIO[DynamoConfig, Error, DynamoGetResponse] = operation.provideLayer(layer)

val dynamoConfig: DynamoConfig = ???

val operation3: ZIO[Any, Error, DynamoGetResponse] = operation2.provide(dynamoConfig)

ZLayer

ZLayer[-RIn, +E, +ROut <: Has[_]] is a recipe to build an environment of type ROut, starting from a value RIn, possibly producing an error E during creation.

Simplest constructors:

ZLayer

ZLayer[-RIn, +E, +ROut <: Has[_]] is a recipe to build an environment of type ROut, starting from a value RIn, possibly producing an error E during creation.

Simplest constructors:

Effectful variations, when the function returns a ZIO:

ZLayer

ZLayer[-RIn, +E, +ROut <: Has[_]] is a recipe to build an environment of type ROut, starting from a value RIn, possibly producing an error E during creation.

Simplest constructors:

"service" variations, when the layer input A is a service (requires a Has[A]):

Effectful variations, when the function returns a ZIO:

Managed variations:

Managed variations:

Managed variations:

Some useful type aliases

  type RLayer[-RIn, +ROut]  = ZLayer[RIn, Throwable, ROut]
  type URLayer[-RIn, +ROut] = ZLayer[RIn, Nothing, ROut]
  type Layer[+E, +ROut]     = ZLayer[Any, E, ROut]
  type ULayer[+ROut]        = ZLayer[Any, Nothing, ROut]
  type TaskLayer[+ROut]     = ZLayer[Any, Throwable, ROut]
object DynamoAsync {
  // ...

  val live: ZLayer[Has[DynamoConfig], Throwable, DynamoAsync] =
    ZLayer.fromServiceManaged { config: DynamoConfig =>
      ZManaged
        .makeEffect {
          DynamoDbAsyncClient
            .builder()
            .region(Region.of(config.signingRegion))
            .endpointOverride(config.endpoint)
            .build()
        } { client =>
          ZIO.effectTotal(client.close())
        }
        .map { dynamoDbAsyncClient =>
          new DynamoLive(
            dynamoDbAsyncClient,
            config.subscriptionsTableName
          ): DynamoAsync.Service
        }
    }

}

Combinators

object Logging {
	val consoleLogger: ZLayer[Console, Nothing, Logging] = ???
}

object UserRepo {
	val inMemory: Layer[Nothing, UserRepo] = ???
}

// compose horizontally
val horizontal: ZLayer[Console, Nothing, Logging with UserRepo] = 
  Logging.consoleLogger ++ UserRepo.inMemory

Console.live: ZLayer[Any, Nothing, Console] = ???

// fulfill missing deps, composing vertically
val fullLayer: Layer[Nothing, Logging with UserRepo] = 
  Console.live >>> horizontal
  
  
val logWhatever: ZIO[Console, Nothing, Unit] = logInfo("whatever")

val dbQuery: ZIO[DynamoAsync, , Nothing, Unit] = ???

for {
 _ <- logWhatever
 result <- dbQuery
} yield result

==== logWhatever *> dbQuery

See Main in subscription-commands-processor

Some notes

  • This is mostly for start up / bootstrapping code. We may not need to do this so frequently.
  • ZLayer seems to cover some of the use cases of ZManaged. Maybe prefer ZLayer(?)
  • Official docs are a good read
Made with Slides.com