SlideShare a Scribd company logo
Unsucking Errors with
Futures
Gary Coady <gcoady@gilt.com>
Option[A]
Error Handling: Possible Absence of a Value
def	
  userForEmail(email:	
  String):	
  Option[User]	
  =	
  ???	
  
def	
  shoeSizeForUser(user:	
  User):	
  Option[Size]	
  =	
  ???	
  
def	
  recommendedShoeStyleForSize(size:	
  Size):	
  Option[ShoeStyle]	
  	
  =	
  ???	
  
def	
  recommendedShoe(email:	
  String):	
  Option[ShoeStyle]	
  =	
  for	
  {	
  
	
  	
  user	
  <-­‐	
  userForEmail(email)	
  
	
  	
  size	
  <-­‐	
  shoeSizeForUser(user)	
  
	
  	
  shoe	
  <-­‐	
  recommendedShoeStyleForSize(size)	
  
}	
  yield	
  shoe
recommendedShoe("a@example.com")	
  match	
  {	
  
	
  	
  case	
  None	
  =>	
  println("No	
  shoe	
  recommendation")	
  
	
  	
  case	
  Some(shoe)	
  =>	
  println(s"Shoe	
  recommendation:	
  $shoe")	
  
}
Future[A]
Asynchronous Computation
def	
  userForEmail(email:	
  String):	
  Future[User]	
  =	
  ???	
  
def	
  shoeSizeForUser(user:	
  User):	
  Future[Size]	
  =	
  ???	
  
def	
  recommendedShoeStyleForSize(size:	
  Size):	
  Future[ShoeStyle]	
  	
  =	
  ???	
  
def	
  recommendedShoe(email:	
  String):	
  Future[ShoeStyle]	
  =	
  for	
  {	
  
	
  	
  user	
  <-­‐	
  userForEmail(email)	
  
	
  	
  size	
  <-­‐	
  shoeSizeForUser(user)	
  
	
  	
  shoe	
  <-­‐	
  recommendedShoeStyleForSize(size)	
  
}	
  yield	
  shoe
recommendedShoe("a@example.com")	
  onComplete	
  {	
  
	
  	
  case	
  Failure(t)	
  =>	
  println("Shoe	
  recommendation	
  failed")	
  
	
  	
  case	
  Success(shoe)	
  =>	
  println(s"Shoe	
  recommendation:	
  $shoe")	
  
}
Future[Option[A]]
Errors (possible missing values)
+
asynchronous computation
def	
  userForEmail(email:	
  String):	
  Future[Option[User]]	
  =	
  ???	
  
def	
  shoeSizeForUser(userOpt:	
  Option[User]):	
  Future[Option[Size]]	
  =	
  {	
  
	
  	
  userOpt	
  match	
  {	
  
	
  	
  	
  	
  case	
  None	
  =>	
  Future.successful(None)	
  
	
  	
  	
  	
  case	
  Some(user)	
  =>	
  ???	
  
	
  	
  }	
  
}	
  
def	
  recommendedShoeStyleForSize(sizeOpt:	
  Option[Size]):	
  Future[Option[ShoeStyle]]	
  =	
  {	
  
	
  	
  sizeOpt	
  match	
  {	
  
	
  	
  	
  	
  case	
  None	
  =>	
  Future.successful(None)	
  
	
  	
  	
  	
  case	
  Some(size)	
  =>	
  ???	
  
	
  	
  }	
  
}	
  
def	
  recommendedShoe(email:	
  String):	
  Future[Option[ShoeStyle]]	
  =	
  for	
  {	
  
	
  	
  user	
  <-­‐	
  userForEmail(email)	
  
	
  	
  size	
  <-­‐	
  shoeSizeForUser(user)	
  
	
  	
  shoe	
  <-­‐	
  recommendedShoeStyleForSize(size)	
  
}	
  yield	
  shoe
recommendedShoe("a@example.com")	
  onComplete	
  {	
  
	
  	
  case	
  Failure(t)	
  =>	
  println("Shoe	
  recommendation	
  failed")	
  
	
  	
  case	
  Success(None)	
  =>	
  println("No	
  shoe	
  recommendation")	
  
	
  	
  case	
  Success(Some(shoe))	
  =>	
  println(s"Shoe	
  recommendation:	
  $shoe")	
  
}
def	
  userForEmail(email:	
  String):	
  Future[Option[User]]	
  =	
  ???	
  
def	
  shoeSizeForUser(userOpt:	
  Option[User]):	
  Future[Option[Size]]	
  =	
  {	
  
	
  	
  userOpt	
  match	
  {	
  
	
  	
  	
  	
  case	
  None	
  =>	
  Future.successful(None)	
  
	
  	
  	
  	
  case	
  Some(user)	
  =>	
  ???	
  
	
  	
  }	
  
}	
  
def	
  recommendedShoeStyleForSize(sizeOpt:	
  Option[Size]):	
  Future[Option[ShoeStyle]]	
  =	
  {	
  
	
  	
  sizeOpt	
  match	
  {	
  
	
  	
  	
  	
  case	
  None	
  =>	
  Future.successful(None)	
  
	
  	
  	
  	
  case	
  Some(size)	
  =>	
  ???	
  
	
  	
  }	
  
}	
  
def	
  recommendedShoe(email:	
  String):	
  Future[Option[ShoeStyle]]	
  =	
  for	
  {	
  
	
  	
  user	
  <-­‐	
  userForEmail(email)	
  
	
  	
  size	
  <-­‐	
  shoeSizeForUser(user)	
  
	
  	
  shoe	
  <-­‐	
  recommendedShoeStyleForSize(size)	
  
}	
  yield	
  shoe
recommendedShoe("a@example.com")	
  onComplete	
  {	
  
	
  	
  case	
  Failure(t)	
  =>	
  println("Shoe	
  recommendation	
  failed")	
  
	
  	
  case	
  Success(None)	
  =>	
  println("No	
  shoe	
  recommendation")	
  
	
  	
  case	
  Success(Some(shoe))	
  =>	
  println(s"Shoe	
  recommendation:	
  $shoe")	
  
}
–Dave Thomas
“Every piece of knowledge must have a single,
unambiguous, authoritative representation
within a system.”
def	
  flatMap[B](f:	
  A	
  =>	
  F[B]):	
  F[B]	
  Given	
  an	
  F[A]:
def	
  flatMap[B](f:	
  A	
  =>	
  F[B]):	
  F[B]	
  Given	
  an	
  F[A]:
case	
  class	
  FutureOption[+A](future:	
  Future[Option[A]])	
  {	
  
	
  	
  def	
  flatMap[B](f:	
  A	
  =>	
  FutureOption[B]):	
  FutureOption[B]	
  =	
  {	
  
	
  	
  	
  	
  val	
  result	
  =	
  future	
  flatMap	
  {	
  
	
  	
  	
  	
  	
  	
  case	
  None	
  =>	
  Future.successful(None)	
  
	
  	
  	
  	
  	
  	
  case	
  Some(opt)	
  =>	
  f(opt).future	
  
	
  	
  	
  	
  }	
  
	
  	
  	
  	
  FutureOption(result)	
  
	
  	
  }	
  
}
def	
  userForEmail(email:	
  String):	
  Future[Option[User]]	
  =	
  ???	
  
def	
  shoeSizeForUser(user:	
  User):	
  Future[Option[Size]]	
  =	
  ???	
  
def	
  recommendedShoeStyleForSize(size:	
  Size):	
  Future[Option[ShoeStyle]]	
  =	
  ???	
  
def	
  recommendedShoe(email:	
  String):	
  FutureOption[ShoeStyle]	
  =	
  for	
  {	
  
	
  	
  user	
  <-­‐	
  FutureOption(userForEmail(email))	
  
	
  	
  size	
  <-­‐	
  FutureOption(shoeSizeForUser(user))	
  
	
  	
  shoe	
  <-­‐	
  FutureOption(recommendedShoeStyleForSize(size))	
  
}	
  yield	
  shoe
recommendedShoe("a@example.com").future	
  onComplete	
  {	
  
	
  	
  case	
  Failure(t)	
  =>	
  println("Shoe	
  recommendation	
  failed")	
  
	
  	
  case	
  Success(None)	
  =>	
  println("No	
  shoe	
  recommendation")	
  
	
  	
  case	
  Success(Some(shoe))	
  =>	
  println(s"Shoe	
  recommendation:	
  $shoe")	
  
}
def	
  userForEmail(email:	
  String):	
  Future[Option[User]]	
  =	
  ???	
  
def	
  shoeSizeForUser(user:	
  User):	
  Future[Option[Size]]	
  =	
  ???	
  
def	
  recommendedShoeStyleForSize(size:	
  Size):	
  Future[Option[ShoeStyle]]	
  =	
  ???	
  
def	
  recommendedShoe(email:	
  String):	
  FutureOption[ShoeStyle]	
  =	
  for	
  {	
  
	
  	
  user	
  <-­‐	
  FutureOption(userForEmail(email))	
  
	
  	
  size	
  <-­‐	
  FutureOption(shoeSizeForUser(user))	
  
	
  	
  shoe	
  <-­‐	
  FutureOption(recommendedShoeStyleForSize(size))	
  
}	
  yield	
  shoe
recommendedShoe("a@example.com").future	
  onComplete	
  {	
  
	
  	
  case	
  Failure(t)	
  =>	
  println("Shoe	
  recommendation	
  failed")	
  
	
  	
  case	
  Success(None)	
  =>	
  println("No	
  shoe	
  recommendation")	
  
	
  	
  case	
  Success(Some(shoe))	
  =>	
  println(s"Shoe	
  recommendation:	
  $shoe")	
  
}
def	
  userForEmail(email:	
  String):	
  Future[Option[User]]	
  =	
  ???	
  
def	
  shoeSizeForUser(user:	
  User):	
  Future[Option[Size]]	
  =	
  ???	
  
def	
  recommendedShoeStyleForSize(size:	
  Size):	
  Future[Option[ShoeStyle]]	
  =	
  ???	
  
def	
  recommendedShoe(email:	
  String):	
  FutureOption[ShoeStyle]	
  =	
  for	
  {	
  
	
  	
  user	
  <-­‐	
  FutureOption(userForEmail(email))	
  
	
  	
  size	
  <-­‐	
  FutureOption(shoeSizeForUser(user))	
  
	
  	
  shoe	
  <-­‐	
  FutureOption(recommendedShoeStyleForSize(size))	
  
}	
  yield	
  shoe
recommendedShoe("a@example.com").future	
  onComplete	
  {	
  
	
  	
  case	
  Failure(t)	
  =>	
  println("Shoe	
  recommendation	
  failed")	
  
	
  	
  case	
  Success(None)	
  =>	
  println("No	
  shoe	
  recommendation")	
  
	
  	
  case	
  Success(Some(shoe))	
  =>	
  println(s"Shoe	
  recommendation:	
  $shoe")	
  
}
N*N types
=
N2
Implementations?
Monad Transformers To the Rescue
Scalaz
cats
OptionT[F[_], A]
Wraps F[Option[A]]
Works for any F[_] (as long as F[_] is a monad)
Implements passing None through the F[_] effect
e.g. OptionT[Future, A] is a wrapper for Future[Option[A]]
import	
  scala.concurrent.Future	
  
import	
  scala.concurrent.ExecutionContext.Implicits.global



import	
  scalaz._	
  
import	
  Scalaz._	
  
def	
  userForEmail(email:	
  String):	
  Future[Option[User]]	
  =	
  ???	
  
def	
  shoeSizeForUser(user:	
  User):	
  Future[Option[Size]]	
  =	
  ???	
  
def	
  recommendedShoeStyleForSize(size:	
  Size):	
  Future[Option[ShoeStyle]]	
  =	
  ???	
  
def	
  recommendedShoe(email:	
  String):	
  OptionT[Future,	
  ShoeStyle]	
  =	
  for	
  {	
  
	
  	
  user	
  <-­‐	
  OptionT(userForEmail(email))	
  
	
  	
  size	
  <-­‐	
  OptionT(shoeSizeForUser(user))	
  
	
  	
  shoe	
  <-­‐	
  OptionT(recommendedShoeStyleForSize(size))	
  
}	
  yield	
  shoe
recommendedShoe("a@example.com").run	
  onComplete	
  {	
  
	
  	
  case	
  Failure(t)	
  =>	
  println("Shoe	
  recommendation	
  failed")	
  
	
  	
  case	
  Success(None)	
  =>	
  println("No	
  shoe	
  recommendation")	
  
	
  	
  case	
  Success(Some(shoe))	
  =>	
  println(s"Shoe	
  recommendation:	
  $shoe")	
  
}
libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.1.4"
Either[A, B]
Errors with a “reason”
Either[A, B]:
not nearly as useful as it could be.
In scalaz, use:
A / B — same as /[A, B]
In cats, use:
A Xor B — same as Xor[A, B]
def	
  userForEmail(email:	
  String):	
  Future[String	
  /	
  User]	
  =	
  ???	
  
def	
  shoeSizeForUser(userOpt:	
  String	
  /	
  User):	
  Future[String	
  /	
  Size]	
  =	
  {	
  
	
  	
  userOpt.fold(	
  
	
  	
  	
  	
  err	
  =>	
  Future.successful(err.left),	
  
	
  	
  	
  	
  user	
  =>	
  ???	
  
	
  	
  )	
  
}	
  
def	
  recommendedShoeStyleForSize(sizeOpt:	
  String	
  /	
  Size):	
  Future[String	
  /	
  ShoeStyle]	
  =	
  {	
  
	
  	
  sizeOpt.fold(	
  
	
  	
  	
  	
  err	
  =>	
  Future.successful(err.left),	
  
	
  	
  	
  	
  size	
  =>	
  ???	
  
	
  	
  )	
  
}	
  
def	
  recommendedShoe(email:	
  String):	
  Future[String	
  /	
  ShoeStyle]	
  =	
  for	
  {	
  
	
  	
  user	
  <-­‐	
  userForEmail(email)	
  
	
  	
  size	
  <-­‐	
  shoeSizeForUser(user)	
  
	
  	
  shoe	
  <-­‐	
  recommendedShoeStyleForSize(size)	
  
}	
  yield	
  shoe
recommendedShoe("a@example.com")	
  onComplete	
  {	
  
	
  	
  case	
  scala.util.Failure(t)	
  =>	
  println("Shoe	
  recommendation	
  failed")	
  
	
  	
  case	
  scala.util.Success(res)	
  =>	
  
	
  	
  	
  	
  res.fold(	
  
	
  	
  	
  	
  	
  	
  err	
  =>	
  println(s"No	
  shoe	
  recommendation,	
  reason:	
  $err"),	
  
	
  	
  	
  	
  	
  	
  shoe	
  =>	
  println(s"Shoe	
  recommendation:	
  $shoe")	
  
	
  	
  	
  	
  )	
  
}
def	
  userForEmail(email:	
  String):	
  Future[String	
  /	
  User]	
  =	
  ???	
  
def	
  shoeSizeForUser(userOpt:	
  String	
  /	
  User):	
  Future[String	
  /	
  Size]	
  =	
  {	
  
	
  	
  userOpt.fold(	
  
	
  	
  	
  	
  err	
  =>	
  Future.successful(err.left),	
  
	
  	
  	
  	
  user	
  =>	
  ???	
  
	
  	
  )	
  
}	
  
def	
  recommendedShoeStyleForSize(sizeOpt:	
  String	
  /	
  Size):	
  Future[String	
  /	
  ShoeStyle]	
  =	
  {	
  
	
  	
  sizeOpt.fold(	
  
	
  	
  	
  	
  err	
  =>	
  Future.successful(err.left),	
  
	
  	
  	
  	
  size	
  =>	
  ???	
  
	
  	
  )	
  
}	
  
def	
  recommendedShoe(email:	
  String):	
  Future[String	
  /	
  ShoeStyle]	
  =	
  for	
  {	
  
	
  	
  user	
  <-­‐	
  userForEmail(email)	
  
	
  	
  size	
  <-­‐	
  shoeSizeForUser(user)	
  
	
  	
  shoe	
  <-­‐	
  recommendedShoeStyleForSize(size)	
  
}	
  yield	
  shoe
recommendedShoe("a@example.com")	
  onComplete	
  {	
  
	
  	
  case	
  scala.util.Failure(t)	
  =>	
  println("Shoe	
  recommendation	
  failed")	
  
	
  	
  case	
  scala.util.Success(res)	
  =>	
  
	
  	
  	
  	
  res.fold(	
  
	
  	
  	
  	
  	
  	
  err	
  =>	
  println(s"No	
  shoe	
  recommendation,	
  reason:	
  $err"),	
  
	
  	
  	
  	
  	
  	
  shoe	
  =>	
  println(s"Shoe	
  recommendation:	
  $shoe")	
  
	
  	
  	
  	
  )	
  
}
def	
  userForEmail(email:	
  String):	
  Future[String	
  /	
  User]	
  =	
  ???	
  
def	
  shoeSizeForUser(user:	
  User):	
  Future[String	
  /	
  Size]	
  =	
  ???	
  
def	
  recommendedShoeStyleForSize(size:	
  Size):	
  Future[String	
  /	
  ShoeStyle]	
  =	
  ???	
  
def	
  recommendedShoe(email:	
  String):	
  EitherT[Future,	
  String,	
  ShoeStyle]	
  =	
  for	
  {	
  
	
  	
  user	
  <-­‐	
  EitherT(userForEmail(email))	
  
	
  	
  size	
  <-­‐	
  EitherT(shoeSizeForUser(user))	
  
	
  	
  shoe	
  <-­‐	
  EitherT(recommendedShoeStyleForSize(size))	
  
}	
  yield	
  shoe
recommendedShoe("a@example.com").run	
  onComplete	
  {	
  
	
  	
  case	
  scala.util.Failure(t)	
  =>	
  println("Shoe	
  recommendation	
  failed")	
  
	
  	
  case	
  scala.util.Success(res)	
  =>	
  
	
  	
  	
  	
  res.fold(	
  
	
  	
  	
  	
  	
  	
  err	
  =>	
  println(s"No	
  shoe	
  recommendation,	
  reason:	
  $err"),	
  
	
  	
  	
  	
  	
  	
  shoe	
  =>	
  println(s"Shoe	
  recommendation:	
  $shoe")	
  
	
  	
  	
  	
  )	
  
}
Either vs Option
Some(3) /> "no value" == 3.right
None /> "no value" == "no value".left
Converting Option to / (Either)
Representing Errors
in Play Framework
type	
  Response[A]	
  =	
  EitherT[Future,	
  Result,	
  A]
type	
  Response[A]	
  =	
  EitherT[Future,	
  Result,	
  A]
Response[String] EitherT[Future,	
  Result,	
  String]
type	
  Response[A]	
  =	
  EitherT[Future,	
  Result,	
  A]
Response[String] EitherT[Future,	
  Result,	
  String]
Response[User] EitherT[Future,	
  Result,	
  User]
type	
  Response[A]	
  =	
  EitherT[Future,	
  Result,	
  A]
Response[String] EitherT[Future,	
  Result,	
  String]
Response[User]
Response[UserAndAuthInfo]
EitherT[Future,	
  Result,	
  User]
EitherT[Future,	
  Result,	
  UserAndAuthInfo]
type	
  Response[A]	
  =	
  EitherT[Future,	
  Result,	
  A]
Response[String] EitherT[Future,	
  Result,	
  String]
Response[User]
Response[UserAndAuthInfo]
Response[Result]
EitherT[Future,	
  Result,	
  User]
EitherT[Future,	
  Result,	
  UserAndAuthInfo]
EitherT[Future,	
  Result,	
  Result]
EitherT[Future,	
  Result,	
  Result]
def	
  merge(implicit	
  ev:	
  A	
  =:=	
  B)	
  =	
  fold(left	
  =>	
  ev(left),	
  right	
  =>	
  right)
Future[Result]
def	
  response(block:	
  =>	
  Response[Result]):	
  Action[AnyContent]	
  =	
  
	
  	
  Action.async(block.merge)	
  
def	
  response[A](bodyParser:	
  BodyParser[A])(block:	
  Request[A]	
  =>	
  Response[Result]):	
  Action[A]	
  =	
  
	
  	
  Action.async(bodyParser)(req	
  =>	
  block(req).merge)	
  
def	
  response(block:	
  Request[AnyContent]	
  =>	
  Response[Result]):	
  Action[AnyContent]	
  =	
  
	
  	
  response(BodyParsers.parse.default)(block)	
  
type	
  Response[A]	
  =	
  EitherT[Future,	
  Result,	
  A]	
  
object	
  Response	
  {	
  
	
  def	
  fromFuture[A](o:	
  Future[A]):	
  Response[A]	
  =	
  
	
  	
  	
  	
  EitherT(o.map(_.right))	
  
	
  	
  def	
  fromFutureOption[A](noValue:	
  =>	
  Result)(o:	
  Future[Option[A]]):	
  Response[A]	
  =	
  
	
  	
  	
  	
  EitherT(o.map(_	
  />	
  noValue))	
  
	
  	
  def	
  fromFutureEither[A,	
  B](err:	
  A	
  =>	
  Result)(o:	
  Future[A	
  /	
  B]):	
  Response[B]	
  =	
  
	
  	
  	
  	
  EitherT(o.map(_.leftMap(err)))	
  
	
  	
  def	
  fromOption[A](noValue:	
  =>	
  Result)(o:	
  Option[A]):	
  Response[A]	
  =	
  
	
  	
  	
  	
  EitherT(Future.successful(o	
  />	
  noValue))	
  
	
  	
  def	
  fromEither[A,	
  B](e:	
  A	
  =>	
  Result)(o:	
  A	
  /	
  B):	
  Response[B]	
  =	
  
	
  	
  	
  	
  EitherT(Future.successful(o.leftMap(e)))	
  
	
  	
  def	
  fromTry[A](t:	
  Throwable	
  =>	
  Result)(o:	
  Try[A]):	
  Response[A]	
  =	
  {	
  
	
  	
  	
  	
  val	
  eitherResult	
  =	
  o	
  match	
  {	
  
	
  	
  	
  	
  	
  	
  case	
  scala.util.Success(s)	
  =>	
  s.right	
  
	
  	
  	
  	
  	
  	
  case	
  scala.util.Failure(f)	
  =>	
  t(f).left	
  
	
  	
  	
  	
  }	
  
	
  	
  	
  	
  EitherT(Future.successful(eitherResult))	
  
	
  	
  }	
  
}
def	
  getUserByEmail(email:	
  String):	
  Future[String	
  /	
  User]	
  =	
  ???	
  
def	
  getFavouriteProducts(user:	
  User):	
  Future[String	
  /	
  Seq[String]]	
  =	
  ???
	
  def	
  productsByEmail(email:	
  String)	
  =	
  response	
  {	
  
	
  	
  	
  	
  for	
  {	
  
	
  	
  	
  	
  	
  	
  user	
  	
  	
  	
  	
  <-­‐	
  getUserByEmail(email)	
  	
  	
  	
  	
  	
  |>	
  fromFutureEither(s	
  =>	
  InternalServerError(s))	
  
	
  	
  	
  	
  	
  	
  products	
  <-­‐	
  getFavouriteProducts(user)	
  |>	
  fromFuture	
  
	
  	
  	
  	
  }	
  yield	
  Ok(views.html.productsByEmail(user,	
  products))	
  
	
  	
  }
type	
  Response[A]	
  =	
  EitherT[Future,	
  Result,	
  A]	
  
object	
  Response	
  {	
  
	
  def	
  fromFuture[A](o:	
  Future[A]):	
  Response[A]	
  =	
  
	
  	
  	
  	
  EitherT(o.map(_.right))	
  
	
  def	
  fromFutureEither[A,	
  B](err:	
  A	
  =>	
  Result)(o:	
  Future[A	
  /	
  B]):	
  Response[B]	
  =	
  
	
  	
  	
  	
  EitherT(o.map(_.leftMap(err)))	
  
}
f(a) == a |> f
Domain-specific
Errors
sealed	
  trait	
  UserServiceError	
  
private[controllers]	
  case	
  class	
  AuthError(why:	
  String)	
  extends	
  UserServiceError	
  
private[controllers]	
  case	
  object	
  ConnectionError	
  extends	
  UserServiceError	
  
object	
  UserServiceError	
  {	
  
	
  	
  def	
  authError(why:	
  String):	
  UserServiceError	
  =	
  AuthError(why)	
  
	
  	
  val	
  connectionError:	
  UserServiceError	
  =	
  ConnectionError	
  
	
  	
  def	
  fold[A](authError:	
  String	
  =>	
  A,	
  connectionError:	
  =>	
  A)(u:	
  UserServiceError)	
  =	
  {	
  
	
  	
  	
  	
  u	
  match	
  {	
  
	
  	
  	
  	
  	
  	
  case	
  AuthError(why)	
  =>	
  authError(why)	
  
	
  	
  	
  	
  	
  	
  case	
  ConnectionError	
  =>	
  connectionError	
  
	
  	
  	
  	
  }	
  
	
  	
  }	
  
}
sealed	
  trait	
  UserServiceError	
  
private[controllers]	
  case	
  class	
  AuthError(why:	
  String)	
  extends	
  UserServiceError	
  
private[controllers]	
  case	
  object	
  ConnectionError	
  extends	
  UserServiceError
def	
  getUserByEmail(email:	
  String):	
  Future[UserServiceError	
  /	
  User]	
  =	
  ???	
  
def	
  getFavouriteProducts(user:	
  User):	
  Future[String	
  /	
  Seq[String]]	
  =	
  ???
	
  	
  val	
  userServiceErrorToResult	
  =	
  UserServiceError.fold(	
  
	
  	
  	
  	
  authError	
  =	
  Forbidden(_),	
  
	
  	
  	
  	
  connectionError	
  =	
  InternalServerError("foobar")	
  
	
  	
  )	
  _	
  
	
  	
  def	
  productsByEmail(email:	
  String)	
  =	
  response	
  {	
  
	
  	
  	
  	
  for	
  {	
  
	
  	
  	
  	
  	
  	
  user	
  	
  	
  	
  	
  <-­‐	
  getUserByEmail(email)	
  	
  	
  	
  	
  	
  |>	
  fromFutureEither(userServiceErrorToResult)	
  
	
  	
  	
  	
  	
  	
  products	
  <-­‐	
  getFavouriteProducts(user)	
  |>	
  fromFuture	
  
	
  	
  	
  	
  }	
  yield	
  Ok(views.html.productsByEmail(user,	
  products))	
  
	
  	
  }	
  
Questions?

More Related Content

PDF
Custom deployments with sbt-native-packager
PDF
Advance Scala - Oleg Mürk
PDF
Ship your Scala code often and easy with Docker
PDF
Continous delivery - lad koden flyde 2014
PDF
Continous delivery with sbt
PDF
Automated Performance Testing With J Meter And Maven
PDF
Scala, docker and testing, oh my! mario camou
PDF
Akka Http , Routes, Streams with Scala
Custom deployments with sbt-native-packager
Advance Scala - Oleg Mürk
Ship your Scala code often and easy with Docker
Continous delivery - lad koden flyde 2014
Continous delivery with sbt
Automated Performance Testing With J Meter And Maven
Scala, docker and testing, oh my! mario camou
Akka Http , Routes, Streams with Scala

Viewers also liked (12)

PPTX
Using Jenkins and Jmeter to build a scalable Load Testing solution
PPT
Performance Testing With Jmeter
PDF
Akka http 2
ODP
An Introduction to Akka http
PDF
Securing Microservices using Play and Akka HTTP
PDF
Building scalable rest service using Akka HTTP
PPTX
Building a Reactive RESTful API with Akka Http & Slick
PDF
Spring Boot Microservices vs Akka Actor Cluster
PDF
Jmeter Performance Testing
PDF
Functional programming in Scala
PDF
Node.js vs Play Framework
PDF
Jenkins Workflow
Using Jenkins and Jmeter to build a scalable Load Testing solution
Performance Testing With Jmeter
Akka http 2
An Introduction to Akka http
Securing Microservices using Play and Akka HTTP
Building scalable rest service using Akka HTTP
Building a Reactive RESTful API with Akka Http & Slick
Spring Boot Microservices vs Akka Actor Cluster
Jmeter Performance Testing
Functional programming in Scala
Node.js vs Play Framework
Jenkins Workflow
Ad

Recently uploaded (20)

PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Encapsulation theory and applications.pdf
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Empathic Computing: Creating Shared Understanding
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Electronic commerce courselecture one. Pdf
PPTX
MYSQL Presentation for SQL database connectivity
PPTX
1. Introduction to Computer Programming.pptx
PDF
cuic standard and advanced reporting.pdf
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PPTX
Programs and apps: productivity, graphics, security and other tools
PDF
A comparative analysis of optical character recognition models for extracting...
PPT
Teaching material agriculture food technology
PDF
Accuracy of neural networks in brain wave diagnosis of schizophrenia
PDF
Machine learning based COVID-19 study performance prediction
Reach Out and Touch Someone: Haptics and Empathic Computing
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Encapsulation theory and applications.pdf
Mobile App Security Testing_ A Comprehensive Guide.pdf
Encapsulation_ Review paper, used for researhc scholars
Empathic Computing: Creating Shared Understanding
Digital-Transformation-Roadmap-for-Companies.pptx
Unlocking AI with Model Context Protocol (MCP)
The Rise and Fall of 3GPP – Time for a Sabbatical?
Electronic commerce courselecture one. Pdf
MYSQL Presentation for SQL database connectivity
1. Introduction to Computer Programming.pptx
cuic standard and advanced reporting.pdf
“AI and Expert System Decision Support & Business Intelligence Systems”
Advanced methodologies resolving dimensionality complications for autism neur...
Programs and apps: productivity, graphics, security and other tools
A comparative analysis of optical character recognition models for extracting...
Teaching material agriculture food technology
Accuracy of neural networks in brain wave diagnosis of schizophrenia
Machine learning based COVID-19 study performance prediction
Ad

Unsucking Error Handling with Futures

  • 3. def  userForEmail(email:  String):  Option[User]  =  ???   def  shoeSizeForUser(user:  User):  Option[Size]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Option[ShoeStyle]    =  ???   def  recommendedShoe(email:  String):  Option[ShoeStyle]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  match  {      case  None  =>  println("No  shoe  recommendation")      case  Some(shoe)  =>  println(s"Shoe  recommendation:  $shoe")   }
  • 5. def  userForEmail(email:  String):  Future[User]  =  ???   def  shoeSizeForUser(user:  User):  Future[Size]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[ShoeStyle]    =  ???   def  recommendedShoe(email:  String):  Future[ShoeStyle]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(shoe)  =>  println(s"Shoe  recommendation:  $shoe")   }
  • 6. Future[Option[A]] Errors (possible missing values) + asynchronous computation
  • 7. def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(userOpt:  Option[User]):  Future[Option[Size]]  =  {      userOpt  match  {          case  None  =>  Future.successful(None)          case  Some(user)  =>  ???      }   }   def  recommendedShoeStyleForSize(sizeOpt:  Option[Size]):  Future[Option[ShoeStyle]]  =  {      sizeOpt  match  {          case  None  =>  Future.successful(None)          case  Some(size)  =>  ???      }   }   def  recommendedShoe(email:  String):  Future[Option[ShoeStyle]]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   }
  • 8. def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(userOpt:  Option[User]):  Future[Option[Size]]  =  {      userOpt  match  {          case  None  =>  Future.successful(None)          case  Some(user)  =>  ???      }   }   def  recommendedShoeStyleForSize(sizeOpt:  Option[Size]):  Future[Option[ShoeStyle]]  =  {      sizeOpt  match  {          case  None  =>  Future.successful(None)          case  Some(size)  =>  ???      }   }   def  recommendedShoe(email:  String):  Future[Option[ShoeStyle]]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   }
  • 9. –Dave Thomas “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”
  • 10. def  flatMap[B](f:  A  =>  F[B]):  F[B]  Given  an  F[A]:
  • 11. def  flatMap[B](f:  A  =>  F[B]):  F[B]  Given  an  F[A]: case  class  FutureOption[+A](future:  Future[Option[A]])  {      def  flatMap[B](f:  A  =>  FutureOption[B]):  FutureOption[B]  =  {          val  result  =  future  flatMap  {              case  None  =>  Future.successful(None)              case  Some(opt)  =>  f(opt).future          }          FutureOption(result)      }   }
  • 12. def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(user:  User):  Future[Option[Size]]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[Option[ShoeStyle]]  =  ???   def  recommendedShoe(email:  String):  FutureOption[ShoeStyle]  =  for  {      user  <-­‐  FutureOption(userForEmail(email))      size  <-­‐  FutureOption(shoeSizeForUser(user))      shoe  <-­‐  FutureOption(recommendedShoeStyleForSize(size))   }  yield  shoe recommendedShoe("[email protected]").future  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   }
  • 13. def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(user:  User):  Future[Option[Size]]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[Option[ShoeStyle]]  =  ???   def  recommendedShoe(email:  String):  FutureOption[ShoeStyle]  =  for  {      user  <-­‐  FutureOption(userForEmail(email))      size  <-­‐  FutureOption(shoeSizeForUser(user))      shoe  <-­‐  FutureOption(recommendedShoeStyleForSize(size))   }  yield  shoe recommendedShoe("[email protected]").future  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   }
  • 14. def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(user:  User):  Future[Option[Size]]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[Option[ShoeStyle]]  =  ???   def  recommendedShoe(email:  String):  FutureOption[ShoeStyle]  =  for  {      user  <-­‐  FutureOption(userForEmail(email))      size  <-­‐  FutureOption(shoeSizeForUser(user))      shoe  <-­‐  FutureOption(recommendedShoeStyleForSize(size))   }  yield  shoe recommendedShoe("[email protected]").future  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   }
  • 16. Monad Transformers To the Rescue Scalaz cats
  • 17. OptionT[F[_], A] Wraps F[Option[A]] Works for any F[_] (as long as F[_] is a monad) Implements passing None through the F[_] effect e.g. OptionT[Future, A] is a wrapper for Future[Option[A]]
  • 18. import  scala.concurrent.Future   import  scala.concurrent.ExecutionContext.Implicits.global
 
 import  scalaz._   import  Scalaz._   def  userForEmail(email:  String):  Future[Option[User]]  =  ???   def  shoeSizeForUser(user:  User):  Future[Option[Size]]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[Option[ShoeStyle]]  =  ???   def  recommendedShoe(email:  String):  OptionT[Future,  ShoeStyle]  =  for  {      user  <-­‐  OptionT(userForEmail(email))      size  <-­‐  OptionT(shoeSizeForUser(user))      shoe  <-­‐  OptionT(recommendedShoeStyleForSize(size))   }  yield  shoe recommendedShoe("[email protected]").run  onComplete  {      case  Failure(t)  =>  println("Shoe  recommendation  failed")      case  Success(None)  =>  println("No  shoe  recommendation")      case  Success(Some(shoe))  =>  println(s"Shoe  recommendation:  $shoe")   } libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.1.4"
  • 19. Either[A, B] Errors with a “reason”
  • 20. Either[A, B]: not nearly as useful as it could be. In scalaz, use: A / B — same as /[A, B] In cats, use: A Xor B — same as Xor[A, B]
  • 21. def  userForEmail(email:  String):  Future[String  /  User]  =  ???   def  shoeSizeForUser(userOpt:  String  /  User):  Future[String  /  Size]  =  {      userOpt.fold(          err  =>  Future.successful(err.left),          user  =>  ???      )   }   def  recommendedShoeStyleForSize(sizeOpt:  String  /  Size):  Future[String  /  ShoeStyle]  =  {      sizeOpt.fold(          err  =>  Future.successful(err.left),          size  =>  ???      )   }   def  recommendedShoe(email:  String):  Future[String  /  ShoeStyle]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  onComplete  {      case  scala.util.Failure(t)  =>  println("Shoe  recommendation  failed")      case  scala.util.Success(res)  =>          res.fold(              err  =>  println(s"No  shoe  recommendation,  reason:  $err"),              shoe  =>  println(s"Shoe  recommendation:  $shoe")          )   }
  • 22. def  userForEmail(email:  String):  Future[String  /  User]  =  ???   def  shoeSizeForUser(userOpt:  String  /  User):  Future[String  /  Size]  =  {      userOpt.fold(          err  =>  Future.successful(err.left),          user  =>  ???      )   }   def  recommendedShoeStyleForSize(sizeOpt:  String  /  Size):  Future[String  /  ShoeStyle]  =  {      sizeOpt.fold(          err  =>  Future.successful(err.left),          size  =>  ???      )   }   def  recommendedShoe(email:  String):  Future[String  /  ShoeStyle]  =  for  {      user  <-­‐  userForEmail(email)      size  <-­‐  shoeSizeForUser(user)      shoe  <-­‐  recommendedShoeStyleForSize(size)   }  yield  shoe recommendedShoe("[email protected]")  onComplete  {      case  scala.util.Failure(t)  =>  println("Shoe  recommendation  failed")      case  scala.util.Success(res)  =>          res.fold(              err  =>  println(s"No  shoe  recommendation,  reason:  $err"),              shoe  =>  println(s"Shoe  recommendation:  $shoe")          )   }
  • 23. def  userForEmail(email:  String):  Future[String  /  User]  =  ???   def  shoeSizeForUser(user:  User):  Future[String  /  Size]  =  ???   def  recommendedShoeStyleForSize(size:  Size):  Future[String  /  ShoeStyle]  =  ???   def  recommendedShoe(email:  String):  EitherT[Future,  String,  ShoeStyle]  =  for  {      user  <-­‐  EitherT(userForEmail(email))      size  <-­‐  EitherT(shoeSizeForUser(user))      shoe  <-­‐  EitherT(recommendedShoeStyleForSize(size))   }  yield  shoe recommendedShoe("[email protected]").run  onComplete  {      case  scala.util.Failure(t)  =>  println("Shoe  recommendation  failed")      case  scala.util.Success(res)  =>          res.fold(              err  =>  println(s"No  shoe  recommendation,  reason:  $err"),              shoe  =>  println(s"Shoe  recommendation:  $shoe")          )   }
  • 25. Some(3) /> "no value" == 3.right None /> "no value" == "no value".left Converting Option to / (Either)
  • 27. type  Response[A]  =  EitherT[Future,  Result,  A]
  • 28. type  Response[A]  =  EitherT[Future,  Result,  A] Response[String] EitherT[Future,  Result,  String]
  • 29. type  Response[A]  =  EitherT[Future,  Result,  A] Response[String] EitherT[Future,  Result,  String] Response[User] EitherT[Future,  Result,  User]
  • 30. type  Response[A]  =  EitherT[Future,  Result,  A] Response[String] EitherT[Future,  Result,  String] Response[User] Response[UserAndAuthInfo] EitherT[Future,  Result,  User] EitherT[Future,  Result,  UserAndAuthInfo]
  • 31. type  Response[A]  =  EitherT[Future,  Result,  A] Response[String] EitherT[Future,  Result,  String] Response[User] Response[UserAndAuthInfo] Response[Result] EitherT[Future,  Result,  User] EitherT[Future,  Result,  UserAndAuthInfo] EitherT[Future,  Result,  Result]
  • 32. EitherT[Future,  Result,  Result] def  merge(implicit  ev:  A  =:=  B)  =  fold(left  =>  ev(left),  right  =>  right) Future[Result]
  • 33. def  response(block:  =>  Response[Result]):  Action[AnyContent]  =      Action.async(block.merge)   def  response[A](bodyParser:  BodyParser[A])(block:  Request[A]  =>  Response[Result]):  Action[A]  =      Action.async(bodyParser)(req  =>  block(req).merge)   def  response(block:  Request[AnyContent]  =>  Response[Result]):  Action[AnyContent]  =      response(BodyParsers.parse.default)(block)  
  • 34. type  Response[A]  =  EitherT[Future,  Result,  A]   object  Response  {    def  fromFuture[A](o:  Future[A]):  Response[A]  =          EitherT(o.map(_.right))      def  fromFutureOption[A](noValue:  =>  Result)(o:  Future[Option[A]]):  Response[A]  =          EitherT(o.map(_  />  noValue))      def  fromFutureEither[A,  B](err:  A  =>  Result)(o:  Future[A  /  B]):  Response[B]  =          EitherT(o.map(_.leftMap(err)))      def  fromOption[A](noValue:  =>  Result)(o:  Option[A]):  Response[A]  =          EitherT(Future.successful(o  />  noValue))      def  fromEither[A,  B](e:  A  =>  Result)(o:  A  /  B):  Response[B]  =          EitherT(Future.successful(o.leftMap(e)))      def  fromTry[A](t:  Throwable  =>  Result)(o:  Try[A]):  Response[A]  =  {          val  eitherResult  =  o  match  {              case  scala.util.Success(s)  =>  s.right              case  scala.util.Failure(f)  =>  t(f).left          }          EitherT(Future.successful(eitherResult))      }   }
  • 35. def  getUserByEmail(email:  String):  Future[String  /  User]  =  ???   def  getFavouriteProducts(user:  User):  Future[String  /  Seq[String]]  =  ???  def  productsByEmail(email:  String)  =  response  {          for  {              user          <-­‐  getUserByEmail(email)            |>  fromFutureEither(s  =>  InternalServerError(s))              products  <-­‐  getFavouriteProducts(user)  |>  fromFuture          }  yield  Ok(views.html.productsByEmail(user,  products))      } type  Response[A]  =  EitherT[Future,  Result,  A]   object  Response  {    def  fromFuture[A](o:  Future[A]):  Response[A]  =          EitherT(o.map(_.right))    def  fromFutureEither[A,  B](err:  A  =>  Result)(o:  Future[A  /  B]):  Response[B]  =          EitherT(o.map(_.leftMap(err)))   } f(a) == a |> f
  • 37. sealed  trait  UserServiceError   private[controllers]  case  class  AuthError(why:  String)  extends  UserServiceError   private[controllers]  case  object  ConnectionError  extends  UserServiceError   object  UserServiceError  {      def  authError(why:  String):  UserServiceError  =  AuthError(why)      val  connectionError:  UserServiceError  =  ConnectionError      def  fold[A](authError:  String  =>  A,  connectionError:  =>  A)(u:  UserServiceError)  =  {          u  match  {              case  AuthError(why)  =>  authError(why)              case  ConnectionError  =>  connectionError          }      }   }
  • 38. sealed  trait  UserServiceError   private[controllers]  case  class  AuthError(why:  String)  extends  UserServiceError   private[controllers]  case  object  ConnectionError  extends  UserServiceError def  getUserByEmail(email:  String):  Future[UserServiceError  /  User]  =  ???   def  getFavouriteProducts(user:  User):  Future[String  /  Seq[String]]  =  ???    val  userServiceErrorToResult  =  UserServiceError.fold(          authError  =  Forbidden(_),          connectionError  =  InternalServerError("foobar")      )  _      def  productsByEmail(email:  String)  =  response  {          for  {              user          <-­‐  getUserByEmail(email)            |>  fromFutureEither(userServiceErrorToResult)              products  <-­‐  getFavouriteProducts(user)  |>  fromFuture          }  yield  Ok(views.html.productsByEmail(user,  products))      }