Reputation: 18639
This is an continuation of my previous question How do I get around type erasure on Akka receive method
I have 10 type of events which extends from Event that I need to handle.
I want to implement business logic for each event in separate trait, because because mixing all 10 event handler functions will produce several hundreds(if not thousands) lines of code.
I don't want to create different Actor types for each event. For example:
class Event1Actor extend Actor{
def receive ={
case Event1(e) => //event1 Business Logic
}
}
class Event2Actor extend Actor{
def receive ={
case Event2(e) => //event2 Business Logic
}
}
and the same Event3Actor, Event4Actor,etc....
Such code seems ugly to me, because I need to implement business Logic inside each Actor.
Implementing 10 different traits and 10 different Actor classes seems also as bad design.
I'm seeking for some kind generic solution based on design pattern, for example strategy pattern.
Upvotes: 6
Views: 633
Reputation: 35443
You could try something like this, which involves auto-composing receive functionality via a base trait. First the code:
case class Event1(s:String)
case class Event2(i:Int)
case class Event3(f:Float)
trait EventHandlingActor extends Actor{
var handlers:List[Receive] = List.empty
override def preStart = {
val composedReceive = handlers.foldLeft(receive)((r,h) => r.orElse(h))
context.become(composedReceive)
}
def addHandler(r:Receive) {
handlers = r :: handlers
}
def receive = PartialFunction.empty[Any,Unit]
}
trait Event1Handling{ me:EventHandlingActor =>
addHandler{
case Event1(s) => println(s"${self.path.name} handling event1: $s")
}
}
trait Event2Handling{ me:EventHandlingActor =>
addHandler{
case Event2(i) => println(s"${self.path.name} handling event2: $i")
}
}
trait Event3Handling{ me:EventHandlingActor =>
addHandler{
case Event3(f) => println(s"${self.path.name} handling event3: $f")
}
}
So you can see in the EventHandlingActor
trait we set up a List
of type Receive
that can be added to by each specific handling trait that we stack into a concrete actor. Then you can see the definitions of the handling functionality for each event defined in a separate trait that is calling addHandler
to add another piece of handling functionality. In preStart
for any kind of EventHandlingActor
impl the receive functions will be composed together with receive
being the starting point (empty by default) before hot-swapping out the receive impl with context.become
.
Now for a couple of impl actors as an example:
class MyEventHandlingActor extends EventHandlingActor
with Event1Handling with Event2Handling with Event3Handling
case class SomeOtherMessage(s:String)
class MyOtherEventHandlingActor extends EventHandlingActor with Event1Handling{
override def receive = {
case SomeOtherMessage(s) => println(s"otherHandler handling some other message: $s")
}
}
The first one only handles events, so all it needs to do is define which ones it handles my mixing in the appropriate traits. The second one handles one type of event but also some other message that is not an event. This class overrides the default empty receive and provides functionality to handle the non-event message.
If we tested the code like so:
val system = ActorSystem("test")
val handler = system.actorOf(Props[MyEventHandlingActor], "handler")
handler ! Event1("foo")
handler ! Event2(123)
handler ! Event3(123.456f)
val otherHandler = system.actorOf(Props[MyOtherEventHandlingActor], "otherHandler")
otherHandler ! Event1("bar")
otherHandler ! SomeOtherMessage("baz")
Then we would see output similar to this (with the order changing due to asynch handling of messages):
otherHandler handling event1: bar
handler handling event1: foo
handler handling event2: 123
handler handling event3: 123.456
Upvotes: 2
Reputation: 116
case class EventOperation[T <: Event](eventType: T)
class OperationActor extends Actor {
def receive = {
case EventOperation(eventType) => eventType.execute
}
}
trait Event {
def execute //implement execute in specific event class
}
class Event1 extends Event {/*execute implemented with business logic*/}
class Event2 extends Event {/*execute implemented with business logic*/}
hope this is what you are looking for and helps, I have used this patternt to remove the redundant amount of actors wrapping all actions under a single actor executing different type of events.
Upvotes: 3