ZLayer provides a powerful dependency injection system for building and composing services with compile-time dependency resolution and automatic resource management.
The core layer type for describing how to build services with dependency injection.
/**
* A ZLayer describes how to build services of type ROut from dependencies of type RIn
* - RIn: Required dependencies
* - E: Error type during construction
* - ROut: Services provided by this layer
*/
sealed abstract class ZLayer[-RIn, +E, +ROut]
// Layer 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]Create layers from values, effects, and scoped resources.
/**
* Create a layer from a constant value
*/
def succeed[A: Tag](a: => A): ULayer[A]
/**
* Create a layer that always fails
*/
def fail[E](e: => E): Layer[E, Nothing]
/**
* Create a layer from a ZIO effect
*/
def fromZIO[R, E, A: Tag](zio: => ZIO[R, E, A]): ZLayer[R, E, A]
/**
* Create a layer from another layer by passing through dependencies
*/
def service[A: Tag]: ZLayer[A, Nothing, A]
/**
* Create a layer from a scoped resource (with automatic cleanup)
*/
def scoped[R]: ScopedPartiallyApplied[R]
/**
* Create a layer from a function of dependencies
*/
def fromFunction[In]: FunctionConstructor[In]#Out
/**
* Create multiple instances of a service in parallel
*/
def collectAll[R, E, A: Tag](layers: Iterable[ZLayer[R, E, A]]): ZLayer[R, E, List[A]]
/**
* Create a layer that provides debug information
*/
def debug: ULayer[Unit]Usage Examples:
import zio._
// From constant value
val configLayer = ZLayer.succeed(AppConfig(port = 8080, debug = true))
// From effect
val databaseLayer = ZLayer.fromZIO {
ZIO.attempt(new DatabaseImpl("jdbc:postgresql://localhost/mydb"))
}
// From scoped resource (auto-cleanup)
val connectionLayer = ZLayer.scoped {
ZIO.acquireRelease(
ZIO.attempt(Connection.create())
)(conn => ZIO.succeed(conn.close()).ignore)
}
// From function of dependencies
val userServiceLayer = ZLayer.fromFunction(UserServiceImpl.apply _)
// Pass-through layer
val configPassthrough = ZLayer.service[AppConfig]Compose layers using various operators for building complex dependency graphs.
/**
* Combine two layers horizontally (both outputs available)
*/
def ++[E1 >: E, RIn2, ROut2](that: => ZLayer[RIn2, E1, ROut2]): ZLayer[RIn with RIn2, E1, ROut with ROut2]
/**
* Compose layers vertically (output of first feeds into second)
*/
def >>>[E1 >: E, ROut2](that: => ZLayer[ROut, E1, ROut2]): ZLayer[RIn, E1, ROut2]
/**
* Compose and merge outputs (both outputs available after composition)
*/
def >+>[E1 >: E, ROut2](that: => ZLayer[ROut, E1, ROut2]): ZLayer[RIn, E1, ROut with ROut2]
/**
* Use this layer only if condition is true
*/
def when[R1 <: RIn](p: => Boolean): ZLayer[R1, E, Option[ROut]]
/**
* Provide partial environment to this layer
*/
def provideSomeEnvironment[RIn0](f: ZEnvironment[RIn0] => ZEnvironment[RIn]): ZLayer[RIn0, E, ROut]
/**
* Fresh instance for each use (no sharing)
*/
def fresh: ZLayer[RIn, E, ROut]
/**
* Memoized layer (singleton instance)
*/
def memoize: UIO[ZLayer[RIn, E, ROut]]Usage Examples:
// Horizontal composition (combine outputs)
val dataLayer = databaseLayer ++ cacheLayer ++ loggerLayer
// Vertical composition (chain dependencies)
val fullStack = configLayer >>> databaseLayer >>> userServiceLayer
// Compose and merge
val enrichedService = databaseLayer >+> (auditLayer >>> userServiceLayer)
// Complex dependency graph
val appLayer =
configLayer ++
loggerLayer ++
(configLayer >>> databaseLayer) ++
(configLayer ++ databaseLayer >>> userServiceLayer)Transform layer inputs, outputs, and errors.
/**
* Transform the output environment
*/
def map[ROut1](f: ZEnvironment[ROut] => ZEnvironment[ROut1]): ZLayer[RIn, E, ROut1]
/**
* Transform the error type
*/
def mapError[E1](f: E => E1): ZLayer[RIn, E1, ROut]
/**
* Transform both error and output
*/
def bimap[E1, ROut1](f: E => E1, g: ZEnvironment[ROut] => ZEnvironment[ROut1]): ZLayer[RIn, E1, ROut1]
/**
* Recover from all layer construction errors
*/
def catchAll[RIn1 <: RIn, E1, ROut1 >: ROut](handler: E => ZLayer[RIn1, E1, ROut1]): ZLayer[RIn1, E1, ROut1]
/**
* Provide a fallback layer if construction fails
*/
def orElse[RIn1 <: RIn, E1, ROut1 >: ROut](that: => ZLayer[RIn1, E1, ROut1]): ZLayer[RIn1, E1, ROut1]
/**
* Retry layer construction according to a schedule
*/
def retry[RIn1 <: RIn, S](schedule: => Schedule[RIn1, E, S]): ZLayer[RIn1, E, ROut]
/**
* Project out a specific service from the layer output
*/
def project[A: Tag](implicit ev: A <:< ROut): ZLayer[RIn, E, A]Usage Examples:
// Transform output
val enhancedDb = databaseLayer.map { env =>
val db = env.get[Database]
ZEnvironment(DatabaseWithMetrics(db))
}
// Error recovery
val resilientLayer = databaseLayer.catchAll { error =>
Console.printLineError(s"DB failed: $error") *>
ZLayer.succeed(MockDatabase())
}
// Fallback chain
val reliableDb =
primaryDatabaseLayer
.orElse(secondaryDatabaseLayer)
.orElse(ZLayer.succeed(LocalDatabase()))Build, launch, and manage layer lifecycles with proper resource management.
/**
* Build the layer into a scoped ZIO effect
*/
def build: ZIO[RIn with Scope, E, ZEnvironment[ROut]]
/**
* Launch the layer and run it forever (for daemon services)
*/
def launch: ZIO[RIn, E, Nothing]
/**
* Convert layer to a Runtime with the provided services
*/
def toRuntime: ZIO[Scope, E, Runtime[ROut]]
/**
* Provide this layer to a ZIO effect
*/
def apply[R1 <: ROut, E1 >: E, A](zio: ZIO[R1, E1, A]): ZIO[RIn, E1, A]Usage Examples:
// Build layer in scope
val program = ZIO.scoped {
for {
env <- appLayer.build
_ <- program.provideEnvironment(env)
} yield ()
}
// Launch daemon service
val server = httpServerLayer.launch
// Use layer with effect
val result = appLayer {
for {
user <- ZIO.service[UserService]
_ <- user.createUser("Alice")
} yield ()
}Work with ZEnvironment and service tags for type-safe dependency access.
/**
* Tag for service identification in dependency injection
*/
sealed trait Tag[A] extends EnvironmentTag[A] {
def tag: LightTypeTag
}
/**
* Type-safe environment container
*/
sealed trait ZEnvironment[+R] {
def get[A >: R](implicit tag: Tag[A]): A
def add[A: Tag](a: A): ZEnvironment[R with A]
def union[R1](that: ZEnvironment[R1]): ZEnvironment[R with R1]
def prune[R1](implicit tag: EnvironmentTag[R1]): ZEnvironment[R1]
}
object ZEnvironment {
def empty: ZEnvironment[Any]
def apply[A: Tag](a: A): ZEnvironment[A]
}Usage Examples:
// Create environment
val env = ZEnvironment(DatabaseImpl()) ++ ZEnvironment(CacheImpl())
// Access services
val database = env.get[Database]
val cache = env.get[Cache]
// Service-based layer construction
case class UserService(db: Database, cache: Cache)
val userServiceLayer = ZLayer {
for {
db <- ZIO.service[Database]
cache <- ZIO.service[Cache]
} yield UserService(db, cache)
}Common patterns for organizing and structuring layer dependencies.
// Service pattern - single responsibility
trait UserRepository {
def findUser(id: Long): Task[Option[User]]
def saveUser(user: User): Task[Unit]
}
val userRepositoryLayer = ZLayer {
for {
db <- ZIO.service[Database]
} yield UserRepositoryImpl(db)
}
// Configuration pattern
case class DatabaseConfig(url: String, maxConnections: Int)
val databaseConfigLayer = ZLayer.fromZIO {
ZIO.config[DatabaseConfig](DatabaseConfig.config)
}
// Resource pattern with cleanup
val connectionPoolLayer = ZLayer.scoped {
ZIO.acquireRelease(
ZIO.attempt(ConnectionPool.create(maxSize = 10))
)(pool => ZIO.succeed(pool.shutdown()).ignore)
}
// Factory pattern
trait UserServiceFactory {
def create(config: UserConfig): UserService
}
val userServiceFactoryLayer = ZLayer.succeed {
new UserServiceFactory {
def create(config: UserConfig): UserService =
UserServiceImpl(config)
}
}Usage Examples:
// Application wiring
val liveAppLayer =
// Configuration
configLayer ++
loggerLayer ++
// Infrastructure
(configLayer >>> databaseLayer) ++
(configLayer >>> cacheLayer) ++
// Services
(databaseLayer >>> userRepositoryLayer) ++
(userRepositoryLayer ++ cacheLayer >>> userServiceLayer) ++
// HTTP
(userServiceLayer >>> httpRoutesLayer) ++
(configLayer ++ httpRoutesLayer >>> httpServerLayer)
// Test layer with mocks
val testAppLayer =
ZLayer.succeed(TestConfig()) ++
ZLayer.succeed(MockDatabase()) ++
ZLayer.succeed(MockCache()) ++
(ZLayer.service[MockDatabase] >>> userRepositoryLayer)
// Main application
object MyApp extends ZIOAppDefault {
def run = program.provide(liveAppLayer)
val program = for {
server <- ZIO.service[HttpServer]
_ <- server.start
_ <- Console.printLine("Server started on port 8080")
_ <- ZIO.never
} yield ()
}