Lorenzo Barasti
A Future is
an object holding a value
which may become available
at some point.
Running expensive computations...
val inverseFuture: Future[Matrix] = Future {
fatMatrix.inverse()
}
...off the main thread
Retrieving data from a database...
val query = coffees
.filter(_.name.startsWith("ESPRESSO"))
.map(_.name)
val coffeeNames: Future[Seq[String]] = db.run(query.result)
...without blocking
Issuing an HTTP request...
val responseFuture: Future[HttpResponse] =
Http().singleRequest(HttpRequest(uri = "http://akka.io"))
...without having to wait for the response
Running concurrent computations
val vectors: Seq[(Float, Float)]
def length(vector: (Float, Float)): Float
val lengths: Seq[Float] = vectors.map(v => length(v))
Running concurrent computations
val vectors: Seq[(Float, Float)]
def length(vector: (Float, Float)): Float
val lengths: Seq[Future[Float]] = vectors.map(v => Future(length(v)))
Responsiveness
Error Isolation
Performance
Code Composability
Future {
Matrix.factorize(mat)
} // => Future[(Matrix, Matrix)]
Task A
db.run(query)
// => Future[Seq[Coffee]]
Task B
http.singleRequest(request)
// => Future[HttpResponse]
Task C
Task A
db.run(query)
// => Future[Seq[Coffee]]
Task B
http.singleRequest(request)
// => Future[HttpResponse]
Task C
Execution
Context
passed implicitly
Future {
Matrix.factorize(mat)
} // => Future[(Matrix, Matrix)]
Shared Queue
Task A
Execution Context
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Execution Context
Scheduler
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task B
Task C
Task D
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task C
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task C
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task C
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task C
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task C
Done
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task E
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task D
Task B
Task E
Task A
Done
Handling a successful future with .forEach
def hasCycle(g: Graph): Boolean
Future(hasCycle(g)).forEach { cycleFound =>
if(cycleFound) println("I found a cycle")
else println("The graph is acyclic")
}
// => _: Future[Unit]
Handling a successful future with .map
def hasCycle(g: Graph): Boolean
def topologicalOrdering(g: Graph): Seq[Int]
Future(hasCycle(g)).map { cycleFound =>
if(!cycleFound) Some(topologicalOrdering(g))
else None
}
// => _: Future[Option[Seq[Int]]]
Dealing with side-effects with .andThen
def colouring(g: Graph, upperBound: Int): Int
def findColouringUpperBound(g: Graph): Future[Int]
findColouringUpperBound(g).andThen {
case Failure(ex) => println(
"Could not compute upper bound")
case Success(upperBound) =>
updateUpperBoundCache(g, upperBound)
}.map(upperBound => colouring(g, upperBound))
// => Future[Int]
Running dependent operations with .flatMap
def colouring(g: Graph, upperBound: Int): Future[Int]
def findColouringUpperBound(g: Graph): Future[Int]
findColouringUpperBound(g)
.recover(_ => g.size)
.map(upperBound => colouring(g, upperBound))
.andThen {
case Success(Success(colours)) => println(g, colours)
}
// => Future[Future[Int]]
Running dependent operations with .flatMap
def colouring(g: Graph, upperBound: Int): Future[Int]
def findColouringUpperBound(g: Graph): Future[Int]
findColouringUpperBound(g)
.recover(_ => g.size)
.flatMap(upperBound => colouring(g, upperBound))
.andThen {
case Success(colour) => println(g, colour)
}
// => Future[Int]
Handling failures with .recover
def colouring(g: Graph, upperBound: Int): Int
def findColouringUpperBound(g: Graph): Future[Int]
findColouringUpperBound(g).recover { exeption =>
println("Could not compute upper bound, " +
"using graph size as fallback")
g.size
}.map(upperBound => colouring(g, upperBound))
// => Future[Int]
Avoid instantiating futures on known values
val userCache: Map[Int, User]
def getUserFromDb(id: Int): Future[User]
val userId: Int = 42
def getUser(id: Int): Future[User] = {
if(userCache.has(id))
Future(userCache(id))
else
getUserFromDb(id)
}
Avoid instantiating futures on known values
val userCache: Map[Int, User]
def getUserFromDb(id: Int): Future[User]
val userId: Int = 42
def getUser(id: Int): Future[User] = {
if(userCache.has(id))
Future.successful(userCache(id))
else
getUserFromDb(id)
}
Use .andThen to wrap side-effects and return the original value
def transform(user: User): PowerUser
storage.selectFileById(id).map { result =>
log.debug(s"Retrieved object:$id filesSize:'${result.size}'")
result
}.map(transform)
// => Future[PowerUser]
Use .andThen to wrap side-effects and return the original value
def transform(user: User): PowerUser
storage.selectFileById(id).andThen {
case Success(result) =>
log.debug(s"Retrieved object:$id filesSize:'${result.size}'")
}.map(transform)
// => Future[PowerUser]
Use .transform/.transformWith for fine grained control over failures
def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]
val g: Graph
getColourUpperBound(g).flatMap(n => colouring(g, n))
.recover(_ => colouringHeuristic(g))
// => Future[Seq[Int]]
Which error are we handling?
Use .transform/.transformWith for fine grained control over failures
def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]
val g = Graph(...)
getColourUpperBound(g).transform {
case Success(n) => colouring(g, n)
case Failure(_) => colouringHeuristic(g)
}
// Future[Seq[Int]]
Use for comprehension
def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]
def sendResult(colouring: Seq[Int]): Future[HttpResponse]
def report(result: Seq[Int]): Report
val g = Graph(...)
getColourUpperBound(g).flatMap { upperBound =>
colouring(g, upperBound).flatMap { result =>
sendResult(result).map { _ =>
report(result)
}
}
}
// Future[Report]
Use for comprehension
def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]
def sendResult(colouring: Seq[Int]): Future[HttpResponse]
def report(result: Seq[Int]): Report
val g = Graph(...)
for {
upperBound <- getColourUpperBound(g)
result <- colouring(g, upperBound)
_ <- sendResult(result)
} yield report(result)
// Future[Report]
NICE!
GetUserProfile
GetUserProfile
GetUserProfile
GetUserProfile
UserProfile
GetUserProfile
Cache
GetUserProfile
UserProfile
Cache
Actor A: just ask
val userProfileActor: ActorRef
val userId: UserId
(userProfileActor ? GetUserProfile(userId))
.mapTo[UserProfile]
.andThen { case profile => println(userId, profile) }
Send a message and wait for a response
Actor A: just ask
val userProfileActor: ActorRef
val userId: UserId
(userProfileActor ? GetUserProfile(userId))
.mapTo[UserProfile]
.andThen { case profile => println(userId, profile) }
The type of the response is Any
val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
fetch(userId).map { profile =>
cache += userId -> profile
sender() ! profile
}
}
}
UserProfileActor
ALERT! Closing over sender()
val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
fetch(userId).map { profile =>
cache += userId -> profile
sender() ! profile
}
}
}
UserProfileActor
Won't return the sender you expect
val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
val ref = sender()
fetch(userId).map { profile =>
cache += userId -> profile
ref ! profile
}
}
}
UserProfileActor
Capture the sender in the right context
val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
val ref = sender()
fetch(userId).map { profile =>
cache += userId -> profile
ref ! profile
}
}
}
UserProfileActor
Suspicious...
val http: HttpRef
var cache: Map[UserId, UserProfile]
var subscribers: Map[UserId, Seq[ActorRef]]
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
if(subscribers(userId).isEmpty)
fetch(userId).foreach { profile =>
cache += userId -> profile
subscribers(userId).foreach(_ ! profile)
subscribers -= userId
}
subscribers += userId -> (sender() +: subscribers(userId))
}
}
UserProfileActor
Changing the actor state
val http: HttpRef
var cache: Map[UserId, UserProfile]
var subscribers: Map[UserId, Seq[ActorRef]]
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
if(subscribers(userId).isEmpty)
fetch(userId).map(FetchedUserProfile) pipeTo self
val updatedSubs = sender() +: subscribers(userId)
subscribers += userId -> updatedSubs
}
case FetchedUserProfile(profile) =>
cache += profile.userId -> profile
subscribers(profile.userId).foreach(_ ! profile)
subscribers = subscribers - profile.userId
Wooooooo!
What if the call fails?
def receive: Receive = {
// ...
case failure @ Status.Failure(InvalidResponse(userId, statusCode)) =>
subscribers(userId).foreach(_ ! failure)
subscribers = subscribers - userId
case failure @ Status.Failure(MalformedJsonException(_)) =>
subscribers(userId).foreach(_ ! failure)
subscribers = subscribers - userId
}
Your application
with Futures
Fast
Responsive
Awesome
Your Futures
Composable API
Maintainable code
Available in many colours!
Should have used Scala Futures
Bad error handling
pipeTo
Actors
Akka Application
ask
Lorenzo Barasti
Jacob Taylor-Hindle
@lbarasti
lbarasti.github.io
@glassandonehalf