0
# Application Framework
1
2
ZIO provides a complete application framework with dependency injection, configuration, and lifecycle management for building production applications with proper resource management and graceful shutdown.
3
4
## Capabilities
5
6
### ZIOApp - Application Entry Point
7
8
The foundational trait for building ZIO applications with custom environments and dependency injection.
9
10
```scala { .api }
11
/**
12
* Base trait for ZIO applications with custom environments
13
*/
14
trait ZIOApp {
15
/** The environment type required by this application */
16
type Environment
17
18
/** Evidence that the environment type has a tag for dependency injection */
19
implicit def environmentTag: EnvironmentTag[Environment]
20
21
/** Layer that provides the application environment from command line args */
22
def bootstrap: ZLayer[ZIOAppArgs, Any, Environment]
23
24
/** Main application logic that runs with the provided environment */
25
def run: ZIO[Environment with ZIOAppArgs with Scope, Any, Any]
26
27
/** Timeout for graceful shutdown (default: infinite) */
28
def gracefulShutdownTimeout: Duration = Duration.Infinity
29
}
30
31
/**
32
* Default ZIO application that requires no custom environment
33
*/
34
abstract class ZIOAppDefault extends ZIOApp {
35
type Environment = Any
36
final def bootstrap: ZLayer[ZIOAppArgs, Any, Any] = ZLayer.empty
37
}
38
39
/**
40
* ZIO application with argument parsing capabilities
41
*/
42
abstract class ZIOAppArgs extends ZIOApp {
43
type Environment = Any
44
final def bootstrap: ZLayer[ZIOAppArgs, Any, Any] = ZLayer.empty
45
46
/** Define command line argument specification */
47
def args: Args[Any]
48
}
49
```
50
51
### Application Helpers
52
53
Utility methods for common application operations and lifecycle management.
54
55
```scala { .api }
56
/**
57
* Get command line arguments within the application
58
*/
59
def getArgs: ZIO[ZIOAppArgs, Nothing, Chunk[String]]
60
61
/**
62
* Exit the application with a specific exit code
63
*/
64
def exit(code: ExitCode): UIO[Unit]
65
66
/**
67
* Run an application programmatically (useful for testing)
68
*/
69
def invoke(args: Chunk[String]): ZIO[Any, Any, Any]
70
71
/**
72
* Combine two applications into one
73
*/
74
def <>(that: ZIOApp): ZIOApp
75
```
76
77
### Application Factory Methods
78
79
Create ZIO applications from effects and layers programmatically.
80
81
```scala { .api }
82
/**
83
* Create a ZIOApp from an effect and bootstrap layer
84
*/
85
def apply[R](
86
run0: ZIO[R with ZIOAppArgs with Scope, Any, Any],
87
bootstrap0: ZLayer[ZIOAppArgs, Any, R]
88
): ZIOApp
89
90
/**
91
* Create a ZIOApp from a simple effect (no custom environment)
92
*/
93
def fromZIO(run0: ZIO[ZIOAppArgs, Any, Any]): ZIOApp
94
95
/**
96
* Proxy class for converting ZIOApp values to runnable applications
97
*/
98
class Proxy(val app: ZIOApp) extends ZIOApp {
99
type Environment = app.Environment
100
def environmentTag = app.environmentTag
101
def bootstrap = app.bootstrap
102
def run = app.run
103
override def gracefulShutdownTimeout = app.gracefulShutdownTimeout
104
}
105
```
106
107
**Usage Examples:**
108
109
```scala
110
import zio._
111
112
// Simple application
113
object SimpleApp extends ZIOAppDefault {
114
def run = for {
115
_ <- Console.printLine("Hello, ZIO!")
116
_ <- Console.printLine("Application completed successfully")
117
} yield ExitCode.success
118
}
119
120
// Application with command line arguments
121
object ArgsApp extends ZIOAppDefault {
122
def run = for {
123
args <- getArgs
124
_ <- Console.printLine(s"Received ${args.length} arguments")
125
_ <- ZIO.foreach(args.zipWithIndex) { case (arg, index) =>
126
Console.printLine(s"Arg[$index]: $arg")
127
}
128
} yield ExitCode.success
129
}
130
131
// Application with custom environment
132
trait UserService {
133
def getUser(id: Long): Task[User]
134
def listUsers(): Task[List[User]]
135
}
136
137
object DatabaseApp extends ZIOApp {
138
type Environment = UserService with Console
139
140
def bootstrap =
141
Console.live ++
142
(DatabaseConfig.live >>> Database.live >>> UserService.live)
143
144
def run = for {
145
userService <- ZIO.service[UserService]
146
users <- userService.listUsers()
147
_ <- Console.printLine(s"Found ${users.length} users")
148
_ <- ZIO.foreach(users)(user => Console.printLine(s"User: ${user.name}"))
149
} yield ExitCode.success
150
}
151
```
152
153
### Application Configuration and Environment
154
155
Patterns for configuring applications and managing environments.
156
157
```scala { .api }
158
// Configuration loading pattern
159
case class AppConfig(
160
serverPort: Int,
161
databaseUrl: String,
162
logLevel: String,
163
enableMetrics: Boolean
164
)
165
166
object AppConfig {
167
// Load from environment variables
168
val fromEnv: Task[AppConfig] = for {
169
port <- System.env("SERVER_PORT").map(_.getOrElse("8080")).map(_.toInt)
170
dbUrl <- System.env("DATABASE_URL").someOrFail(new RuntimeException("DATABASE_URL required"))
171
logLevel <- System.env("LOG_LEVEL").map(_.getOrElse("INFO"))
172
metrics <- System.env("ENABLE_METRICS").map(_.getOrElse("false")).map(_.toBoolean)
173
} yield AppConfig(port, dbUrl, logLevel, metrics)
174
175
// Layer for dependency injection
176
val live: TaskLayer[AppConfig] = ZLayer.fromZIO(fromEnv)
177
}
178
179
// Service layer pattern
180
trait DatabaseService {
181
def connect(): Task[Unit]
182
def disconnect(): Task[Unit]
183
def query(sql: String): Task[List[Map[String, Any]]]
184
}
185
186
object DatabaseService {
187
val live: RLayer[AppConfig, DatabaseService] = ZLayer {
188
for {
189
config <- ZIO.service[AppConfig]
190
} yield DatabaseServiceImpl(config.databaseUrl)
191
}
192
}
193
194
// HTTP server layer pattern
195
trait HttpServer {
196
def start(): Task[Unit]
197
def stop(): Task[Unit]
198
}
199
200
object HttpServer {
201
val live: RLayer[AppConfig with UserService, HttpServer] = ZLayer {
202
for {
203
config <- ZIO.service[AppConfig]
204
userService <- ZIO.service[UserService]
205
} yield HttpServerImpl(config.serverPort, userService)
206
}
207
}
208
```
209
210
### Complete Application Examples
211
212
Full application examples demonstrating various patterns and configurations.
213
214
```scala { .api }
215
// Microservice application
216
object MicroserviceApp extends ZIOApp {
217
type Environment = AppConfig with DatabaseService with HttpServer with Console
218
219
def bootstrap =
220
AppConfig.live ++
221
Console.live ++
222
(AppConfig.live >>> DatabaseService.live) ++
223
(AppConfig.live ++ UserService.live >>> HttpServer.live)
224
225
def run = for {
226
config <- ZIO.service[AppConfig]
227
db <- ZIO.service[DatabaseService]
228
server <- ZIO.service[HttpServer]
229
230
_ <- Console.printLine(s"Starting microservice on port ${config.serverPort}")
231
_ <- db.connect()
232
_ <- server.start()
233
_ <- Console.printLine("Microservice started successfully")
234
235
// Run until interrupted
236
_ <- ZIO.never
237
238
} yield ExitCode.success
239
}
240
241
// CLI application with subcommands
242
sealed trait Command
243
case class ListUsers(limit: Int) extends Command
244
case class CreateUser(name: String, email: String) extends Command
245
case class DeleteUser(id: Long) extends Command
246
247
object CliApp extends ZIOApp {
248
type Environment = UserService with Console
249
250
def bootstrap =
251
Console.live ++
252
(DatabaseConfig.live >>> Database.live >>> UserService.live)
253
254
def run = for {
255
args <- getArgs
256
command <- parseCommand(args)
257
_ <- executeCommand(command)
258
} yield ExitCode.success
259
260
private def parseCommand(args: Chunk[String]): Task[Command] = {
261
args.toList match {
262
case "list" :: limit :: Nil =>
263
ZIO.succeed(ListUsers(limit.toInt))
264
case "create" :: name :: email :: Nil =>
265
ZIO.succeed(CreateUser(name, email))
266
case "delete" :: id :: Nil =>
267
ZIO.succeed(DeleteUser(id.toLong))
268
case _ =>
269
ZIO.fail(new IllegalArgumentException("Invalid command"))
270
}
271
}
272
273
private def executeCommand(command: Command): ZIO[UserService with Console, Throwable, Unit] = {
274
command match {
275
case ListUsers(limit) =>
276
for {
277
users <- ZIO.service[UserService].flatMap(_.listUsers(limit))
278
_ <- ZIO.foreach(users)(user => Console.printLine(s"${user.id}: ${user.name}"))
279
} yield ()
280
281
case CreateUser(name, email) =>
282
for {
283
user <- ZIO.service[UserService].flatMap(_.createUser(name, email))
284
_ <- Console.printLine(s"Created user: ${user.id}")
285
} yield ()
286
287
case DeleteUser(id) =>
288
for {
289
_ <- ZIO.service[UserService].flatMap(_.deleteUser(id))
290
_ <- Console.printLine(s"Deleted user: $id")
291
} yield ()
292
}
293
}
294
}
295
296
// Batch processing application
297
object BatchProcessor extends ZIOApp {
298
type Environment = AppConfig with DatabaseService with Console
299
300
def bootstrap =
301
AppConfig.live ++
302
Console.live ++
303
(AppConfig.live >>> DatabaseService.live)
304
305
def run = for {
306
config <- ZIO.service[AppConfig]
307
_ <- Console.printLine("Starting batch processing job")
308
309
// Process in batches
310
_ <- processInBatches(batchSize = 1000)
311
312
_ <- Console.printLine("Batch processing completed")
313
} yield ExitCode.success
314
315
private def processInBatches(batchSize: Int): ZIO[DatabaseService with Console, Throwable, Unit] = {
316
def processBatch(offset: Int): ZIO[DatabaseService with Console, Throwable, Boolean] = {
317
for {
318
db <- ZIO.service[DatabaseService]
319
batch <- db.query(s"SELECT * FROM records LIMIT $batchSize OFFSET $offset")
320
321
_ <- ZIO.when(batch.nonEmpty) {
322
Console.printLine(s"Processing batch of ${batch.length} records at offset $offset") *>
323
ZIO.foreach(batch)(processRecord) *>
324
ZIO.sleep(100.millis) // Rate limiting
325
}
326
} yield batch.nonEmpty
327
}
328
329
def loop(offset: Int): ZIO[DatabaseService with Console, Throwable, Unit] = {
330
processBatch(offset).flatMap { hasMore =>
331
if (hasMore) loop(offset + batchSize)
332
else ZIO.unit
333
}
334
}
335
336
loop(0)
337
}
338
339
private def processRecord(record: Map[String, Any]): Task[Unit] = {
340
// Process individual record
341
ZIO.succeed(())
342
}
343
}
344
345
// Testing applications
346
object TestableApp {
347
def make(userService: UserService): ZIOApp = {
348
ZIOApp.fromZIO {
349
for {
350
users <- userService.listUsers()
351
_ <- Console.printLine(s"Found ${users.length} users")
352
} yield ExitCode.success
353
}
354
}
355
}
356
357
// Application testing
358
val testApp = for {
359
mockUserService <- ZIO.succeed(MockUserService(List(User(1, "Alice"), User(2, "Bob"))))
360
app = TestableApp.make(mockUserService)
361
result <- app.invoke(Chunk.empty)
362
} yield result
363
```
364
365
### Application Lifecycle and Shutdown
366
367
Managing application lifecycle, graceful shutdown, and resource cleanup.
368
369
```scala { .api }
370
// Graceful shutdown pattern
371
object GracefulApp extends ZIOApp {
372
type Environment = HttpServer with DatabaseService with Console
373
374
override def gracefulShutdownTimeout = 30.seconds
375
376
def bootstrap =
377
Console.live ++
378
DatabaseService.live ++
379
HttpServer.live
380
381
def run = ZIO.scoped {
382
for {
383
server <- ZIO.service[HttpServer]
384
db <- ZIO.service[DatabaseService]
385
386
// Add shutdown hooks
387
_ <- Scope.addFinalizerExit { exit =>
388
for {
389
_ <- Console.printLine("Shutting down gracefully...")
390
_ <- server.stop().timeout(10.seconds).ignore
391
_ <- db.disconnect().timeout(5.seconds).ignore
392
_ <- Console.printLine("Shutdown complete")
393
} yield ()
394
}
395
396
// Start services
397
_ <- db.connect()
398
_ <- server.start()
399
_ <- Console.printLine("Application started")
400
401
// Run until interrupted
402
_ <- ZIO.never
403
404
} yield ExitCode.success
405
}
406
}
407
408
// Health check and monitoring
409
trait HealthCheck {
410
def check(): Task[HealthStatus]
411
}
412
413
case class HealthStatus(service: String, healthy: Boolean, message: Option[String])
414
415
object HealthCheckApp extends ZIOApp {
416
type Environment = List[HealthCheck] with Console
417
418
def bootstrap =
419
Console.live ++
420
ZLayer.succeed(List(
421
DatabaseHealthCheck(),
422
HttpHealthCheck(),
423
DiskSpaceHealthCheck()
424
))
425
426
def run = for {
427
_ <- performHealthChecks.repeat(Schedule.fixed(30.seconds))
428
} yield ExitCode.success
429
430
private def performHealthChecks: ZIO[List[HealthCheck] with Console, Throwable, Unit] = {
431
for {
432
healthChecks <- ZIO.service[List[HealthCheck]]
433
results <- ZIO.foreachPar(healthChecks)(_.check())
434
435
healthy = results.forall(_.healthy)
436
_ <- Console.printLine(s"Health check: ${if (healthy) "HEALTHY" else "UNHEALTHY"}")
437
438
_ <- ZIO.foreach(results.filter(!_.healthy)) { status =>
439
Console.printLineError(s"${status.service}: ${status.message.getOrElse("Unknown error")}")
440
}
441
} yield ()
442
}
443
}
444
445
// Multi-environment application
446
object MultiEnvApp extends ZIOApp {
447
type Environment = AppConfig with DatabaseService with Console
448
449
def bootstrap = {
450
val env = System.env("APP_ENV").map(_.getOrElse("development"))
451
452
ZLayer.fromZIO(env).flatMap {
453
case "production" => productionLayers
454
case "staging" => stagingLayers
455
case "development" => developmentLayers
456
case "test" => testLayers
457
case unknown => ZLayer.fail(new RuntimeException(s"Unknown environment: $unknown"))
458
}
459
}
460
461
private val productionLayers =
462
Console.live ++
463
ProductionConfig.live ++
464
(ProductionConfig.live >>> DatabaseService.live)
465
466
private val developmentLayers =
467
Console.live ++
468
DevelopmentConfig.live ++
469
(DevelopmentConfig.live >>> DatabaseService.live)
470
471
// ... other environment layers
472
473
def run = for {
474
config <- ZIO.service[AppConfig]
475
_ <- Console.printLine(s"Running in environment: ${config.environment}")
476
_ <- mainApplicationLogic
477
} yield ExitCode.success
478
}
479
```