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

application.mddocs/

Application Framework

ZIO provides a complete application framework with dependency injection, configuration, and lifecycle management for building production applications with proper resource management and graceful shutdown.

Capabilities

ZIOApp - Application Entry Point

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

Application Helpers

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): ZIOApp

Application Factory Methods

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

Application Configuration and Environment

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

Complete Application Examples

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 result

Application Lifecycle and Shutdown

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