I have two Akka actors that respond to some messages in the same way, but others in a different way. They both respond to the same set of messages. Wondering how to design my two actors with their receive methods, via inheritance, composure, etc? I tried chaining together partial functions from other traits with "orElse", which unfortunately exposes the class to its trait's functionality, plus I wasn't sure how the trait's receive could easily access the actor context. A drop-in, modularized solution would be ideal, but I'm wondering if this is a solved problem somewhere?
There's really a bunch of ways that you can go about this. I'll list two from the OO way (similar to what @Randal Schulz is suggesting) and 1 more functional way. For the first possible solution, you could do something simple like this:
case class MessageA(s:String)
case class MessageB(i:Int)
case class MessageC(d:Double)
trait MyActor extends Actor{
def receive = {
case a:MessageA =>
handleMessageA(a)
case b:MessageB =>
handleMessageB(b)
case c:MessageC =>
handleMessageC(c)
}
def handleMessageA(a:MessageA)
def handleMessageB(b:MessageB) = {
//do handling here
}
def handleMessageC(c:MessageC)
}
class MyActor1 extends MyActor{
def handleMessageA(a:MessageA) = {}
def handleMessageC(c:MessageC) = {}
}
class MyActor2 extends MyActor{
def handleMessageA(a:MessageA) = {}
def handleMessageC(c:MessageC) = {}
}
With this approach, you define basically an abstract actor impl where the receive
function is defined for all messages that are handled. The messages are delegated to def
s where the real business logic would be. Two are abstract, letting the concrete classes define the handling and one is fully implemented for a case where the logic does not need to differ.
Now a variant on this approach using the Strategy Pattern:
trait MessageHandlingStrategy{
def handleMessageA(a:MessageA)
def handleMessageB(b:MessageB) = {
//do handling here
}
def handleMessageC(c:MessageC)
}
class Strategy1 extends MessageHandlingStrategy{
def handleMessageA(a:MessageA) = {}
def handleMessageC(c:MessageC) = {}
}
class Strategy2 extends MessageHandlingStrategy{
def handleMessageA(a:MessageA) = {}
def handleMessageC(c:MessageC) = {}
}
class MyActor(strategy:MessageHandlingStrategy) extends Actor{
def receive = {
case a:MessageA =>
strategy.handleMessageA(a)
case b:MessageB =>
strategy.handleMessageB(b)
case c:MessageC =>
strategy.handleMessageC(c)
}
}
Here the approach is to pass in a strategy class during construction that defines the handling for a and c, with b again being handled the same regardless. The two approaches are pretty similar and accomplish the same goal. The last approach uses partial function chaining and could look like this:
trait MessageAHandling{
self: Actor =>
def handleA1:Receive = {
case a:MessageA => //handle way 1
}
def handleA2:Receive = {
case a:MessageA => //handle way 2
}
}
trait MessageBHandling{
self: Actor =>
def handleB:Receive = {
case b:MessageB => //handle b
}
}
trait MessageCHandling{
self: Actor =>
def handleC1:Receive = {
case c:MessageC => //handle way 1
}
def handleC2:Receive = {
case c:MessageC => //handle way 2
}
}
class MyActor1 extends Actor with MessageAHandling with MessageBHandling with MessageCHandling{
def receive = handleA1 orElse handleB orElse handleC1
}
class MyActor2 extends Actor with MessageAHandling with MessageBHandling with MessageCHandling{
def receive = handleA2 orElse handleB orElse handleC2
}
Here, some traits are setup that define message handling behaviors for the 3 message types. The concrete actors mix in those traits and then pick which behaviors they want when building out their receive
function by using partial function chaining.
There are probably many other ways to do what you seek, but I just figured I'd throw a few options out there for you. Hope it helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With