ale64bit
ale64bit

Reputation: 6242

How to persist behavior in Akka Persistence?

I have a PersistentActor which, for a given event, can mutate his state and/or his behavior. For persisting the state there's no problem, but it turns out that I need to persist the behavior also as part of the state, otherwise it won't be correctly initialized in case of a failure recovery from a snapshot, since the snapshot only carries the state and not the behavior. How to achieve this correctly?

Currently, what I do is to save a tuple of the state and the behavior, but I don't know if this is the correct way to do it. I'm open to any kind of suggestions and ideas. FWIW, the use case of this kind of actor is as an entry for cluster sharding, where I need each entry to go through several states using (become/unbecome) and persist the state of the entries as well as the behavior they were in. Thanks a lot.

Here is the code illustrating the problem:

class FooActor extends PersistentActor with ActorLogging {
  import FooActor._

  var state: List[String] = Nil

  def updateState(e: FooEvent): Unit = e match {
    case Initialized   ⇒ context become initialized
    case Uninitialized ⇒ context become uninitialized
    case Fooed(data)   ⇒ state = data :: state
  }

  def uninitialized: Receive = {
    case Init      ⇒ persist(Initialized)(updateState)
  }

  def initialized: Receive = {
    case Foo(data) ⇒ persist(Fooed(data))(updateState)
    case Uninit    ⇒ persist(Uninitialized)(updateState)
    case Snap      ⇒ saveSnapshot((state, receiveCommand)) // how to persist current behavior also?
  }

  def receiveRecover: Receive = {
    case e: FooEvent                              ⇒ updateState(e)
    // here, if I don't persist the behavior also, when the state is
    // recovered, the actor would be in the uninitialized behavior and 
    // thus will not process any Foo commands
    case SnapshotOffer(_, (_state: List[String], behavior: Receive)) ⇒
      state = _state
      context become behavior
  }

  def receiveCommand: Receive = uninitialized

  val persistenceId = "foo-pid"
}

object FooActor {
  case object Init
  case object Uninit
  case object Snap
  case class Foo(data: String)
  trait FooEvent
  case object Initialized extends FooEvent
  case object Uninitialized extends FooEvent
  case class Fooed(data: String) extends FooEvent
}

Upvotes: 0

Views: 891

Answers (1)

Diego Martinoia
Diego Martinoia

Reputation: 4652

What you are doing looks good, just I think in general you should first change your behavior during recover and then update the state (it's not the case here, but there maybe some validation constraints in general on your state).

The only other option I can think of is to override the onshutdown handler and guarantee that you unitialized yourself and save a snapshot before going down, as well as an init message. In this way you are guaranteed that on respawn you are uninitialized, and will receive the init message. Something like

onShutdown {
  context become uninit
  saveSnapshot(state)
  persist(Init)
}

On restart, your uninit snapshot would receive first of all an init message, and then whatever comes next.

Though I'm not sure it's much better, you should be able to avoid storing the behavior, but then you'd have to be careful never to die without doing that.

Upvotes: 1

Related Questions