ZIO provides a complete application framework with dependency injection, configuration, and lifecycle management for building production applications with proper resource management and graceful shutdown.
The foundational trait for building ZIO applications with custom environments and dependency injection.
/**
* Base trait for ZIO applications with custom environments
*/
trait ZIOApp {
/** The environment type required by this application */
type Environment
/** Evidence that the environment type has a tag for dependency injection */
implicit def environmentTag: EnvironmentTag[Environment]
/** Layer that provides the application environment from command line args */
def bootstrap: ZLayer[ZIOAppArgs, Any, Environment]
/** Main application logic that runs with the provided environment */
def run: ZIO[Environment with ZIOAppArgs with Scope, Any, Any]
/** Timeout for graceful shutdown (default: infinite) */
def gracefulShutdownTimeout: Duration = Duration.Infinity
}
/**
* Default ZIO application that requires no custom environment
*/
abstract class ZIOAppDefault extends ZIOApp {
type Environment = Any
final def bootstrap: ZLayer[ZIOAppArgs, Any, Any] = ZLayer.empty
}
/**
* ZIO application with argument parsing capabilities
*/
abstract class ZIOAppArgs extends ZIOApp {
type Environment = Any
final def bootstrap: ZLayer[ZIOAppArgs, Any, Any] = ZLayer.empty
/** Define command line argument specification */
def args: Args[Any]
}Utility methods for common application operations and lifecycle management.
/**
* Get command line arguments within the application
*/
def getArgs: ZIO[ZIOAppArgs, Nothing, Chunk[String]]
/**
* Exit the application with a specific exit code
*/
def exit(code: ExitCode): UIO[Unit]
/**
* Run an application programmatically (useful for testing)
*/
def invoke(args: Chunk[String]): ZIO[Any, Any, Any]
/**
* Combine two applications into one
*/
def <>(that: ZIOApp): ZIOAppCreate ZIO applications from effects and layers programmatically.
/**
* Create a ZIOApp from an effect and bootstrap layer
*/
def apply[R](
run0: ZIO[R with ZIOAppArgs with Scope, Any, Any],
bootstrap0: ZLayer[ZIOAppArgs, Any, R]
): ZIOApp
/**
* Create a ZIOApp from a simple effect (no custom environment)
*/
def fromZIO(run0: ZIO[ZIOAppArgs, Any, Any]): ZIOApp
/**
* Proxy class for converting ZIOApp values to runnable applications
*/
class Proxy(val app: ZIOApp) extends ZIOApp {
type Environment = app.Environment
def environmentTag = app.environmentTag
def bootstrap = app.bootstrap
def run = app.run
override def gracefulShutdownTimeout = app.gracefulShutdownTimeout
}Usage Examples:
import zio._
// Simple application
object SimpleApp extends ZIOAppDefault {
def run = for {
_ <- Console.printLine("Hello, ZIO!")
_ <- Console.printLine("Application completed successfully")
} yield ExitCode.success
}
// Application with command line arguments
object ArgsApp extends ZIOAppDefault {
def run = for {
args <- getArgs
_ <- Console.printLine(s"Received ${args.length} arguments")
_ <- ZIO.foreach(args.zipWithIndex) { case (arg, index) =>
Console.printLine(s"Arg[$index]: $arg")
}
} yield ExitCode.success
}
// Application with custom environment
trait UserService {
def getUser(id: Long): Task[User]
def listUsers(): Task[List[User]]
}
object DatabaseApp extends ZIOApp {
type Environment = UserService with Console
def bootstrap =
Console.live ++
(DatabaseConfig.live >>> Database.live >>> UserService.live)
def run = for {
userService <- ZIO.service[UserService]
users <- userService.listUsers()
_ <- Console.printLine(s"Found ${users.length} users")
_ <- ZIO.foreach(users)(user => Console.printLine(s"User: ${user.name}"))
} yield ExitCode.success
}Patterns for configuring applications and managing environments.
// Configuration loading pattern
case class AppConfig(
serverPort: Int,
databaseUrl: String,
logLevel: String,
enableMetrics: Boolean
)
object AppConfig {
// Load from environment variables
val fromEnv: Task[AppConfig] = for {
port <- System.env("SERVER_PORT").map(_.getOrElse("8080")).map(_.toInt)
dbUrl <- System.env("DATABASE_URL").someOrFail(new RuntimeException("DATABASE_URL required"))
logLevel <- System.env("LOG_LEVEL").map(_.getOrElse("INFO"))
metrics <- System.env("ENABLE_METRICS").map(_.getOrElse("false")).map(_.toBoolean)
} yield AppConfig(port, dbUrl, logLevel, metrics)
// Layer for dependency injection
val live: TaskLayer[AppConfig] = ZLayer.fromZIO(fromEnv)
}
// Service layer pattern
trait DatabaseService {
def connect(): Task[Unit]
def disconnect(): Task[Unit]
def query(sql: String): Task[List[Map[String, Any]]]
}
object DatabaseService {
val live: RLayer[AppConfig, DatabaseService] = ZLayer {
for {
config <- ZIO.service[AppConfig]
} yield DatabaseServiceImpl(config.databaseUrl)
}
}
// HTTP server layer pattern
trait HttpServer {
def start(): Task[Unit]
def stop(): Task[Unit]
}
object HttpServer {
val live: RLayer[AppConfig with UserService, HttpServer] = ZLayer {
for {
config <- ZIO.service[AppConfig]
userService <- ZIO.service[UserService]
} yield HttpServerImpl(config.serverPort, userService)
}
}Full application examples demonstrating various patterns and configurations.
// Microservice application
object MicroserviceApp extends ZIOApp {
type Environment = AppConfig with DatabaseService with HttpServer with Console
def bootstrap =
AppConfig.live ++
Console.live ++
(AppConfig.live >>> DatabaseService.live) ++
(AppConfig.live ++ UserService.live >>> HttpServer.live)
def run = for {
config <- ZIO.service[AppConfig]
db <- ZIO.service[DatabaseService]
server <- ZIO.service[HttpServer]
_ <- Console.printLine(s"Starting microservice on port ${config.serverPort}")
_ <- db.connect()
_ <- server.start()
_ <- Console.printLine("Microservice started successfully")
// Run until interrupted
_ <- ZIO.never
} yield ExitCode.success
}
// CLI application with subcommands
sealed trait Command
case class ListUsers(limit: Int) extends Command
case class CreateUser(name: String, email: String) extends Command
case class DeleteUser(id: Long) extends Command
object CliApp extends ZIOApp {
type Environment = UserService with Console
def bootstrap =
Console.live ++
(DatabaseConfig.live >>> Database.live >>> UserService.live)
def run = for {
args <- getArgs
command <- parseCommand(args)
_ <- executeCommand(command)
} yield ExitCode.success
private def parseCommand(args: Chunk[String]): Task[Command] = {
args.toList match {
case "list" :: limit :: Nil =>
ZIO.succeed(ListUsers(limit.toInt))
case "create" :: name :: email :: Nil =>
ZIO.succeed(CreateUser(name, email))
case "delete" :: id :: Nil =>
ZIO.succeed(DeleteUser(id.toLong))
case _ =>
ZIO.fail(new IllegalArgumentException("Invalid command"))
}
}
private def executeCommand(command: Command): ZIO[UserService with Console, Throwable, Unit] = {
command match {
case ListUsers(limit) =>
for {
users <- ZIO.service[UserService].flatMap(_.listUsers(limit))
_ <- ZIO.foreach(users)(user => Console.printLine(s"${user.id}: ${user.name}"))
} yield ()
case CreateUser(name, email) =>
for {
user <- ZIO.service[UserService].flatMap(_.createUser(name, email))
_ <- Console.printLine(s"Created user: ${user.id}")
} yield ()
case DeleteUser(id) =>
for {
_ <- ZIO.service[UserService].flatMap(_.deleteUser(id))
_ <- Console.printLine(s"Deleted user: $id")
} yield ()
}
}
}
// Batch processing application
object BatchProcessor extends ZIOApp {
type Environment = AppConfig with DatabaseService with Console
def bootstrap =
AppConfig.live ++
Console.live ++
(AppConfig.live >>> DatabaseService.live)
def run = for {
config <- ZIO.service[AppConfig]
_ <- Console.printLine("Starting batch processing job")
// Process in batches
_ <- processInBatches(batchSize = 1000)
_ <- Console.printLine("Batch processing completed")
} yield ExitCode.success
private def processInBatches(batchSize: Int): ZIO[DatabaseService with Console, Throwable, Unit] = {
def processBatch(offset: Int): ZIO[DatabaseService with Console, Throwable, Boolean] = {
for {
db <- ZIO.service[DatabaseService]
batch <- db.query(s"SELECT * FROM records LIMIT $batchSize OFFSET $offset")
_ <- ZIO.when(batch.nonEmpty) {
Console.printLine(s"Processing batch of ${batch.length} records at offset $offset") *>
ZIO.foreach(batch)(processRecord) *>
ZIO.sleep(100.millis) // Rate limiting
}
} yield batch.nonEmpty
}
def loop(offset: Int): ZIO[DatabaseService with Console, Throwable, Unit] = {
processBatch(offset).flatMap { hasMore =>
if (hasMore) loop(offset + batchSize)
else ZIO.unit
}
}
loop(0)
}
private def processRecord(record: Map[String, Any]): Task[Unit] = {
// Process individual record
ZIO.succeed(())
}
}
// Testing applications
object TestableApp {
def make(userService: UserService): ZIOApp = {
ZIOApp.fromZIO {
for {
users <- userService.listUsers()
_ <- Console.printLine(s"Found ${users.length} users")
} yield ExitCode.success
}
}
}
// Application testing
val testApp = for {
mockUserService <- ZIO.succeed(MockUserService(List(User(1, "Alice"), User(2, "Bob"))))
app = TestableApp.make(mockUserService)
result <- app.invoke(Chunk.empty)
} yield resultManaging application lifecycle, graceful shutdown, and resource cleanup.
// Graceful shutdown pattern
object GracefulApp extends ZIOApp {
type Environment = HttpServer with DatabaseService with Console
override def gracefulShutdownTimeout = 30.seconds
def bootstrap =
Console.live ++
DatabaseService.live ++
HttpServer.live
def run = ZIO.scoped {
for {
server <- ZIO.service[HttpServer]
db <- ZIO.service[DatabaseService]
// Add shutdown hooks
_ <- Scope.addFinalizerExit { exit =>
for {
_ <- Console.printLine("Shutting down gracefully...")
_ <- server.stop().timeout(10.seconds).ignore
_ <- db.disconnect().timeout(5.seconds).ignore
_ <- Console.printLine("Shutdown complete")
} yield ()
}
// Start services
_ <- db.connect()
_ <- server.start()
_ <- Console.printLine("Application started")
// Run until interrupted
_ <- ZIO.never
} yield ExitCode.success
}
}
// Health check and monitoring
trait HealthCheck {
def check(): Task[HealthStatus]
}
case class HealthStatus(service: String, healthy: Boolean, message: Option[String])
object HealthCheckApp extends ZIOApp {
type Environment = List[HealthCheck] with Console
def bootstrap =
Console.live ++
ZLayer.succeed(List(
DatabaseHealthCheck(),
HttpHealthCheck(),
DiskSpaceHealthCheck()
))
def run = for {
_ <- performHealthChecks.repeat(Schedule.fixed(30.seconds))
} yield ExitCode.success
private def performHealthChecks: ZIO[List[HealthCheck] with Console, Throwable, Unit] = {
for {
healthChecks <- ZIO.service[List[HealthCheck]]
results <- ZIO.foreachPar(healthChecks)(_.check())
healthy = results.forall(_.healthy)
_ <- Console.printLine(s"Health check: ${if (healthy) "HEALTHY" else "UNHEALTHY"}")
_ <- ZIO.foreach(results.filter(!_.healthy)) { status =>
Console.printLineError(s"${status.service}: ${status.message.getOrElse("Unknown error")}")
}
} yield ()
}
}
// Multi-environment application
object MultiEnvApp extends ZIOApp {
type Environment = AppConfig with DatabaseService with Console
def bootstrap = {
val env = System.env("APP_ENV").map(_.getOrElse("development"))
ZLayer.fromZIO(env).flatMap {
case "production" => productionLayers
case "staging" => stagingLayers
case "development" => developmentLayers
case "test" => testLayers
case unknown => ZLayer.fail(new RuntimeException(s"Unknown environment: $unknown"))
}
}
private val productionLayers =
Console.live ++
ProductionConfig.live ++
(ProductionConfig.live >>> DatabaseService.live)
private val developmentLayers =
Console.live ++
DevelopmentConfig.live ++
(DevelopmentConfig.live >>> DatabaseService.live)
// ... other environment layers
def run = for {
config <- ZIO.service[AppConfig]
_ <- Console.printLine(s"Running in environment: ${config.environment}")
_ <- mainApplicationLogic
} yield ExitCode.success
}