I just started learning Akka/Scala, and I wrote a small chat server.
Imagine this is a room-based chat server, everyone can create their own room and can be in several rooms simultaneously. Whenever a room is running out of members, the room shuts down. A room is identified by a id: Int
, and has an immutable name: String
. I wrote the following code to present the room.
class Room(val id: Int, val name: String, var host: ActorRef) extends Actor {
def receive = {
case GetId() =>
sender ! id
case GetName() =>
sender ! name
case AddMember(member) => ...
case RemoveMember(member) => ...
case BroadcastMessage(from, message) => ...
}
Now, a client needs the ids and names of all the rooms in order to decide which room to join.
val rooms: List[ActorRef] // Obtained somewhere
val getIdFutures: List[Future[Int]] = rooms.map { (_ ? GetId()).mapTo[Int] }
val getNameFutures: List[Future[String]] = rooms.map { (_ ? GetName()).mapTo[String] }
val getIds: Future[List[Int]] = Future.sequence(getIdFutures)
val getNames: Future[List[String]] = Future.sequence(getNameFutures)
for (ids <- getIds; names <- getNames) yield {
ids zip names map { pair =>
val id = pair._1
val name = pair._2
println(s"$id: $name")
}
}
Well, ok, it works... but... are there any ways for me to access those immutable members inside an Actor more conveniently? I tried to make a wrapper for the room actor, like the code below:
case class RoomWrapper(val id: Int, val name: String, actor: ActorRef)
Seems good, but there is a problem: Now I have to pass the RoomWrapper
object everywhere. How can I get notified when the room is destroyed? I can't context.watch
a RoomWrapper
!
How to solve this? Is there a possibility for me to write like this?
val rooms: List[ActorRef]
rooms map { room =>
println(room.id)
println(room.name)
}
Well, this is just my opinion. I don't think you should do as you propose, since that would "diversify" your code (I mean, out of the actor model into a more specific stuff). Technically speaking, the actors should never share any state, and moreover, they should only react to events (messages). In any case, using for comprehensions you can rewrite the above as:
for {
room <- rooms
id <- (room ? GetId).mapTo[Int]
name <- (room ? GetName).mapTo[String]
} {
println(id)
println(name)
}
Moreover, you could make a message which returns both id and name as a tuple, and so on. The possibilities are infinite, but I would not allow to access directly even the immutable state in that way, since it clutters a bit my vision of what I'm coding getting into specific patterns of the application.