Actor DSL
§The Actor DSL
Simple actors—for example one-off workers or even when trying things out in the
REPL—can be created more concisely using the Act
trait. The supporting
infrastructure is bundled in the following import:
- import akka.actor.ActorDSL._
- import akka.actor.ActorSystem
-
- implicit val system = ActorSystem("demo")
This import is assumed for all code samples throughout this section. The
implicit actor system serves as ActorRefFactory
for all examples
below. To define a simple actor, the following is sufficient:
- val a = actor(new Act {
- become {
- case "hello" ⇒ sender() ! "hi"
- }
- })
Here, actor
takes the role of either system.actorOf
or
context.actorOf
, depending on which context it is called in: it takes an
implicit ActorRefFactory
, which within an actor is available in the
form of the implicit val context: ActorContext
. Outside of an actor, you’ll
have to either declare an implicit ActorSystem
, or you can give the
factory explicitly (see further below).
The two possible ways of issuing a context.become
(replacing or adding the
new behavior) are offered separately to enable a clutter-free notation of
nested receives:
- val a = actor(new Act {
- become { // this will replace the initial (empty) behavior
- case "info" ⇒ sender() ! "A"
- case "switch" ⇒
- becomeStacked { // this will stack upon the "A" behavior
- case "info" ⇒ sender() ! "B"
- case "switch" ⇒ unbecome() // return to the "A" behavior
- }
- case "lobotomize" ⇒ unbecome() // OH NOES: Actor.emptyBehavior
- }
- })
Please note that calling unbecome
more often than becomeStacked
results
in the original behavior being installed, which in case of the Act
trait is the empty behavior (the outer become
just replaces it during
construction).
§Life-cycle management
Life-cycle hooks are also exposed as DSL elements (see Start Hook and Stop Hook), where later invocations of the methods shown below will replace the contents of the respective hooks:
- val a = actor(new Act {
- whenStarting { testActor ! "started" }
- whenStopping { testActor ! "stopped" }
- })
The above is enough if the logical life-cycle of the actor matches the restart
cycles (i.e. whenStopping
is executed before a restart and whenStarting
afterwards). If that is not desired, use the following two hooks (see Restart Hooks):
- val a = actor(new Act {
- become {
- case "die" ⇒ throw new Exception
- }
- whenFailing { case m @ (cause, msg) ⇒ testActor ! m }
- whenRestarted { cause ⇒ testActor ! cause }
- })
It is also possible to create nested actors, i.e. grand-children, like this:
- // here we pass in the ActorRefFactory explicitly as an example
- val a = actor(system, "fred")(new Act {
- val b = actor("barney")(new Act {
- whenStarting { context.parent ! ("hello from " + self.path) }
- })
- become {
- case x ⇒ testActor ! x
- }
- })
Note
In some cases it will be necessary to explicitly pass the
ActorRefFactory
to the actor
method (you will notice when
the compiler tells you about ambiguous implicits).
The grand-child will be supervised by the child; the supervisor strategy for
this relationship can also be configured using a DSL element (supervision
directives are part of the Act
trait):
- superviseWith(OneForOneStrategy() {
- case e: Exception if e.getMessage == "hello" ⇒ Stop
- case _: Exception ⇒ Resume
- })
§Actor with Stash
Last but not least there is a little bit of convenience magic built-in, which
detects if the runtime class of the statically given actor subtype extends the
RequiresMessageQueue
trait via the Stash
trait (this is a
complicated way of saying that new Act with Stash
would not work because its
runtime erased type is just an anonymous subtype of Act
). The purpose is to
automatically use the appropriate deque-based mailbox type required by Stash
.
If you want to use this magic, simply extend ActWithStash
:
- val a = actor(new ActWithStash {
- become {
- case 1 ⇒ stash()
- case 2 ⇒
- testActor ! 2; unstashAll(); becomeStacked {
- case 1 ⇒ testActor ! 1; unbecome()
- }
- }
- })