1
Error Management
ZIO vs Future
Dublin Scala Meetup, May 11th
John A. De Goes   @jdegoes
Kai @kaidaxofficial
...with help of Pavel Shirshov @shirshovp
Agenda
A Tale of Two Effects
2
Next-Gen Debugging Conclusion
Managing Errors
3
A TALE OF
TWO EFFECTS
FUTURE
Parallel
Future enables parallel
computations and
non-blocking gathering.
Error
Future has a built-in error
channel for
Throwable-based errors.
Eager
Future is not referentially
transparent; refactoring
may change behavior.
4
FUTURE
Async
Future enables
non-blocking code that
efficiently uses threads.
5
Future ZIO Effect
go running
Procedural Functional
def program: Unit = {
println("What’s your name?")
val name = readLine()
println(s"Howdy. $name!")
return ()
}
val program = for {
_ <- putStrLn("What’s your name?")
name <- getStrLn
_ <- putStrLn(s"Howdy, $name")
} yield ()
7
Future ZIO
Performance > 100x slower > 100x faster
Cancellation & Timeouts 𐄂 ✔
Effect Combinators 𐄂 ✔
Resource Safety 𐄂 ✔
Fiber Concurrency 𐄂 ✔
Equational & Type Reasoning 𐄂 ✔
Testability 𐄂 ✔
Error Management & Debugging ? ?
8
ZIO[R, E, A]
Environment Type
Failure Type
Success Type
9
ZIO[R, E, A]
~
R => Either[E, A]
10
type Task [ +A] = ZIO[Any, Throwable, A]
type UIO [ +A] = ZIO[Any, Nothing, A]
type TaskR[+R,+A] = ZIO[ R, Throwable, A]
type IO [+E,+A] = ZIO[Any, E, A]
11
type Task [ +A] = ZIO[Any, Throwable, A]
type UIO [ +A] = ZIO[Any, Nothing, A]
type TaskR[+R,+A] = ZIO[ R, Throwable, A]
type IO [+E,+A] = ZIO[Any, E, A]
12
type Task [ +A] = ZIO[Any, Throwable, A]
type UIO [ +A] = ZIO[Any, Nothing, A]
type TaskR[+R,+A] = ZIO[ R, Throwable, A]
type IO [+E,+A] = ZIO[Any, E, A]
13
type Task [ +A] = ZIO[Any, Throwable, A]
type UIO [ +A] = ZIO[Any, Nothing, A]
type TaskR[+R,+A] = ZIO[ R, Throwable, A]
type IO [+E,+A] = ZIO[Any, E, A]
ZIO[R, E, A]
Synchronous Asynchronous Errors Resource
ZIO.succeed(…)
ZIO.effect(…)
ZIO.effectTotal(…)
effectBlocking(…)
ZIO.effectAsync(…)
ZIO.effectAsyncMaybe(…)
ZIO.effectAsyncInte…(…)
ZIO.fromFuture(…)
ZIO.fail(…)
ZIO.fromOption(…)
ZIO.fromEither(…)
ZIO.fromTry(…)
ZIO.bracket(…)
ZIO.reserve(…)
ZIO.ensuring(…)
fromAutoCloseable(…)
15
ERROR
MANAGEMENT
FUTURE
Fail Domain errors, business errors,
transient errors, expected errors...
Expected Errors
Not Reflected in Types
DieSystem errors, fatal errors,
unanticipated errors, defects...
Unexpected Errors
Reflected in Types
16
ERROR DUALITY
17
ERROR DUALITY
Not Reflected in TypesNot Reflected in Types
val failed: Future[Nothing] =
Future.failed(new Exception)
val died: Future[Nothing] =
Future(throw new Error)
FUTURE
18
ERROR DUALITY
Not Reflected in TypesReflected in Types
val failed: IO[String, Nothing] =
ZIO.fail(“Uh oh!”)
val died: IO[Nothing, Nothing] =
ZIO.dieMessage(“Uh oh!”)
19
ERROR COMPOSITION
e1
e2t
First Error
20
ERROR COMPOSITION
e1
e2t
Second Error
21
ERROR COMPOSITION
e1
e2tFinalizer Error
22
ERROR COMPOSITION
e1
e2t
?
23
ERROR COMPOSITION
e1
e2t
FUTURE
Thrown away!!!Reported on side channel!!!
24
ERROR COMPOSITION
e1
e2t
Cause[E]
25
ERROR COMPOSITION
e1
e2t
Cause[E]
Cause.Fail(e1)
26
ERROR COMPOSITION
e1
e2t
Cause[E]
Cause.Both(
Cause.Fail(e1),
Cause.Fail(e2))
27
ERROR COMPOSITION
e1
e2t
Cause[E]
Cause.Both(
Cause.Fail(e1),
Cause.Then(
Cause.Fail(e2),
Cause.Die(t))
zio.FiberFailure: Fiber failed.
╥
╠══╗
║ ║
║ ║
║ ║
║ ╠─ A checked error was not handled:
║ ║
Failed(DatabaseUnreachableError)
║ ║
║ ╠─ A finalizer threw an error:
║ ▼
Die(IndexOutOfBoundsException())
║
╠─ A checked error was not handled:
▼ Failed(UserIdNotFoundError) 28
ERROR COMPOSITION
e2
t
e1
Asynchronous
29
ERROR PROPAGATION
Synchronous
ConcurrentParallel
Resource
Asynchronous
30
ERROR PROPAGATION
Synchronous
ConcurrentParallel
Resource
FUTURE
Asynchronous
31
ERROR PROPAGATION
Synchronous
ConcurrentParallel
Resource
32
ERROR RECOVERY
Fallback Catching Folding Value
ZIO#orElse(…)
ZIO#orElseEither(…)
ZIO#catchAll(…)
ZIO#catchSome(…)
ZIO#fold(…, …)
ZIO#foldM(…, …)
effect1.orElse(effect2) future1.fallback(future2)
ZIO#either
ZIO#run
FUTURE
33
ERROR RECOVERY
Fallback Catching Folding Value
ZIO#orElse(…)
ZIO#orElseEither(…)
ZIO#catchAll(…)
ZIO#catchSome(…)
ZIO#fold(…, …)
ZIO#foldM(…, …)
effect.catchAll(f) future.recoverWith(pf)
ZIO#either
ZIO#run
FUTURE
34
ERROR RECOVERY
Fallback Catching Folding Value
ZIO#orElse(…)
ZIO#orElseEither(…)
ZIO#catchAll(…)
ZIO#catchSome(…)
ZIO#fold(…, …)
ZIO#foldM(…, …)
effect.catchSome(pf) future.recoverWith(pf)
ZIO#either
ZIO#run
FUTURE
35
ERROR RECOVERY
Fallback Catching Folding Value
ZIO#orElse(…)
ZIO#orElseEither(…)
ZIO#catchAll(…)
ZIO#catchSome(…)
ZIO#fold(…, …)
ZIO#foldM(…, …)
effect.fold(err, succ)
ZIO#either
ZIO#run
FUTURE
36
ERROR RECOVERY
Fallback Catching Folding Value
ZIO#orElse(…)
ZIO#orElseEither(…)
ZIO#catchAll(…)
ZIO#catchSome(…)
ZIO#fold(…, …)
ZIO#foldM(…, …)
effect.foldM(err, succ)
ZIO#either
ZIO#run
FUTURE
37
ERROR RECOVERY
Fallback Catching Folding Value
ZIO#orElse(…)
ZIO#orElseEither(…)
ZIO#catchAll(…)
ZIO#catchSome(…)
ZIO#fold(…, …)
ZIO#foldM(…, …)
effect.either
ZIO#either
ZIO#run
FUTURE
38
ERROR RECOVERY
Fallback Catching Folding Value
ZIO#orElse(…)
ZIO#orElseEither(…)
ZIO#catchAll(…)
ZIO#catchSome(…)
ZIO#fold(…, …)
ZIO#foldM(…, …)
ZIO#either
ZIO#run
effect.run
FUTURE
39
BEST
PRACTICES
40
1. DON’T TYPE UNEXPECTED ERRORS
ZIO.effect(httpClient.get(url)).refineOrDie {
case e : TemporarilyUnavailable => e
}.retry(RetryPolicy).orDie
: IO[TemporarilyUnavailable, Response]
41
2. DO EXTEND EXCEPTION WITH SEALED TRAITS
sealed trait UserServiceError
extends Exception
case class InvalidUserId(id: ID)
extends UserServiceError
case class ExpiredAuth(id: ID)
extends UserServiceError
UserServiceError
InvalidUserId ExpiredAuth
42
2. DO EXTEND EXCEPTION WITH SEALED TRAITS
userServiceError match {
case InvalidUserId(id) => ...
case ExpiredAuth(id) => ...
}
43
2. DO EXTEND EXCEPTION WITH SEALED TRAITS
for {
service <- userAuth(token)
_ <- service.userProfile(userId)
body <- generateEmail(orderDetails)
receipt <- sendEmail(“Your Order Details”,
body, profile.email)
} yield receipt
ExpiredAuth
44
2. DO EXTEND EXCEPTION WITH SEALED TRAITS
for {
service <- userAuth(token)
_ <- service.userProfile(userId)
body <- generateEmail(orderDetails)
receipt <- sendEmail(“Your Order Details”,
body, profile.email)
} yield receipt
InvalidUserId
45
2. DO EXTEND EXCEPTION WITH SEALED TRAITS
for {
service <- userAuth(token)
_ <- service.userProfile(userId)
body <- generateEmail(orderDetails)
receipt <- sendEmail(“Your Order Details”,
body, profile.email)
} yield receipt
Nothing
46
2. DO EXTEND EXCEPTION WITH SEALED TRAITS
for {
service <- userAuth(token)
_ <- service.userProfile(userId)
body <- generateEmail(orderDetails)
receipt <- sendEmail(“Your Order Details”,
body, profile.email)
} yield receipt
EmailDeliveryError
47
2. DO EXTEND EXCEPTION WITH SEALED TRAITS
for {
service <- userAuth(token)
_ <- service.userProfile(userId)
body <- generateEmail(orderDetails)
receipt <- sendEmail(“Your Order Details”,
body, profile.email)
} yield receipt
IO[Exception, Receipt]
48
3. DON’T REFLEXIVELY LOG ERRORS
uploadFile(“contacts.csv”).catchAll { error =>
// Log error and re-fail:
for {
_ <- logger.error(error)
_ <- ZIO.fail(error)
} yield ()
}
49
4. DO GET TO KNOW UIO
type UIO[+A] = ZIO[Any, Nothing, A]
Cannot fail!
lazy val processed: UIO[Unit] =
processUpload(upload).either.flatMap {
case Left (_) => processed.delay(1.minute)
case Right(_) => ZIO.succeed(())
}
50
4. DO GET TO KNOW UIO
Fails with UploadError
lazy val processed: UIO[Unit] =
processUpload(upload).either.flatMap {
case Left (_) => processed.delay(1.minute)
case Right(_) => ZIO.succeed(())
}
51
4. DO GET TO KNOW UIO
Fails with Nothing
52
4. DO GET TO KNOW UIO
lazy val processed: UIO[Unit] =
processUpload(upload).either.flatMap {
case Left (_) => processed.delay(1.minute)
case Right(_) => ZIO.succeed(())
}
Fails with Nothing
NEXT-GENERATION
DEBUGGING
54
def asyncDbCall(sql: SQL): Future[Result]
def selectHumans(): Future[Result] = ...asyncDbCall(...)...
def selectPets(): Future[Result] = ...asyncDbCall(...)...
FUTURE
55
def asyncDbCall(sql: SQL): Future[Result]
def selectHumans(): Future[Result] = ...asyncDbCall(...)...
def selectPets(): Future[Result] = ...asyncDbCall(...)...
FUTURE
Which function failed, selectHumans or selectPets?
PostgresException: Syntax error at or near 42
at example$.getConnection(example.scala:43)
at example$.$anonfun$asyncDbCall$1(example.scala:23)
at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:658)
at scala.util.Success.$anonfun$map$1(Try.scala:255)
at scala.util.Success.map(Try.scala:213)
at scala.concurrent.Future.$anonfun$map$1(Future.scala:292)
at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:33)
at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:33)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64)
at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
56
Which function failed, selectHumans or selectPets?
FUTURE
def asyncDbCall(sql: SQL): Future[Result]
def selectHumans(): Future[Result] = ...asyncDbCall(...)...
def selectPets(): Future[Result] = ...asyncDbCall(...)...
Only the last operation is mentioned
PostgresException: Syntax error at or near 42
at example$.getConnection(example.scala:43)
at example$.$anonfun$asyncDbCall$1(example.scala:23)
at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:658)
at scala.util.Success.$anonfun$map$1(Try.scala:255)
at scala.util.Success.map(Try.scala:213)
at scala.concurrent.Future.$anonfun$map$1(Future.scala:292)
at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:33)
at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:33)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64)
at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
57
Which function failed, selectHumans or selectPets?
FUTURE
Only the last operation is mentioned
There is NO way to know!!!
def asyncDbCall(sql: SQL): Future[Result]
def selectHumans(): Future[Result] = ...asyncDbCall(...)...
def selectPets(): Future[Result] = ...asyncDbCall(...)...
PostgresException: Syntax error at or near 42
at example$.getConnection(example.scala:43)
at example$.$anonfun$asyncDbCall$1(example.scala:23)
at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:658)
at scala.util.Success.$anonfun$map$1(Try.scala:255)
at scala.util.Success.map(Try.scala:213)
at scala.concurrent.Future.$anonfun$map$1(Future.scala:292)
at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:33)
at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:33)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64)
at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
58
Asynchronous
def myQuery =
for {
_ <- UIO(println(“Querying!”))
res <- queryDatabase
} yield res
59
Failure!
def myQuery =
for {
_ <- UIO(println(“Querying!”))
res <- queryDatabase
} yield res
60
def myQuery =
for {
_ <- UIO(println(“Querying!”))
res <- queryDatabase
} yield res
Fiber:0 ZIO Execution trace:
at myQuery(example.scala:4)
at myQuery(example.scala:3)
Fiber:0 was supposed to continue to:
a future continuation at myQuery(example.scala:5)
Failure!
61
def myQuery =
UIO(println(“Querying!”))
.flatMap(_ =>
queryDatabase
.map(res => res))
Fiber:0 ZIO Execution trace:
at myQuery(example.scala:4)
at myQuery(example.scala:3)
Fiber:0 was supposed to continue to:
a future continuation at myQuery(example.scala:5)
62
def myQuery =
UIO(println(“Querying!”))
.flatMap(_ =>
queryDatabase
.map(res => res))
Fiber:0 ZIO Execution trace:
at myQuery(example.scala:4)
at myQuery(example.scala:3)
Fiber:0 was supposed to continue to:
a future continuation at myQuery(example.scala:5)
The Past
63
def myQuery =
UIO(println(“Querying!”))
.flatMap(_ =>
queryDatabase
.map(res => res))
Fiber:0 ZIO Execution trace:
at myQuery(example.scala:4)
at myQuery(example.scala:3)
Fiber:0 was supposed to continue to:
a future continuation at myQuery(example.scala:5)
The Past
The Future
64
def asyncDbCall(sql: SQL): Task[Result]
val selectHumans: Task[Result] = ...asyncDbCall(...)...
val selectPets: Task[Result] = ...asyncDbCall(...)...
65
def asyncDbCall(sql: SQL): Task[Result]
val selectHumans: Task[Result] = ...asyncDbCall(...)...
val selectPets: Task[Result] = ...asyncDbCall(...)...
Fiber:0 ZIO Execution trace:
at asyncDbCall(example.scala:22)
at selectHumans(example.scala:26)
Fiber:0 was supposed to continue to:
a future continuation at selectHumans(example.scala:27)
66
def asyncDbCall(sql: SQL): Task[Result]
val selectHumans: Task[Result] = ...asyncDbCall(...)...
val selectPets: Task[Result] = ...asyncDbCall(...)...
Fiber:0 ZIO Execution trace:
at asyncDbCall(example.scala:22)
at selectHumans(example.scala:26)
Fiber:0 was supposed to continue to:
a future continuation at selectHumans(example.scala:27)
Gotcha!
67
EXECUTION TRACES
def doWork(condition: Boolean) = {
if (condition) {
doSideWork()
}
doMainWork()
}
java.lang.Exception: Worker failed!
at example$.doMainWork(example.scala:54)
at example$.doWork(example.scala:50)
at example$$anon$1.run(example.scala:60)
No mention of doSideWork()
PROCEDURAL
68
EXECUTION TRACES
def doWork(condition: Boolean) =
for {
_ <- IO.when(condition)(doSideWork)
_ <- doMainWork
} yield ()
Fiber:0 ZIO Execution trace:
at example$.doMainWork(example.scala:27)
at example$.doWork(example.scala:23)
at example$.doSideWork(example.scala:26)
The conditional was true!
69
CONCURRENT TRACES
uploadUsers uploadPets
uploadTo(target)
error!
def uploadUsers(users: List[User]): Task[Unit] =
IO.foreachPar_(users.map(toJSON))(uploadTo(dest1))
def uploadPets(pets: List[Pet]): Task[Unit] =
IO.foreachPar_(pets.map(toJSON))(uploadTo(dest2))
def uploadTo(dest: URL)(json: JSON): Task[Unit] = ...
70
CONCURRENT TRACES
uploadUsers uploadPets
uploadTo(target)
error!
java.lang.Exception: Expired credentials
at example$.$anonfun$uploadTo$1(example.scala:28)
Fiber:1 ZIO Execution trace:
at example$.uploadTo(example.scala:28)
Fiber:1 was supposed to continue to: <empty trace>
Fiber:1 was spawned by:
╥
╠─ Fiber:0 ZIO Execution trace: <empty trace>
║
║ Fiber:0 was supposed to continue to:
║ example$.uploadUsers(example.scala:21)
71
TAGLESS FINAL TRACES
Gain insights into FP libraries
72
● Tracing is fast , impact is negligible for real apps
● > 50x faster than Future
● ...even on synthetic benchmarks!
● Impact can be limited by config
● Much lower than monad transformers [10x]
● Enabled by default, no Java agents, no ceremony
Disable if tracing is a hot spot
effect.untraced
MADE FOR
PRODUCTION
I
73
CONCLUSION
https://github.com/zio
https://gitter.im/zio/core
https://zio.dev
@jdegoes
@kaidaxofficial
@shirshovp

Error Management: Future vs ZIO

  • 1.
    1 Error Management ZIO vsFuture Dublin Scala Meetup, May 11th John A. De Goes   @jdegoes Kai @kaidaxofficial ...with help of Pavel Shirshov @shirshovp
  • 2.
    Agenda A Tale ofTwo Effects 2 Next-Gen Debugging Conclusion Managing Errors
  • 3.
    3 A TALE OF TWOEFFECTS FUTURE
  • 4.
    Parallel Future enables parallel computationsand non-blocking gathering. Error Future has a built-in error channel for Throwable-based errors. Eager Future is not referentially transparent; refactoring may change behavior. 4 FUTURE Async Future enables non-blocking code that efficiently uses threads.
  • 5.
  • 6.
    Procedural Functional def program:Unit = { println("What’s your name?") val name = readLine() println(s"Howdy. $name!") return () } val program = for { _ <- putStrLn("What’s your name?") name <- getStrLn _ <- putStrLn(s"Howdy, $name") } yield ()
  • 7.
    7 Future ZIO Performance >100x slower > 100x faster Cancellation & Timeouts 𐄂 ✔ Effect Combinators 𐄂 ✔ Resource Safety 𐄂 ✔ Fiber Concurrency 𐄂 ✔ Equational & Type Reasoning 𐄂 ✔ Testability 𐄂 ✔ Error Management & Debugging ? ?
  • 8.
    8 ZIO[R, E, A] EnvironmentType Failure Type Success Type
  • 9.
    9 ZIO[R, E, A] ~ R=> Either[E, A]
  • 10.
    10 type Task [+A] = ZIO[Any, Throwable, A] type UIO [ +A] = ZIO[Any, Nothing, A] type TaskR[+R,+A] = ZIO[ R, Throwable, A] type IO [+E,+A] = ZIO[Any, E, A]
  • 11.
    11 type Task [+A] = ZIO[Any, Throwable, A] type UIO [ +A] = ZIO[Any, Nothing, A] type TaskR[+R,+A] = ZIO[ R, Throwable, A] type IO [+E,+A] = ZIO[Any, E, A]
  • 12.
    12 type Task [+A] = ZIO[Any, Throwable, A] type UIO [ +A] = ZIO[Any, Nothing, A] type TaskR[+R,+A] = ZIO[ R, Throwable, A] type IO [+E,+A] = ZIO[Any, E, A]
  • 13.
    13 type Task [+A] = ZIO[Any, Throwable, A] type UIO [ +A] = ZIO[Any, Nothing, A] type TaskR[+R,+A] = ZIO[ R, Throwable, A] type IO [+E,+A] = ZIO[Any, E, A]
  • 14.
    ZIO[R, E, A] SynchronousAsynchronous Errors Resource ZIO.succeed(…) ZIO.effect(…) ZIO.effectTotal(…) effectBlocking(…) ZIO.effectAsync(…) ZIO.effectAsyncMaybe(…) ZIO.effectAsyncInte…(…) ZIO.fromFuture(…) ZIO.fail(…) ZIO.fromOption(…) ZIO.fromEither(…) ZIO.fromTry(…) ZIO.bracket(…) ZIO.reserve(…) ZIO.ensuring(…) fromAutoCloseable(…)
  • 15.
  • 16.
    Fail Domain errors,business errors, transient errors, expected errors... Expected Errors Not Reflected in Types DieSystem errors, fatal errors, unanticipated errors, defects... Unexpected Errors Reflected in Types 16 ERROR DUALITY
  • 17.
    17 ERROR DUALITY Not Reflectedin TypesNot Reflected in Types val failed: Future[Nothing] = Future.failed(new Exception) val died: Future[Nothing] = Future(throw new Error) FUTURE
  • 18.
    18 ERROR DUALITY Not Reflectedin TypesReflected in Types val failed: IO[String, Nothing] = ZIO.fail(“Uh oh!”) val died: IO[Nothing, Nothing] = ZIO.dieMessage(“Uh oh!”)
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
    zio.FiberFailure: Fiber failed. ╥ ╠══╗ ║║ ║ ║ ║ ║ ║ ╠─ A checked error was not handled: ║ ║ Failed(DatabaseUnreachableError) ║ ║ ║ ╠─ A finalizer threw an error: ║ ▼ Die(IndexOutOfBoundsException()) ║ ╠─ A checked error was not handled: ▼ Failed(UserIdNotFoundError) 28 ERROR COMPOSITION e2 t e1
  • 29.
  • 30.
  • 31.
  • 32.
    32 ERROR RECOVERY Fallback CatchingFolding Value ZIO#orElse(…) ZIO#orElseEither(…) ZIO#catchAll(…) ZIO#catchSome(…) ZIO#fold(…, …) ZIO#foldM(…, …) effect1.orElse(effect2) future1.fallback(future2) ZIO#either ZIO#run FUTURE
  • 33.
    33 ERROR RECOVERY Fallback CatchingFolding Value ZIO#orElse(…) ZIO#orElseEither(…) ZIO#catchAll(…) ZIO#catchSome(…) ZIO#fold(…, …) ZIO#foldM(…, …) effect.catchAll(f) future.recoverWith(pf) ZIO#either ZIO#run FUTURE
  • 34.
    34 ERROR RECOVERY Fallback CatchingFolding Value ZIO#orElse(…) ZIO#orElseEither(…) ZIO#catchAll(…) ZIO#catchSome(…) ZIO#fold(…, …) ZIO#foldM(…, …) effect.catchSome(pf) future.recoverWith(pf) ZIO#either ZIO#run FUTURE
  • 35.
    35 ERROR RECOVERY Fallback CatchingFolding Value ZIO#orElse(…) ZIO#orElseEither(…) ZIO#catchAll(…) ZIO#catchSome(…) ZIO#fold(…, …) ZIO#foldM(…, …) effect.fold(err, succ) ZIO#either ZIO#run FUTURE
  • 36.
    36 ERROR RECOVERY Fallback CatchingFolding Value ZIO#orElse(…) ZIO#orElseEither(…) ZIO#catchAll(…) ZIO#catchSome(…) ZIO#fold(…, …) ZIO#foldM(…, …) effect.foldM(err, succ) ZIO#either ZIO#run FUTURE
  • 37.
    37 ERROR RECOVERY Fallback CatchingFolding Value ZIO#orElse(…) ZIO#orElseEither(…) ZIO#catchAll(…) ZIO#catchSome(…) ZIO#fold(…, …) ZIO#foldM(…, …) effect.either ZIO#either ZIO#run FUTURE
  • 38.
    38 ERROR RECOVERY Fallback CatchingFolding Value ZIO#orElse(…) ZIO#orElseEither(…) ZIO#catchAll(…) ZIO#catchSome(…) ZIO#fold(…, …) ZIO#foldM(…, …) ZIO#either ZIO#run effect.run FUTURE
  • 39.
  • 40.
    40 1. DON’T TYPEUNEXPECTED ERRORS ZIO.effect(httpClient.get(url)).refineOrDie { case e : TemporarilyUnavailable => e }.retry(RetryPolicy).orDie : IO[TemporarilyUnavailable, Response]
  • 41.
    41 2. DO EXTENDEXCEPTION WITH SEALED TRAITS sealed trait UserServiceError extends Exception case class InvalidUserId(id: ID) extends UserServiceError case class ExpiredAuth(id: ID) extends UserServiceError UserServiceError InvalidUserId ExpiredAuth
  • 42.
    42 2. DO EXTENDEXCEPTION WITH SEALED TRAITS userServiceError match { case InvalidUserId(id) => ... case ExpiredAuth(id) => ... }
  • 43.
    43 2. DO EXTENDEXCEPTION WITH SEALED TRAITS for { service <- userAuth(token) _ <- service.userProfile(userId) body <- generateEmail(orderDetails) receipt <- sendEmail(“Your Order Details”, body, profile.email) } yield receipt ExpiredAuth
  • 44.
    44 2. DO EXTENDEXCEPTION WITH SEALED TRAITS for { service <- userAuth(token) _ <- service.userProfile(userId) body <- generateEmail(orderDetails) receipt <- sendEmail(“Your Order Details”, body, profile.email) } yield receipt InvalidUserId
  • 45.
    45 2. DO EXTENDEXCEPTION WITH SEALED TRAITS for { service <- userAuth(token) _ <- service.userProfile(userId) body <- generateEmail(orderDetails) receipt <- sendEmail(“Your Order Details”, body, profile.email) } yield receipt Nothing
  • 46.
    46 2. DO EXTENDEXCEPTION WITH SEALED TRAITS for { service <- userAuth(token) _ <- service.userProfile(userId) body <- generateEmail(orderDetails) receipt <- sendEmail(“Your Order Details”, body, profile.email) } yield receipt EmailDeliveryError
  • 47.
    47 2. DO EXTENDEXCEPTION WITH SEALED TRAITS for { service <- userAuth(token) _ <- service.userProfile(userId) body <- generateEmail(orderDetails) receipt <- sendEmail(“Your Order Details”, body, profile.email) } yield receipt IO[Exception, Receipt]
  • 48.
    48 3. DON’T REFLEXIVELYLOG ERRORS uploadFile(“contacts.csv”).catchAll { error => // Log error and re-fail: for { _ <- logger.error(error) _ <- ZIO.fail(error) } yield () }
  • 49.
    49 4. DO GETTO KNOW UIO type UIO[+A] = ZIO[Any, Nothing, A] Cannot fail!
  • 50.
    lazy val processed:UIO[Unit] = processUpload(upload).either.flatMap { case Left (_) => processed.delay(1.minute) case Right(_) => ZIO.succeed(()) } 50 4. DO GET TO KNOW UIO Fails with UploadError
  • 51.
    lazy val processed:UIO[Unit] = processUpload(upload).either.flatMap { case Left (_) => processed.delay(1.minute) case Right(_) => ZIO.succeed(()) } 51 4. DO GET TO KNOW UIO Fails with Nothing
  • 52.
    52 4. DO GETTO KNOW UIO lazy val processed: UIO[Unit] = processUpload(upload).either.flatMap { case Left (_) => processed.delay(1.minute) case Right(_) => ZIO.succeed(()) } Fails with Nothing
  • 53.
  • 54.
    54 def asyncDbCall(sql: SQL):Future[Result] def selectHumans(): Future[Result] = ...asyncDbCall(...)... def selectPets(): Future[Result] = ...asyncDbCall(...)... FUTURE
  • 55.
    55 def asyncDbCall(sql: SQL):Future[Result] def selectHumans(): Future[Result] = ...asyncDbCall(...)... def selectPets(): Future[Result] = ...asyncDbCall(...)... FUTURE Which function failed, selectHumans or selectPets? PostgresException: Syntax error at or near 42 at example$.getConnection(example.scala:43) at example$.$anonfun$asyncDbCall$1(example.scala:23) at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:658) at scala.util.Success.$anonfun$map$1(Try.scala:255) at scala.util.Success.map(Try.scala:213) at scala.concurrent.Future.$anonfun$map$1(Future.scala:292) at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:33) at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:33) at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64) at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402) at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
  • 56.
    56 Which function failed,selectHumans or selectPets? FUTURE def asyncDbCall(sql: SQL): Future[Result] def selectHumans(): Future[Result] = ...asyncDbCall(...)... def selectPets(): Future[Result] = ...asyncDbCall(...)... Only the last operation is mentioned PostgresException: Syntax error at or near 42 at example$.getConnection(example.scala:43) at example$.$anonfun$asyncDbCall$1(example.scala:23) at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:658) at scala.util.Success.$anonfun$map$1(Try.scala:255) at scala.util.Success.map(Try.scala:213) at scala.concurrent.Future.$anonfun$map$1(Future.scala:292) at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:33) at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:33) at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64) at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402) at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
  • 57.
    57 Which function failed,selectHumans or selectPets? FUTURE Only the last operation is mentioned There is NO way to know!!! def asyncDbCall(sql: SQL): Future[Result] def selectHumans(): Future[Result] = ...asyncDbCall(...)... def selectPets(): Future[Result] = ...asyncDbCall(...)... PostgresException: Syntax error at or near 42 at example$.getConnection(example.scala:43) at example$.$anonfun$asyncDbCall$1(example.scala:23) at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:658) at scala.util.Success.$anonfun$map$1(Try.scala:255) at scala.util.Success.map(Try.scala:213) at scala.concurrent.Future.$anonfun$map$1(Future.scala:292) at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:33) at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:33) at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64) at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402) at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
  • 58.
    58 Asynchronous def myQuery = for{ _ <- UIO(println(“Querying!”)) res <- queryDatabase } yield res
  • 59.
    59 Failure! def myQuery = for{ _ <- UIO(println(“Querying!”)) res <- queryDatabase } yield res
  • 60.
    60 def myQuery = for{ _ <- UIO(println(“Querying!”)) res <- queryDatabase } yield res Fiber:0 ZIO Execution trace: at myQuery(example.scala:4) at myQuery(example.scala:3) Fiber:0 was supposed to continue to: a future continuation at myQuery(example.scala:5) Failure!
  • 61.
    61 def myQuery = UIO(println(“Querying!”)) .flatMap(_=> queryDatabase .map(res => res)) Fiber:0 ZIO Execution trace: at myQuery(example.scala:4) at myQuery(example.scala:3) Fiber:0 was supposed to continue to: a future continuation at myQuery(example.scala:5)
  • 62.
    62 def myQuery = UIO(println(“Querying!”)) .flatMap(_=> queryDatabase .map(res => res)) Fiber:0 ZIO Execution trace: at myQuery(example.scala:4) at myQuery(example.scala:3) Fiber:0 was supposed to continue to: a future continuation at myQuery(example.scala:5) The Past
  • 63.
    63 def myQuery = UIO(println(“Querying!”)) .flatMap(_=> queryDatabase .map(res => res)) Fiber:0 ZIO Execution trace: at myQuery(example.scala:4) at myQuery(example.scala:3) Fiber:0 was supposed to continue to: a future continuation at myQuery(example.scala:5) The Past The Future
  • 64.
    64 def asyncDbCall(sql: SQL):Task[Result] val selectHumans: Task[Result] = ...asyncDbCall(...)... val selectPets: Task[Result] = ...asyncDbCall(...)...
  • 65.
    65 def asyncDbCall(sql: SQL):Task[Result] val selectHumans: Task[Result] = ...asyncDbCall(...)... val selectPets: Task[Result] = ...asyncDbCall(...)... Fiber:0 ZIO Execution trace: at asyncDbCall(example.scala:22) at selectHumans(example.scala:26) Fiber:0 was supposed to continue to: a future continuation at selectHumans(example.scala:27)
  • 66.
    66 def asyncDbCall(sql: SQL):Task[Result] val selectHumans: Task[Result] = ...asyncDbCall(...)... val selectPets: Task[Result] = ...asyncDbCall(...)... Fiber:0 ZIO Execution trace: at asyncDbCall(example.scala:22) at selectHumans(example.scala:26) Fiber:0 was supposed to continue to: a future continuation at selectHumans(example.scala:27) Gotcha!
  • 67.
    67 EXECUTION TRACES def doWork(condition:Boolean) = { if (condition) { doSideWork() } doMainWork() } java.lang.Exception: Worker failed! at example$.doMainWork(example.scala:54) at example$.doWork(example.scala:50) at example$$anon$1.run(example.scala:60) No mention of doSideWork() PROCEDURAL
  • 68.
    68 EXECUTION TRACES def doWork(condition:Boolean) = for { _ <- IO.when(condition)(doSideWork) _ <- doMainWork } yield () Fiber:0 ZIO Execution trace: at example$.doMainWork(example.scala:27) at example$.doWork(example.scala:23) at example$.doSideWork(example.scala:26) The conditional was true!
  • 69.
    69 CONCURRENT TRACES uploadUsers uploadPets uploadTo(target) error! defuploadUsers(users: List[User]): Task[Unit] = IO.foreachPar_(users.map(toJSON))(uploadTo(dest1)) def uploadPets(pets: List[Pet]): Task[Unit] = IO.foreachPar_(pets.map(toJSON))(uploadTo(dest2)) def uploadTo(dest: URL)(json: JSON): Task[Unit] = ...
  • 70.
    70 CONCURRENT TRACES uploadUsers uploadPets uploadTo(target) error! java.lang.Exception:Expired credentials at example$.$anonfun$uploadTo$1(example.scala:28) Fiber:1 ZIO Execution trace: at example$.uploadTo(example.scala:28) Fiber:1 was supposed to continue to: <empty trace> Fiber:1 was spawned by: ╥ ╠─ Fiber:0 ZIO Execution trace: <empty trace> ║ ║ Fiber:0 was supposed to continue to: ║ example$.uploadUsers(example.scala:21)
  • 71.
    71 TAGLESS FINAL TRACES Gaininsights into FP libraries
  • 72.
    72 ● Tracing isfast , impact is negligible for real apps ● > 50x faster than Future ● ...even on synthetic benchmarks! ● Impact can be limited by config ● Much lower than monad transformers [10x] ● Enabled by default, no Java agents, no ceremony Disable if tracing is a hot spot effect.untraced MADE FOR PRODUCTION
  • 73.