Open Credo

October 28, 2015 | Software Consultancy

What’s new in Akka Persistence 2.4

Let’s have a quick look at the most interesting changes and new features that are now available to Akka users. As there are many new features to highlight in the new Akka release I will focus on those related to Akka Persistence first and cover other areas in a separate post.

WRITTEN BY

Rafal Gancarz

Rafal Gancarz

What’s new in Akka Persistence 2.4

Akka 2.4 released

Very recently the new version of Akka project has been released. Akka 2.4 ships with a number of new modules and lots of significant improvements over 2.3 release.

Let’s have a quick look at the most interesting changes and new features that are now available to Akka users. As there are many new features to highlight in the new Akka release I will focus on those related to Akka Persistence first and cover other areas in a separate post.

Stream based query API for Akka Persistence

Akka Persistence module has been using event sourcing approach to persistence for a while. Event sourcing involves storing the state of persistent entities as a sequences of state affecting events and is commonly used in conjunction with CQRS (Command/Query Responsibility Separation). In Akka Persistence events are stored in the event journal and get replayed to a PersistentActor when it recovers from a failure or is started after a controller application restart. Previously (in Akka 2.3), the only other way to read events from the journal was to use PersistentViews (which were limited to only access events for a single persistent entity) but in version 2.4 a new experimental module called akka-persistence-query-experimental provides another mechanism for reading events from the journal.

New query API allows using stream processing capabilities available in Akka Streams module to be used for querying the journal and retrieving streams of events. Below is a simple example that queries for all events associated with the specific persistent entity and prints them out to the console.

object StreamingJournalQueries {

  def main(args: Array[String]): Unit = {
    implicit val system = ActorSystem()
    implicit val materializer = ActorMaterializer()

    // create a ReadJournal for LevelDB journal backend
    val readJournal =
      PersistenceQuery(system).readJournalFor[LeveldbReadJournal](LeveldbReadJournal.Identifier)

    // create a query for all events associated with a specific persistent entity as a Source of events
    val source: Source[EventEnvelope, Unit] =
      readJournal.eventsByPersistenceId("entity-1234", 0, Long.MaxValue)

    // materialize the query with a Sink printing out events
    source.runForeach(envelope ⇒ println(envelope.event))
  }
}

The module defines a number of standard queries that journal backend implementers can support but remains pretty flexible to allow different backends to determine which standard queries can be supported. Journal backend implementation can also define additional custom queries beyond the few standard ones that include retrieving persistent entity identifiers, events for the specific persistence entity identifier or a specific tag.

Akka Persistence Query module comes with a ReadJournal implementation for a LevelDB backed event journal (the default event journal type supported by Akka Persistence). Other journal type will hopefully provide the support for the streaming query API fairly soon.

The new streaming Query API for Akka Persistence is notable as it provides a fairly easy way to move state changes from the command side of CQRS solution, which event journal is a part of, to the query side which would usually utilise a different persistence store that is optimised for efficient querying against the data model representing the latest view (snapshot) of the data.

Persistent FSM actors in Akka Persistence

Another welcome addition to Akka Persistence module is the support for persistent FSM (Finite State Machine) actors. FSM actors that help implementing state machine based processing flows on top of Akka now can be made persistent and have all state transitions stored in the event journal, which is a primary persistence mechanism offered by the Akka toolkit. This feature is still in the experimental stage and, as with other experimental modules and features, Akka team reserve the right to make API changes that break backward-compatibility.

There are a few differences between non-persistent and persistent FSM actors. For starters, persistent FSM actors need to model states by extending PersistentFSM.FSMState trait and also implement def identifier: String method returning a unique persistent identifier used to qualify events in the event journal.

The main difference compared to non-persistent FSM actors is how state transitions are handled. Instead of state modifications being expressed by replacing the state managed by the FSM actor with the new one, with persistent FSM actors state modifications are made by applying domain events which are then automatically persisted by the framework and replied to the actor when restarted (similarly to any PersistentActor). Below is a simple example from the documentation demonstrating this:

when(Shopping) {
  case Event(AddItem(item), _) ⇒
    stay applying ItemAdded(item) forMax (1 seconds)
  case Event(Buy, _) ⇒
    goto(Paid) applying OrderExecuted andThen {
      case NonEmptyShoppingCart(items) ⇒ reportActor ! PurchaseWasMade(items)
      case EmptyShoppingCart           ⇒ // do nothing...
    }
  ...
}

The code above illustrates how state transitions and applying domain events are expressed but it doesn’t modify the managed state. This is done in the applyEvent() method that should be overridden to take control over changes to the managed state.

    override def applyEvent(event: DomainEvent, cartBeforeEvent: ShoppingCart): ShoppingCart = {
      event match {
        case ItemAdded(item) ⇒ cartBeforeEvent.addItem(item)
        case OrderExecuted   ⇒ cartBeforeEvent
        case OrderDiscarded  ⇒ cartBeforeEvent.empty()
      }
    }

With this mechanism the FSM can be restored to a known state by applying the stream of domain events from the event journal. Persistent FSM can be considered a good approach when the business logic requires many or complex state transitions and the persistence of the workflow execution is also essential.

Event adapters in Akka Persistence

The final area I would like to cover involves dealing with event versioning or detaching event representation in the journal from the domain model used in the application utilising Akka Persistence. I believe this functionality has been added to the persistence module based on the feedback from the users because in any long running application using event sourcing it’s inevitable that events will need to evolve over time and certain use cases can’t be accommodated by the custom serialisation mechanism alone.

Now with event adapters it’s possible to completely separate the data model used to store events in the journal from the domain model used in the application itself. This additional mapping layer provides extra flexibility and allows to:

  • upcast/transform events – as events evolve it’s now possible to transform old event representation into newer ones
  • separate persistent and domain models – different data model can be used for journal storage and inside the application itself allowing using non-standard or specialised serialisation formats
  • use specialised data types – some journal backends offer more flexible or efficient data types that might be used in favour or more generic ones

There can be many event adapters defined and configured within the project using Akka Persistence. The configuration model allows specifying adapters that should be applied to a given event type and multiple adapters can be applied in sequence. EventAdapter trait defines methods used to transform events to and from the data model used for persisting them in the journal. One of the use cases for using event adapters is when previously defined course-grained event needs to be split into more multiple fine-grained events. Here is an example of such an adapter:

    trait V1
    trait V2
     
    // V1 event:
    final case class UserDetailsChanged(name: String, address: String) extends V1
     
    // corresponding V2 events:
    final case class UserNameChanged(name: String) extends V2
    final case class UserAddressChanged(address: String) extends V2
     
    // event splitting adapter:
    class UserEventsAdapter extends EventAdapter {
      override def manifest(event: Any): String = ""
     
      override def fromJournal(event: Any, manifest: String): EventSeq = event match {
        case UserDetailsChanged(null, address) ⇒ EventSeq(UserAddressChanged(address))
        case UserDetailsChanged(name, null)    ⇒ EventSeq(UserNameChanged(name))
        case UserDetailsChanged(name, address) ⇒
          EventSeq(UserNameChanged(name),
            UserAddressChanged(address))
        case event: V2 ⇒ EventSeq(event)
      }
     
      override def toJournal(event: Any): Any = event
    }

It’s worth pointing out event adapters are not the only mechanism that can be used for managing schema evolution in Akka Persistence. In fact, event adapters will most likely be used in conjunction with the serialisation framework provided by Akka. A dedicated documentation section (published as part of version 2.4 release) on schema evolution in Akka Persistence offers an excellent summary of best practices and recommendations for dealing with common situations, utilising both Akka serialisation framework and event adapters.

Final thoughts

With all these latest additions and improvements Akka Persistence appears to be maturing well as a comprehensive event sourcing based persistence framework (command side of CQRS solution). As some of the features described above are still in the experimental stage and API changes are to be potentially expected still but on a plus side the solutions applied can still be reviewed and improved in subsequent Akka releases so any feedback is greatly appreciated no doubt.

Next time

In the next post I will focus on reviewing other new capabilities released in Akka 2.4, particularly around Akka Typed and Akka Distributed Data modules. Stay tuned.

RETURN TO BLOG

SHARE

Twitter LinkedIn Facebook Email

SIMILAR POSTS

Blog