2

Akka and Scala newbie here, please feel free to edit the question as necessary in order to clearly articulate my intent in the domain of Scala and Akka.

Before I show code snippets, here's the problem I want to solve: I essentially want to develop a common module for my team to use when they're developing their applications using Akka actors. I want to allow them to mixin a trait which will extend their receive functionality at runtime, mainly for logging purposes. I'm running into compile errors, which I'll explain soon.

But first, take for example, a simple main:

object Test extends App {

   val system = ActorSystem("system")
   val myActor = system.actorOf(Props(new MyActor), "myActor")
   myActor ! "Hello world!"
}

Here's an example implementation of an actor that a team member might implement in his application:

class MyActor extends Actor with ActorLogger {

   override def receive: Receive = {
       case msg => {
          log.info("testing ...")
       }
       case _ => throw new RuntimeException("Runtime Ex")
   }
}

And here's an example of how I would provide a common trait for them to mixin:

trait ActorLogger extends Actor {

    val log: DiagnosticLoggingAdapter = Logging(this)

    abstract override def receive: Receive = {

         case msg: Any => {
            if (msg.isInstanceOf[String]) {
              println("enter")
              log.mdc(Map[String, Any]("someKey" -> 123))
              super.receive(msg)
              log.clearMDC()
               println("exit")
            }
          }

         case _ => throw new RuntimeException("Runtime Ex")
    }

}

As you can see, I'm trying to add data to an MDC if the message so happens to be String (a basic example, in reality, I would check for some custom type of our own).

The error I get is:

 Error:(29, 16) overriding method receive in trait ActorLogger of type =>   
 MyActor.this.Receive;
 method receive needs `abstract override' modifiers
 override def receive: Receive = {
           ^

What's wrong here? And is stackable traits the right to go away to achieve something like this? If not, what is the most idiomatic way?

More generally, is there another pattern being applied here besides "interceptor" pattern?

Thanks for all the help!

HiChews123
  • 1,598
  • 4
  • 20
  • 39

3 Answers3

6

A solution without a hack with akka package:

import akka.actor.{Actor, ActorSystem, Props}

trait MyActorExtension extends Actor {

  def receiveExtension: Receive = PartialFunction.empty

}

abstract class MyActor extends MyActorExtension {

  protected def receiveMsg: Receive

  def receive: Receive = receiveExtension orElse receiveMsg

}

trait ActorLogger1 extends MyActor with MyActorExtension {

  abstract override def receiveExtension = {
    case msg =>
      println(s"********** Logging # 1: $msg")
      super.receiveExtension.applyOrElse(msg, receiveMsg)
  }

}

trait ActorLogger2 extends MyActor with MyActorExtension {

  abstract override def receiveExtension = {
    case msg =>
      println(s"########## Logging # 2: $msg")
      super.receiveExtension.applyOrElse(msg, receiveMsg)
  }
}

class SpecificActor extends MyActor with ActorLogger1 with ActorLogger2 {

  def receiveMsg = {
    case someMsg =>
      println(s"SpecificActor: $someMsg")
  }
}

object Test extends App {

  val system = ActorSystem("system")
  val mySpecificActor = system.actorOf(Props(new SpecificActor), "SpecificActor")
  mySpecificActor ! "Hello world!"
}
#### Logging # 2: Hello world!

****** Logging # 1: Hello world!

SpecificActor: Hello world!

  • What if you want to chain more than one behaviour inside one actor? – bkowalikpl Aug 01 '14 at 09:49
  • Thanks for the answer! I've tried this and it works, but I was looking for something that would require less from the clients who use the library. For example, ideally the client shouldn't have to extend my own custom actor (MyActor) with actor logging (ActorLogger1&2). They should still extend akka's Actor trait as usual, but mixin my logger trait. Is this achievable? – HiChews123 Aug 04 '14 at 04:24
  • I know only the way (hack) with akka package (see below an answer from @bkowalikpl). – Sergiy Prydatchenko Aug 04 '14 at 10:25
4

aroundReceive is for Akka internal use and the stackable trair pattern is not that comfortable for this case. I recommend you using Receive Pipeline for easy message interception.

ktonga
  • 190
  • 7
  • I expanded on this answer (specific to logging) in http://stackoverflow.com/a/39320938/843660 – dskrvk Sep 04 '16 at 20:00
1

I think that you need something like this

package akka

import akka.MsgsProt._
import akka.actor.{ Actor, ActorSystem, Props }

import scala.concurrent.duration._

sealed trait MsgProt
object MsgsProt {
  case object FooMsg extends MsgProt
  case object BarMsg extends MsgProt
}

trait Foo extends Actor {

  override protected[akka] def aroundReceive(receive: Actor.Receive, msg: Any): Unit = msg match {
    case FooMsg => println("Foo message")
    case msg    => super.aroundReceive(receive, msg)
  }
}

trait Bar extends Actor {
  override protected[akka] def aroundReceive(receive: Actor.Receive, msg: Any): Unit = msg match {
    case BarMsg => println("Bar message")
    case msg    => super.aroundReceive(receive, msg)
  }
}

class MyActor extends Actor with Foo with Bar {
  override def receive: Actor.Receive = {
    case _ => println("Nothing I know")
  }
}

object Foo extends App {
  val system = ActorSystem("foobar")
  val myActor = system.actorOf(Props[MyActor])
  implicit val timeout = 2 seconds

  myActor ! FooMsg
  myActor ! BarMsg
  myActor ! "wrong message"

  system.awaitTermination(10 seconds)
}

The output of this program is: Foo message Bar message Nothing I know

Most important part is that package declaration - akka. Because method aroundReceive is limited only to akka package so you have to have some.package.akka and inside you can use that method aroundReceive. I think that it looks more like a hack not a solution but works. You can see more usage of this inside Akka itself ex. akka.DiagnosticActorLogging. But this is basically solution that you want to do with stacking Actors behaviour.

bkowalikpl
  • 817
  • 5
  • 11