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!
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!
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.
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.
来源:https://stackoverflow.com/questions/25074647/akka-intercepting-receive-with-stackable-behavior