Search code examples
scalaakkaakka-persistence

Overcoming changes to persistent message classes in Akka Persistence


Let's say I start out with a Akka Persistence system like this:

case class MyMessage(x: Int)

class MyProcessor extends Processor {
  def receive = {
    case Persistent(m @ MyMessage) => m.x
    //...
  }
}

And then someday I change it to this:

case class MyMessage(x: Int, y: Int)

class MyProcessor extends Processor {
  def receive = {
    case Persistent(m @ MyMessage) => m.x + m.y
    //...
  } 
}

After I deploy my new system, when the instance of MyProcessor tries to restore its state, the journaled messages will be of the former case class. Because it is expecting the latter type, it will throw an OnReplayFailure, rendering the processor useless. Question is: if we were to assume an absent y can equal 0 (or whatever) is there a best practice to overcome this? For example, perhaps using an implicit to convert from the former message to the latter on recovery?


Solution

  • Akka uses Java serialization by default and says that for a long-term project we should use a proper alternative. This is because Java serialization is very difficult to evolve over time. Akka recommends using either Google Protocol Buffers, Apache Thrift or Apache Avro.

    With Google Protocol Buffers, for example, in your case you'd be writing something like:

    if (p.hasY) p.getY else 0
    

    Akka explains all that in a nice article (admittedly it's not very Google-able):

    http://doc.akka.io/docs/akka/current/scala/persistence-schema-evolution.html

    and even explains your particular use case of adding a new field to an existing message type:

    http://doc.akka.io/docs/akka/current/scala/persistence-schema-evolution.html#Add_fields

    A blog post the Akka documentation recommends for a comparison of the different serialization toolkits:

    http://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html