2

For reference, this question has roots from Scala method performance (collect or foreach or other) looping through sockets?

I'm storing a reference to a websocket inside an actor, and then subscribing that actor to an Akka EventStream:

val socketActor = system.actorOf(Props(new Actor {
  val socket = WebSocketConnection

  def receive = {
    case d: AppMessage ⇒ socket.send(d)
  }
}))
system.eventStream.subscribe(socketActor, classOf[AppMessage])

What bugs me is that the only classifier I can make with an EventStream is a class type. So if you want to route messages to different actors, say based on userId, do you need to create multiple EventStreams and manually build an EventBus or is there something here that I am missing?

It would be nice if I could do something simply like:

system.eventStream.subscribe(socketActor, Map("userId" -> userId, "teamId" -> teamId) )

This may be simply a conceptual issue, as I'm not quite sure what EventStream represents.

Community
  • 1
  • 1
crockpotveggies
  • 12,682
  • 12
  • 70
  • 140

2 Answers2

3

This was my solution based on the ActorEventBus based on this Gist: https://gist.github.com/3757237

I found this to be a lot more maintainable than dealing with EventStreams. Perhaps multiple EventStreams will be needed in the future, but at this point it readily supports current infrastructure.

MessageBus

First, the MessageBus that handles the outgoing messages to sockets wrapped in actors, based on PubSub channels:

case class MessageEvent(val channel:String, val message:String)

/**
 * message bus to route messages to their appropriate contexts
 */
class MessageBus extends ActorEventBus with LookupClassification {

    type Event = MessageEvent
  type Classifier = String

  protected def mapSize(): Int = {
    10
  }

  protected def classify(event: Event): Classifier = {
    event.channel
  }

  protected def publish(event: Event, subscriber: Subscriber): Unit = {
    subscriber ! event
  }

}


object MessageBus {

  val actorSystem = ActorSystem("contexts")
  val Bus = new MessageBus

  /**
   * create an actor that stores a browser socket
   */
  def browserSocketContext(s: WebSocketConnection, userId: Long, teamId: Long) = {
    val subscriber = actorSystem.actorOf(Props(new BrowserSocket(s,userId,teamId)))

    Bus.subscribe( subscriber, "/app/socket/%s" format s.toString)
    Bus.subscribe( subscriber, "/app/browser/u/%s" format userId )
    Bus.subscribe( subscriber, "/app/browser/t/%s" format teamId )
    Bus.subscribe( subscriber, "/app/browser" )
  }
}

Socket Access with Actors

Here's the actor that actually contains the socket:

/**
 * actor wrapping access for browser socket
 */
class BrowserSocket(
  val s: WebSocketConnection,
  val userId: Long,
  val teamId: Long

) extends Actor {

  def receive = {
    case payload:MessageEvent => 
      s.send(payload.message)

    case ping:MessagePing =>
      s.ping(ping.data)

  }

}
crockpotveggies
  • 12,682
  • 12
  • 70
  • 140
1

EventStreams and the Event Bus is for logging and monitoring as far as I know. You usually build the required functionality using Actors and passing messages between them.

So you send the AppMessage to a custom router actor who will sort out which backing actor to send to. Maybe the router can spawn backing actors if it sees fit, or the actors can subscribe at the router (via passing appropriate messages). This mainly depends on the logic you need to implement.

ron
  • 9,262
  • 4
  • 40
  • 73