or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

application.mdconcurrency.mdcore-effects.mddependency-injection.mderror-handling.mdindex.mdmetrics.mdresource-management.mdservices.mdstm.mdstreams.mdtesting.md
tile.json

dependency-injection.mddocs/

Dependency Injection

ZLayer provides a powerful dependency injection system for building and composing services with compile-time dependency resolution and automatic resource management.

Capabilities

ZLayer Type

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]

Layer Construction

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]

Layer Composition

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)

Layer Transformation

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()))

Layer Lifecycle

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 ()
}

Environment and Service Access

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)
}

Layer Patterns

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 ()
}