Search code examples
scalaakkaactorakka-typed

Exploring Akka Typed Actors Functional Vs Object Oriented Way


I had worked on Akka actors (non typed), recently started to write Akka typed actors, and found two ways of achieving one were the functional way and another was the object-oriented way (similar to old).

I was interested in understanding the encapsulation of state in both functional and object-oriented way. So wrote a method for the functional way and a class for the object-oriented way.

Functional Way:


def webSocketConnections(l: List[ActorRef[Message]] = List.empty): Behavior[WebSocketMsg] = {
    Behaviors.receive[WebSocketMsg] {
      (context, message) => {
        message match {
          case Model.UserAdded(actorRef) => webSocketConnections(actorRef :: l)
          case Model.BroadcastToAll(msg) =>
            context.spawnAnonymous(broadCastActorBehaviour) ! Broadcast(l, msg)
            Behaviors.same
        }
      }
    }
  }

Object-Oriented Way

class WebSocketConnectionMaintainer(actorContext: ActorContext[WebSocketMsg]) extends
  AbstractBehavior[WebSocketMsg](actorContext) {
  private var actorRefL: List[ActorRef[Message]] = List.empty

  override def onMessage(msg: WebSocketMsg): Behavior[WebSocketMsg] = {
    msg match {
      case Model.UserAdded(actorRef) =>
        actorRefL =  actorRef :: actorRefL
        Behaviors.same
      case Model.BroadcastToAll(msg) =>
        actorContext.spawnAnonymous(broadCastActorBehaviour) ! Broadcast(actorRefL, msg)
        Behaviors.same
    }
  }
}

If you observe both the cases, in the case of functional way the state gets encapsulated as a param, not sure whether it can have issues with respect to stack safety, as it can crash due to stack overflow error after a lot of calls right?

But if you observe the object-oriented way, it's just mutating the list directly which won't cause any stack overflow issue.

And another problem that I was finding is, I can only use context.spawn for spawning a functional way actor, but in the case of object-oriented, I need a context of a particular type. What to do if I want to create both functional and object-oriented actors together?


Solution

  • Regarding the stack safety concern, the short answer is that the apparent recursion is trampolined, so it won't blow the stack. See this StackOverflow question.

    For spawning an OO-defined typed actor, you use Behaviors.setup which injects the context:

    def ooBehavior: Behavior[WebSocketMsg] = Behaviors.setup { ctx =>
      new WebSocketConnectionMaintainer(ctx)
    }
    context.spawn(ooBehavior)