Home > Enterprise >  How to store data in Akka Actor Object fields?
How to store data in Akka Actor Object fields?

Time:03-10

How can I use a mutable Map in an Actor object to store values depending on the messages? My code

object TestActor {
   final case class Command()
   private val mmap: mutable.Map[Int,Map[Int,Int]] = mutable.Map[Int,Map[Int,Int]]()
   
   def apply(): Behavior[Command] = Behaviors.receive {
       case (context, Command()) => 
            mmap  = (1 -> (2 -> 3)) 
            context.log.info(nmap)
   }
}

This causes problems since the mmap is static as it is a field in a scala object. So when multiple threads add some values to the map, it would result in strange values. Since I still need to use typed akka actors, is there a way to have a field in an object (or in some other way) to make it possible for me to still use objects to represent the actors using akka.typed and still have fields that are not shared among the TestActor actors?

CodePudding user response:

In Akka Typed's functional API (which this example uses), the typical way to do this would be along these lines:

object TestActor {
  final case class Command()

  def apply(): Behavior[Command] =
    fromMap(mutable.Map.empty)

  private def fromMap(mmap: mutable.Map[Int, Map[Int, Int]]): Behavior[Command] =
    Behaviors.receive {
      case (context, Command()) =>
        mmap  = (1 -> Map(2 -> 3))
        context.log.info(mmap)
        Behaviors.same
    }
}

Here, the object is basically serving as just a module and the fields of that object are better thought of as globals.

Note that there is also an object-oriented API for defining typed actors, which in Scala would be:

object TestActor {
  final case class Command()

  def apply(): Behavior[TestActor.Command] =
    Behaviors.setup { context =>
      new TestActor(context)
    }
}

class TestActor(context: ActorContext[TestActor.Command]) extends AbstractBehavior[TestActor.Command](context) {
  val mmap: mutable.Map[Int, Map[Int, Int]] = mutable.Map.empty

  def onMessage(msg: TestActor.Command): Behavior[TestActor.Command] = {
    mmap  = (1 -> Map(2, 3))
    context.log.info(mmap)
    this
  }
}

It's worth noting that in Akka, it's generally better to have vars of immutable data (i.e. mutable containers holding immutable values) than vals of mutable data (i.e. immutable containers holding mutable values), as that can expose a channel for something outside the actor (including another actor) to "pull the rug out from under" an actor.

In the functional API, this would typically take a form like:

object TestActor {
  final case class Command()

  def apply(): Behavior[Command] =
    fromMap(Map.empty)

  private def fromMap(map: Map[Int, Map[Int, Int]]): Behavior[Command] =
    Behaviors.receive {
      case (context, Command()) =>
        val nextMap = map   (1 -> Map(2 -> 3))
        context.log.info(nextMap)
        withMap(nextMap)
    }
}

And in the OO Scala API:

// TestActor companion object is as before

class TestActor(context: ActorContext[TestActor.Command]) extends AbstractBehavior[TestActor.Command](context) {
  var map: Map[Int, Map[Int, Int]] = Map.empty

  def onMessage(msg: TestActor.Command): Behavior[TestActor.Command] = {
    map = map   (1 -> Map(2 -> 3))
    context.log.info(map)
    this
  }
}
  • Related