Actor DSL
Loading

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()
      }
  }
})

Contents