Akka TypedActor vs. writing my own static interface to an Actor class

4.5k Views Asked by At

I've been using Akka and Scala for about a month and I'm somewhat bothered by replacing explicit interfaces with messages. Consider the following simple Akka Actor:

case class DoMyHomework()
class Parent extends Actor {
  def receive = {
    case d: DoMyHomework => // do nothing
  }
}

Actor or non-actor code that sends this actor a DoMyHomework message like this:

ActorRef parent = ...
parent.ask(DoMyHomework)

Has no idea what the outcome will be. What's the type of the answer? Will I ever get an answer? Can I get an exception? And so on.

The fix seems to be to document the case class... but what if some other actor also receives that same case class. Then the documentation should for receiving that message should be in the actor itself.

In an effort to clean this up a little I thought of doing the following:

trait SomeoneSmarter {
  def wouldYouDoMyHomework: Future[Boolean] 
}
class Parent extends Actor with SomeoneSmarter {
  case class DoMyHomework()
  def wouldYouDoMyHomework = {
    (self ? DoMyHomework()).mapTo(Boolean)
  }
  def receive = {
    case d: DoMyHomework =>
      // TODO: If I'm busy schedule a false "No way" reply for a few seconds from now.
      // Just to keep their hopes up for a while. Otherwise, say sure right away.
  }
}

So, I chatted with colleagues about this and one of the reactions was "you're not being true to the actor model."

First, I would really appreciate some guidance from folks that have been using Actors for a longer time. Do all the messages become unwieldy? Do you end up hiding message-passing behind interfaces?

The actors I'm proposing still have the option of sending messages among themselves, subscribing to event streams, all the stuff you expect from Akka. And the interface gives you a time-tested way of knowing what you're talking to. And it helps when coding in IDEs, and so on. And why should the user of an actor need to know it's an actor (unless it's also an actor and is very tightly coupled with it)?

The other reaction I got was "it looks like you want a TypedActor". But after reading about TypedActor I'm not convinced. Certainly TypedActor saves me the trouble of creating those internal messages. But, at least from the code sample at http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html I get the impression that the TypedActor is meant only to work as a proxy around a block of code you want to encapsulate, or make thread-safe, or simply not call directly from your current thread. And what you code is just the implementation and interface. You don't mess with the actor itself (the proxy) - e.g. if you want your implementation to do periodic work or subscribe to an event stream, or do anything else unrelated to the interface.

I've also read http://letitcrash.com/post/19074284309/when-to-use-typedactors and didn't find that example more illuminating. I'm probably just not grokking TypedActor (not that I claim to have really understood Actors yet).

thanks in advance for help.

Pino

3

There are 3 best solutions below

1
On

The Actor model of computation has huge similarities with object-oriented programming. OO is about indirect transfer of control. When you call a method (send a message) you lose the control of the message. Of course static programming languages help you a little bit with all the type checking goodness, but other than that you have no idea what will happen to the message. Hell, maybe the method never returns, although the return type clearly says it will (thrown exception, live lock, you name it...)! Agreed, when you used to Java or even better Scala it sucks giving up static typing, but it is not like you are not getting any benefits. Dynamic typing gives you loose coupling. For example there is no need to create extra interfaces just to introduce a mock actor for your tests; ActorRef is the only API you need.

2
On

Disclaimer: I'm not an Akka/Actors expert. I've been working with Actors and Akka about 18 months and I'm still trying to wrap my head around certain concepts especially when not to use Akka.

For the particular and narrow case that you want to know the return type of an Akka future, yes, you should use a TypedActor. The few times I've used TypedActors they were used to provide an API to a module that fell outside the Actor system. That is, I've built a system on top of Akka that did most of its work inside an Akka network but had one or two modules outside of the Akka network that required access to the features provided by the Akka network. The most notable was a Scalatra frontend that called into the Akka network and did some work on the values returned by the Akka network before responding to its client. The TypedActor, however, was really just a frontend to the Akka network. I look at using a TypedActor as an API frontend to external (external to the Akka network) modules as another separation of concerns.

In general, I agree with those that are telling you that "you're not being true to the actor model" in trying to coerce a view of its return types. In its purest form, and the way I've had the most success, the Actor model is implemented using fire and forget semantics. The messages do not get unwieldy and in many cases they've helped organize my code and define work boundaries. It does help to put them in their own package.

Were I to implement the feature you've described it would look like the following:

trait SomeoneSmarter {

  def wouldYouDoMyHomework : Boolean 

}

class Response()
case class NoWay() extends Response
case class Sure() extends Response

class ActorNetworkFrontEnd extends Actor {

  def receive = {
    case d: DoMyHomework =>
      busy match {
        case true => sender ! NoWay()
        case false => sender ! Sure()
      }
  }
}

case class SomeoneSmarter(actorNetworkFrontEnd:ActorRef) extends SomeoneSmarter {

  def wouldYouDoMyHomework : Boolean = {
    val future = actorNetworkFrontEnd ? DoMyHomework()
    val response = Await.result(future, timeout.duration).asInstanceOf[Response]
    response match {
      case NoWay() => false
      case Sure() => true
    }
  }

}

Keep in mind the way I've written wouldYouDoMyHomework, it will block while waiting for an answer. There are clever ways to do this asynchronously however. See http://doc.akka.io/docs/akka/2.0.3/scala/futures.html for more information.

Also, keep in mind that once your message is inside the Akka network you can do all of the cool scaling and remoting stuff and the user of your TypedActor API never has to know.

Doing this does add some complexity in large projects but if you consider it as seperating the responsibility of providing an API to external modules and maybe even move that responsibility to another package it's very easy to manage.

Good question. I can't wait to hear answers from the more experienced Akka developers.

0
On

Actor Encapsulation

Let me first respond to one point which I think is very important. You say:

And why should the user of an actor need to know it's an actor (unless it's also an actor and is very tightly coupled with it)?

Actors are a radically different programming paradigm from conventional OO, the main difference is that everything is asynchronous and hence there never are real “return values”. This means that it is usually a bad idea to hide the fact that it is an actor, for exceptions refer to my TypedActors blog post. The best thing about actors is that they are fully encapsulated—in Akka behind ActorRef—as opposed to the weak encapsulation of OO languages. To get the most out of it, expose ActorRefs wherever possible, which gives client code the opportunity to use them in the most appropriate way (which may be using tell or ask depending on the context).

Structuring your Messages

When writing an actor, you should put everything about this actor in one place, including the interface contract description. It could look a bit like this:

object Parent {
  /**
   * Send this message to make your parent do your homework … yeah, right ;-)
   */
  case object DoHomework
}

/**
 * This actor will do your homework if asked to.
 * 
 * ==Actor Contract==
 * 
 * ===Inbound Messages===
 *  - '''DoHomework''' will ask to do the homework
 * 
 * ===Outbound Messages===
 *  - '''HomeworkResult''' is sent as reply to the '''DoHomework''' request
 * 
 * ===Failure Modes===
 *  - '''BusinessTripException''' if the parent was not home
 *  - '''GrumpyException''' if the parent thinks you should do your own homework
 */
class Parent extends Actor {
  …
}

Differences to TypedActor

Using normal untyped actors lets you make use of the full power of the actor model, including dynamically changing the behavior, and not being tempted to box yourself into the timeout-guarded cage of “synchronous” calls (in short, TypedActors are mostly useful when implementing a traditional synchronous interface using actors behind the scenes). I agree that IDE support for message types would be nice, but that is a tool problem (I have been talking with the ScalaIDE team about adding some magic, but that will have to wait until it can receive priority). Having all properties about the actor defined in one place is the important part.