This is the presentation from my talk at the Vienna Scala Usergroup on June 8th, 2017. It is about various approaches for error and failure handling in Scala.
7. THE PROBLEM DOMAIN
sealed trait TimeEntryStatus
case object Open extends TimeEntryStatus
case object Accepted extends TimeEntryStatus
case object Declined extends TimeEntryStatus
case class TimeEntry(
id: UUID,
begin: LocalDateTime,
end: LocalDateTime,
status: TimeEntryStatus
)
8. THE PROBLEM DOMAIN
val timeEntry = TimeEntry(
id = UUID.randomUUID,
begin = LocalDateTime.now.minusHours(1),
end = LocalDateTime.now,
status = Open
)
11. A JAVAISH
SOLUTION
Many of us are used to
develop like this
Failures handling with
exceptions
Better solutions
already exist
12. A JAVAISH SOLUTION
abstract class TimeEntryException(message: String) extends Exception
case class TimeEntryAlreadyExistsException(id: UUID)
extends TimeEntryException(s"Time Entry with id $id already exists.")
case class TimeEntryDoesNotExistException(id: UUID)
extends TimeEntryException(s"Time Entry with id $id does not exist.")
13. A JAVAISH SOLUTION
class TimeEntryService {
def create(t: TimeEntry): Unit = {
if (timeEntries.contains(t.id)) {
throw TimeEntryAlreadyExistsException(t.id)
} else {
timeEntries = timeEntries + (t.id -> t)
}
}
}
19. MY PRECIOUS
sealed trait TimeEntryFailure
case class ValidationFailed(failures: List[String])
extends TimeEntryFailure
case class TimeEntryDoesNotExist(id: UUID)
extends TimeEntryFailure
case class TimeEntryAlreadyExists(id: UUID)
extends TimeEntryFailure
21. ... AND MATCH IT
//type TimeEntryResult[A] = Either[TimeEntryFailure,A]
val result: TimeEntryResult[Unit] = for {
_ <- service.create(entry)
_ <- service.create(entry)
_ <- service.accept(entry.id)
} yield ()
result.fold(
{
case TimeEntryDoesNotExist(id) => println("Sorry, the time entry does not exis
case TimeEntryAlreadyExists(id) => println("Sorry, the time entry already exis
}, { _ =>
println("Yeaaah, success!")
}
)
23. LET'S MAKE THIS A HARD CONSTRAINT
scalacOptions += "-Xfatal-warnings",
[error] /Users/daniel/presentations/2017-06-08_error-handling-in-scala/
code/src/main/scala/com/github/dpfeiffer/errorhandling/either/
ExampleWithEither.scala:60: match may not be exhaustive.
[error] It would fail on the following input: ValidationFailed(_)
[error] {
[error] ^
[error] one error found
26. ... THAT LOOKS LIKE THE TRY
val result = for {
_ <- service.create(timeEntry)
_ <- service.accept(timeEntry.id)
_ <- service.create(timeEntry)
} yield ()
result.onComplete {
case Success(_) => println("Succeeded.")
case Failure(ex) => println(s"Exception occurred. ${ex.getMessage}")
}
33. WRAPPING EVERYTHING IN A NEW
MONAD
type FutureEither = EitherT[Future, String, String]
val a: FutureEither = EitherT(Future(Right("right")))
val b: FutureEither = EitherT(Future(Left("left"))
val c: FutureEither = EitherT.right(Future("right"))
val d: FutureEither = EitherT.left(Future("left"))
val e: FutureEither = EitherT.fromEither(Right("right"))
34. THE USAGE LOOKS GOOD IF...
we only use TimeEntryResult[_]
//type TimeEntryResult[A] = EitherT[Future,TimeEntryFailure,A]
val result: TimeEntryResult[Unit] = for {
_ <- service.create(entry)
_ <- service.create(entry)
_ <- service.accept(entry.id)
} yield ()
result
.fold(handleFailure, _ => println("Success!!!"))
.onComplete {
case Failure(t) => println("Oh noooo!")
case Success(_) =>
}