The document discusses various patterns and anti-patterns related to Akka actors. It begins by describing some common anti-patterns like asking actors for information instead of telling them, which can lead to timeouts. It then discusses patterns like dependency injection using actor references, blocking actors safely, initializing actors through messages or parameterized receives, and safely closing over actor state using a local executor. The document concludes by covering flow control patterns like push-based messaging, throttling, and push with acknowledgements or pull-based approaches.
2. About me
• Work at Qubell Inc.
• Scala since 2011
• Akka since 1.x
• Now: Scala, Akka, Play, MongoDB, RabbitMQ
3. Agenda
• Anti-patterns
• Ask is so cool
• Dependency injection for the win
• I like to block it, block it
• No mutable state is so functional
• Patterns
• Actor initialization
• Safe closures
• Flow control
6. Tell, don’t ask:
classic actor code
class FineService extends Actor {
def receive = {
case GetFine(v) =>
(greedinessService ? GetGreediness)
.mapTo[Double].map(_ * v).pipeTo(sender())
}
}
class GreedinessService extends Actor {
def receive = {
case GetGreediness =>
(db ? Query("select ..."))
.mapTo[Double].pipeTo(sender())
}
}
Timeout
Timeout
7. Tell, don’t ask
• Works perfect until loaded
• Timeouts everywhere
• equal — you don’t get the exact cause
• different — unmanageable hell
• Use thoughtful!
8. Tell, don’t ask:
invert control flow
class FineService extends Actor {
var greediness: Double = 0
def receive = {
case GetFine(v) => sender() ! greediness
case SetGreediness(g) => greediness = g
}
}
class GreedinessService extends Actor {
def receive = {
case ReloadGreediness =>
(db ? Query("select ..."))
.mapTo[Double].map(SetGreediness).pipeTo(fineService)
}
}
17. Stateless actor
class StatelessActor extends Actor {
def receive = {
case Divide(a, b) => sender() ! (a / b)
}
}
class Calculator extends Actor {
def receive = {
case Calculate =>
context.actorOf(Props[StatelessActor]) ! Divide(84, 2)
case result: Int =>
println(s"The answer is $result")
}
}
18. Just future
class Calculator extends Actor {
def receive = {
case Calculate =>
Future { 84 / 2 } pipeTo self
case result: Int =>
println(s"The answer is $result")
}
}
20. Actor initialization
• Actor parameters:
• Actor constructor
• Initializing message
• Initialize yourself
• Long initialization:
• Blocking
• Switching actor behavior with dropping
• Switching actor behavior with stashing
21. Actor initialization:
initializing message
class FineCalculator extends Actor {
var greediness = none[Double]
def receive = {
case SetGreediness(g) =>
greediness = some(g)
case GetFine(v) =>
sender() ! (v * greediness.get)
}
}
22. Actor initialization:
parameterized receive
class FineCalculator extends Actor {
def receive = uninitialized
def uninitialized: Receive = {
case SetGreediness(m) => context.become(initialized(m))
}
def initialized(greediness: Double): Receive = {
case GetFine(x) => sender() ! (x * greediness)
}
}
23. Scala.Rx
val a = Var(1)
val b = Var(2)
val c = Rx{ a() + b() }
val cObs = Obs(c) { println(c()) }
// prints 3
assert(c() == 3)
a() = 4
// prints 6
assert(c() == 6)
24. Actor initialization:
reactive receive
class FineCalculator extends Actor {
val greediness = Var(none[Double])
val actorReceive =
Rx { greediness().fold(uninitialized)(initialized) }
val actorReceiveObs =
Obs(actorReceive) { context.become(actorReceive()) }
def receive = uninitialized
def uninitialized: Receive = {
case SetGreediness(g) => greediness() = some(g)
}
def initialized(g: Double): Receive = uninitialized orElse {
case GetFine(v) => sender() ! (v * g)
}
}
25. Actor initialization:
initialize yourself
class FineCalculatorCallback extends Actor {
(context.actorSelection("..") ? GetGreediness)
.mapTo[Double].map(SetGreediness).pipeTo(self)
def receive = uninitialized
def uninitialized: Receive = {
case SetGreediness(m) => context.become(initialized(m))
}
def initialized(greediness: Double): Receive = {
case GetFine(x) => sender() ! (x * greediness)
}
}
27. Closing over actor state
When you use
• Future.apply
• future methods (map,
flatMap etc.)
• scheduler
And access
• this
• vars
• context
• sender()
you are asking for trouble
28. Unsafe closures
class Computer extends Actor {
var counter = 0
override def receive: Receive = {
case Inc =>
val requester = sender()
Future {
counter += 1 // unsafe!
requester ! counter
}
}
}
29. Local executor
trait LocalExecutor {
this: Actor =>
implicit val executor = new ExecutionContext {
override def execute(runnable: Runnable): Unit =
self ! runnable
override def reportFailure(t: Throwable): Unit =
self ! t
}
def receive: Receive = {
case r: Runnable => r.run()
case t: Throwable => throw t
}
}
30. Safe closures
class Computer extends Actor with LocalExecutor {
var counter = 0
override def receive: Receive = super.receive orElse {
case Inc =>
val requester = sender()
Future {
counter += 1 // safe
requester ! counter
}
}
}