The document discusses Akka Typed, a new approach to typed actors in Akka. Some key differences from regular Akka actors include:
- Behaviors are used instead of receive partial functions
- Protocols are made explicit through message types and ActorRef types
- Become is replaced by returning the next behavior
- The sender reference is replaced by passing the replyTo in message types
- Actor systems and actor refs are typed based on message types
6. Disclaimer
Modules discussed in this talk are
pre-experimental or not even released yet.
Also known as
“don’t use in production”,
“please give us feedback”,
“docs are work in progress”
etc.
Everything shown is experimental!
The cake is a lie!
7. Agenda
• What Akka is at it’s heart
• Old Typed Actors + some background
• Akka Typed
• Akka Streams / Reactive Streams
• Wrapping up
experimental
experimental
not released yet
10. The Actor Model
The Actor Model as defined by Hewitt, Bishop and Steiger in 1973 is a computational
model that expresses exactly what it means for computation to be distributed.
Actors can only communicate by exchanging messages.
Upon reception of a message an Actor can do the following three fundamental actions:
• send a finite number of messages to Actors it knows
• create a finite number of new Actors
• designate the behavior to be applied to the next message
11. Why Untyped?
Inspired by Erlang, that’s untyped as well.
Modelling “become” is non obvious:
“what about races in sending msgs which change state?”
Networking is untyped - sorry.
Pattern matching makes extracting types simple.
Several failed attempts (incl. in Erlang)
(Yes, we know session types).
15. Old Typed Actors
Were never intended to be a core abstraction.
Just a “bridge from Java-land to Actor-land”.
A note on Distributed Computing by Jim Waldo et al.
16. Old Typed Actors
Were never intended to be a core abstraction.
Just a “bridge from Java-land to Actor-land”.
trait Squarer {
def squareDontCare(i: Int): Unit //fire-forget
def square(i: Int): Future[Int] //non-blocking send-request-reply
def squareNowPlease(i: Int): Option[Int] //blocking send-request-reply
def squareNow(i: Int): Int //blocking send-request-reply
@throws(classOf[Exception]) //declare it or you will get an UndeclaredThrowableException
def squareTry(i: Int): Int //blocking send-request-reply with possible exception
}
A note on Distributed Computing by Jim Waldo et al.
17. Old Typed Actors
Were never intended to be a core abstraction.
Just a “bridge from Java-land to Actor-land”.
trait Squarer {
def squareDontCare(i: Int): Unit //fire-forget
def square(i: Int): Future[Int] //non-blocking send-request-reply
def squareNowPlease(i: Int): Option[Int] //blocking send-request-reply
def squareNow(i: Int): Int //blocking send-request-reply
@throws(classOf[Exception]) //declare it or you will get an UndeclaredThrowableException
def squareTry(i: Int): Int //blocking send-request-reply with possible exception
}
NOT what you’re looking for
A note on Distributed Computing by Jim Waldo et al.
18. The old “Typed Actors”
trait Squarer {
def squareDontCare(i: Int): Unit //fire-forget
def square(i: Int): Future[Int] //non-blocking send-request-reply
def squareNowPlease(i: Int): Option[Int] //blocking send-request-reply
def squareNow(i: Int): Int //blocking send-request-reply
@throws(classOf[Exception]) //declare it or you will get an UndeclaredThrowableException
def squareTry(i: Int): Int //blocking send-request-reply with possible exception
}
NOT what you’re looking for
- 10x slower than plain Actors (because reflection)
- way too easy to expose blocking APIs (oh no!)
- interface cannot express become
- too much magic
http://stackoverflow.com/questions/28516273/akka-typed-actors-in-java
20. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor
21. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
val greeter = Total[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Same
}
val system: ActorSystem[Greet] =
ActorSystem(“typed", Props(totalGreeter))
system ! Greet("kapi", system.deadLetters)
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor Akka Typed
22. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
val greeter = Total[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Same
}
val system: ActorSystem[Greet] =
ActorSystem(“typed", Props(totalGreeter))
system ! Greet("kapi", system.deadLetters)
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor Akka Typed
The main concept is Behaviour[T]
Explicit protocols for the win!
23. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
val greeter = Total[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Same
}
val system: ActorSystem[Greet] =
ActorSystem(“typed", Props(totalGreeter))
system ! Greet("kapi", system.deadLetters)
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor Akka Typed
Since Behaviour[Greet] is typed,
msg is-a Greet.
24. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
val greeter = Total[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Same
}
val system: ActorSystem[Greet] =
ActorSystem(“typed", Props(totalGreeter))
system ! Greet("kapi", system.deadLetters)
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor Akka Typed
The Behaviour[T]is the receive.
25. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
val greeter = Total[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Same
}
val system: ActorSystem[Greet] =
ActorSystem(“typed", Props(totalGreeter))
system ! Greet("kapi", system.deadLetters)
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor Akka Typed
sender() is no more.
Explicit protocols for the win!
26. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
val greeter = Total[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Same
}
val system: ActorSystem[Greet] =
ActorSystem(“typed", Props(totalGreeter))
system ! Greet("kapi", system.deadLetters)
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor Akka Typed
sender() is no more.
Explicit protocols for the win!
final case class Greet(whom: String, replyTo: ActorRef[Greeted])
27. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
val greeter = Total[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Same
}
val system: ActorSystem[Greet] =
ActorSystem(“typed", Props(totalGreeter))
system ! Greet("kapi", system.deadLetters)
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor Akka Typed
ActorRef[T] is now typed!
28. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
val greeter = Total[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Same
}
val system: ActorSystem[Greet] =
ActorSystem(“typed", Props(totalGreeter))
system ! Greet("kapi", system.deadLetters)
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor Akka Typed
become()is required.
And replaced by returning the “next” Behaviour[T]
// context.become(receive)
29. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
val greeter = Total[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Same
}
val system: ActorSystem[Greet] =
ActorSystem(“typed", Props(totalGreeter))
system ! Greet("kapi", system.deadLetters)
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor Akka Typed
become()is required.
And replaced by returning the “next” Behaviour[T]
On a conceptual level at least,
we provide Static[T]if you don’t need become.
// context.become(receive)
30. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
val greeter = Total[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Same
}
val system: ActorSystem[Greet] =
ActorSystem(“typed", Props(totalGreeter))
system ! Greet("kapi", system.deadLetters)
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor Akka Typed
become()is required.
And replaced by returning the “next” Behaviour[T]
Special behaviors:
Same / Unhandled / Empty / Stopped / Ignore
// context.become(receive)
31. Akka Typed: What’s different?
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
val greeter = Total[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Same
}
val system: ActorSystem[Greet] =
ActorSystem(“typed", Props(totalGreeter))
system ! Greet("kapi", system.deadLetters)
class Greeter extends Actor {
def receive = {
case msg: Greet
println(s"Hello ${msg.whom}!")
sender() ! Greeted(msg.whom)
}
}
val system: ActorSystem =
ActorSystem(“akka-actor-system“)
val greeter =
system.actorOf(Props[Greeter])
system ! Greet("kapi", system.deadLetters)
Akka Actor Akka Typed
“Root actor” is not user-defined for ActorSystem[T]
Encourages thinking about supervision.
33. Akka Typed: Behaviors
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
object HelloWorld {
final case class Greet(whom: String, replyTo: ActorRef[Greeted])
final case class Greeted(whom: String)
val greeter = Static[Greet] { msg
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
}
}
Static[T] - if become not needed
Total[T] - behavior is total function
Partial[T] - behavior is partial function
Full[T] - when interested in system signals
FullTotal[T] or ActorContext
34. Akka Typed: Behaviors
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
Static[T] - if become not needed
Total[T] - behavior is total function
Partial[T] - behavior is partial function
Full[T] - when interested in system signals
FullTotal[T] or ActorContext
val master = Full[WorkProtocol] {
case Msg(ctx, w: Work)
ctx.spawn(Props(worker), s"worker-${w.id}") ! w
// ...
}
35. Akka Typed: Behavior Combinators
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
Combine behaviors:
final case class And[T](left: Behavior[T], right: Behavior[T])
extends Behavior[T] { /* … */ }
final case class Or[T](left: Behavior[T], right: Behavior[T])
extends Behavior[T] { /* … */ }
Smarter “left orElse right”
36. Akka Typed: narrow / widen
http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html
def narrow[U <: T]: Behavior[U]
def widen[U >: T](matcher: PartialFunction[U, T]): Behavior[U]
Narrowing a Behavior is always safe
Widening requires help in form of a matcher:
69. Akka Streams – Linear Flow
Flow[Double].map(_.toInt). [...]
No Source attached yet.
“Pipe ready to work with Doubles”.
70. Akka Streams – Linear Flow
implicit val sys = ActorSystem("scalar-sys")
implicit val mat = ActorFlowMaterializer()
Source(1 to 3).runWith(Sink.foreach(println))
71. Akka Streams – Linear Flow
implicit val sys = ActorSystem("scalar-sys")
implicit val mat = ActorFlowMaterializer()
Source(1 to 3).runWith(Sink.foreach(println))
// sugar for runWith
Source(1 to 3).foreach(println)
72. Akka Streams – Linear Flow
implicit val sys = ActorSystem("scalar-sys")
implicit val mat = ActorFlowMaterializer()
Source(1 to 3).runWith(Sink.foreach(println))
// sugar for runWith
Source(1 to 3).foreach(println)
Sink.fold
Sink.head
Sink.ignore
Sink.publisher
Sink.cancelled
// your own Sink
…
73. Akka Streams <-> Actors – Advanced
val subscriber = ActorSubscriber(
system.actorOf(Props[SubStreamParent], ”parent”)
)
Source(1 to 100)
.map(_.toString)
.filter(_.length == 2)
.drop(2)
.conflate(seed => seed)((acc, i) => acc + i)
.groupBy(_.last)
.runWith(subscriber)
All the usual ops available for Linear Flows.
75. Akka Streams <-> Actors – Advanced
val subscriber = ActorSubscriber(
system.actorOf(Props[SubStreamParent], ”parent”)
)
Source(1 to 100)
.map(_.toString)
.filter(_.length == 2)
.drop(2)
.conflate(seed => seed)((acc, i) => acc + i)
.groupBy(_.last)
.runWith(subscriber)
Creates a stream of streams:
Source[Source[String]]
76. Akka Streams: Graphs
val p: Publisher[String] =
FlowGraph.closed(out) { implicit b o
val merge = b.add(new StrictRoundRobin[String])
in1 ~> merge.in(0)
in2 ~> merge.in(1)
merge.out ~> o.inlet
}.run()
val in1 = Source(List("a", "b", "c", "d"))
val in2 = Source(List("e", "f"))
val out = Sink.publisher[String]
77. Akka Streams: Graphs
val p: Publisher[String] =
FlowGraph.closed(out) { implicit b o
val merge = b.add(new StrictRoundRobin[String])
in1 ~> merge.in(0)
in2 ~> merge.in(1)
merge.out ~> o.inlet
}.run()
val in1 = Source(List("a", "b", "c", "d"))
val in2 = Source(List("e", "f"))
val out = Sink.publisher[String]
Sink[String, Publisher[String]]
imports Graphs
78. Akka Streams: Graphs
val p: Publisher[String] =
FlowGraph.closed(out) { implicit b o
val merge = b.add(new StrictRoundRobin[String])
in1 ~> merge.in(0)
in2 ~> merge.in(1)
merge.out ~> o.inlet
}.run()
val in1 = Source(List("a", "b", "c", "d"))
val in2 = Source(List("e", "f"))
val out = Sink.publisher[String]
Sink[String, Publisher[String]]
materializes a Publisher[String]
79. Akka Streams: Partial Graphs
FlowGraph.partial() { implicit b
import FlowGraph.Implicits._
val priorityMerge = b.add(MergePreferred[In](1))
val balance = b.add(Balance[In](workerCount))
val resultsMerge = b.add(Merge[Out](workerCount))
// After merging priority and ordinary jobs, we feed them to the balancer
priorityMerge ~> balance
// Wire up each of the outputs of the balancer to a worker flow
// then merge them back
for (i <- 0 until workerCount)
balance.out(i) ~> worker ~> resultsMerge.in(i)
// We now expose the input ports of the priorityMerge and the output
// of the resultsMerge as our PriorityWorkerPool ports
// -- all neatly wrapped in our domain specific Shape
PriorityWorkerPoolShape(
jobsIn = priorityMerge.in(0),
priorityJobsIn = priorityMerge.preferred,
resultsOut = resultsMerge.out)
}
80. Akka Streams: Partial Graphs
FlowGraph.partial() { implicit b
import FlowGraph.Implicits._
val priorityMerge = b.add(MergePreferred[In](1))
val balance = b.add(Balance[In](workerCount))
val resultsMerge = b.add(Merge[Out](workerCount))
// After merging priority and ordinary jobs, we feed them to the balancer
priorityMerge ~> balance
// Wire up each of the outputs of the balancer to a worker flow
// then merge them back
for (i <- 0 until workerCount)
balance.out(i) ~> worker ~> resultsMerge.in(i)
// We now expose the input ports of the priorityMerge and the output
// of the resultsMerge as our PriorityWorkerPool ports
// -- all neatly wrapped in our domain specific Shape
PriorityWorkerPoolShape(
jobsIn = priorityMerge.in(0),
priorityJobsIn = priorityMerge.preferred,
resultsOut = resultsMerge.out)
}
81. Akka Streams: Partial Graphs
FlowGraph.partial() { implicit b
import FlowGraph.Implicits._
val priorityMerge = b.add(MergePreferred[In](1))
val balance = b.add(Balance[In](workerCount))
val resultsMerge = b.add(Merge[Out](workerCount))
// After merging priority and ordinary jobs, we feed them to the balancer
priorityMerge ~> balance
// Wire up each of the outputs of the balancer to a worker flow
// then merge them back
for (i <- 0 until workerCount)
balance.out(i) ~> worker ~> resultsMerge.in(i)
// We now expose the input ports of the priorityMerge and the output
// of the resultsMerge as our PriorityWorkerPool ports
// -- all neatly wrapped in our domain specific Shape
PriorityWorkerPoolShape(
jobsIn = priorityMerge.in(0),
priorityJobsIn = priorityMerge.preferred,
resultsOut = resultsMerge.out)
}
case class PriorityWorkerPoolShape[In, Out](
jobsIn: Inlet[In],
priorityJobsIn: Inlet[In],
resultsOut: Outlet[Out]) extends Shape { // …
82. Akka Streams: Partial Graphs
val worker1 = Flow[String].map("step 1 " + _)
val worker2 = Flow[String].map("step 2 " + _)
FlowGraph.closed() { implicit b =>
import FlowGraph.Implicits._
val priorityPool1 = b.add(PriorityWorkerPool(worker1, 4))
val priorityPool2 = b.add(PriorityWorkerPool(worker2, 2))
Source(1 to 100).map("job: " + _) ~> priorityPool1.jobsIn
Source(1 to 100).map("priority job: " + _) ~> priorityPool1.priorityJobsIn
priorityPool1.resultsOut ~> priorityPool2.jobsIn
Source(1 to 100).map("one-step, priority " + _) ~> priorityPool2.priorityJobsIn
priorityPool2.resultsOut ~> Sink.foreach(println)
}.run()
83. Akka Streams: Partial Graphs
val worker1 = Flow[String].map("step 1 " + _)
val worker2 = Flow[String].map("step 2 " + _)
FlowGraph.closed() { implicit b =>
import FlowGraph.Implicits._
val priorityPool1 = b.add(PriorityWorkerPool(worker1, 4))
val priorityPool2 = b.add(PriorityWorkerPool(worker2, 2))
Source(1 to 100).map("job: " + _) ~> priorityPool1.jobsIn
Source(1 to 100).map("priority job: " + _) ~> priorityPool1.priorityJobsIn
priorityPool1.resultsOut ~> priorityPool2.jobsIn
Source(1 to 100).map("one-step, priority " + _) ~> priorityPool2.priorityJobsIn
priorityPool2.resultsOut ~> Sink.foreach(println)
}.run()
87. Wrapping up
The future is typed!
More typesafety / perf / features coming in future releases.
It’s not “just typesafety”,
it’s profoundly better ways to model things.
Static stream processing graph layouts =
more constraints => better ways to optimise.
89. Timing
Reactive Streams - 1.0-RC5 yesterday, 1.0 in “weeks”
Akka Streams – 1.0-RC1 in around 2 weeks
Akka Http – 1.0-M6 in around 2 weeks
Akka Typed – merged as draft + experimental in Akka 2.4.x
Akka Persistence – new “read side” will use Akka Streams
ALL DATES ARE SUBJECT TO CHANGE.
It’s done “when it’s done.”