Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Error Management: Future vs ZIO

5,129 views

Published on

Modern applications are concurrent, parallel, asynchronous, and synchronous; they utilize many different subsystems, including network systems, actor systems, distributed systems, and more. Across all these modes of computation and different subsystems, the one constant is failure. Errors happen everywhere, and taming their monstrous complexity in a way that helps developers write correct code and troubleshoot failures is one of the hardest challenges of modern application development.

In this presentation, created just for the Dublin Scala Meetup, John A. De Goes and Kai ⁣from 7mind.io will take attendees on a tour of error management in Scala, comparing and contrasting Scala's own Future type, and the ZIO effect type. You'll see how functional effects provide features that go way beyond Future: including unified errors across all modes of computation, powerful error operators, lossless error propagation, compiler-assisted error handling, and a stunning new feature for debugging, sponsored by Irish consultancy 7mind.io, will be unveiled exclusively at this presentation.

Come learn about how modern functional effect systems like ZIO provide compelling new solutions to the problems of everyday error management.

Published in: Technology

Error Management: Future vs ZIO

  1. 1. 1 Error Management ZIO vs Future Dublin Scala Meetup, May 11th John A. De Goes   @jdegoes Kai @kaidaxofficial ...with help of Pavel Shirshov @shirshovp
  2. 2. Agenda A Tale of Two Effects 2 Next-Gen Debugging Conclusion Managing Errors
  3. 3. 3 A TALE OF TWO EFFECTS FUTURE
  4. 4. 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. 5. 5 Future ZIO Effect go running
  6. 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. 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. 8 ZIO[R, E, A] Environment Type Failure Type Success Type
  9. 9. 9 ZIO[R, E, A] ~ R => Either[E, A]
  10. 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. 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. 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. 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. 14. 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. 15. 15 ERROR MANAGEMENT FUTURE
  16. 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. 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. 18. 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. 19. 19 ERROR COMPOSITION e1 e2t First Error
  20. 20. 20 ERROR COMPOSITION e1 e2t Second Error
  21. 21. 21 ERROR COMPOSITION e1 e2tFinalizer Error
  22. 22. 22 ERROR COMPOSITION e1 e2t ?
  23. 23. 23 ERROR COMPOSITION e1 e2t FUTURE Thrown away!!!Reported on side channel!!!
  24. 24. 24 ERROR COMPOSITION e1 e2t Cause[E]
  25. 25. 25 ERROR COMPOSITION e1 e2t Cause[E] Cause.Fail(e1)
  26. 26. 26 ERROR COMPOSITION e1 e2t Cause[E] Cause.Both( Cause.Fail(e1), Cause.Fail(e2))
  27. 27. 27 ERROR COMPOSITION e1 e2t Cause[E] Cause.Both( Cause.Fail(e1), Cause.Then( Cause.Fail(e2), Cause.Die(t))
  28. 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. 29. Asynchronous 29 ERROR PROPAGATION Synchronous ConcurrentParallel Resource
  30. 30. Asynchronous 30 ERROR PROPAGATION Synchronous ConcurrentParallel Resource FUTURE
  31. 31. Asynchronous 31 ERROR PROPAGATION Synchronous ConcurrentParallel Resource
  32. 32. 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. 33. 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. 34. 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. 35. 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. 36. 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. 37. 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. 38. 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. 39. 39 BEST PRACTICES
  40. 40. 40 1. DON’T TYPE UNEXPECTED ERRORS ZIO.effect(httpClient.get(url)).refineOrDie { case e : TemporarilyUnavailable => e }.retry(RetryPolicy).orDie : IO[TemporarilyUnavailable, Response]
  41. 41. 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. 42. 42 2. DO EXTEND EXCEPTION WITH SEALED TRAITS userServiceError match { case InvalidUserId(id) => ... case ExpiredAuth(id) => ... }
  43. 43. 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. 44. 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. 45. 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. 46. 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. 47. 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. 48. 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. 49. 49 4. DO GET TO KNOW UIO type UIO[+A] = ZIO[Any, Nothing, A] Cannot fail!
  50. 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. 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. 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
  53. 53. NEXT-GENERATION DEBUGGING
  54. 54. 54 def asyncDbCall(sql: SQL): Future[Result] def selectHumans(): Future[Result] = ...asyncDbCall(...)... def selectPets(): Future[Result] = ...asyncDbCall(...)... FUTURE
  55. 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. 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. 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. 58 Asynchronous def myQuery = for { _ <- UIO(println(“Querying!”)) res <- queryDatabase } yield res
  59. 59. 59 Failure! def myQuery = for { _ <- UIO(println(“Querying!”)) res <- queryDatabase } yield res
  60. 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. 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. 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. 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. 64 def asyncDbCall(sql: SQL): Task[Result] val selectHumans: Task[Result] = ...asyncDbCall(...)... val selectPets: Task[Result] = ...asyncDbCall(...)...
  65. 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. 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. 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. 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. 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. 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. 71 TAGLESS FINAL TRACES Gain insights into FP libraries
  72. 72. 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
  73. 73. I 73 CONCLUSION https://github.com/zio https://gitter.im/zio/core https://zio.dev @jdegoes @kaidaxofficial @shirshovp

×