akka-persistence prototype

The most prominent changes compared to eventsourced are:

- No central processor and channel registry any more
- Auto-recovery of processors on start and restart (can be disabled)
- Recovery of processor networks doesn't require coordination
- Explicit channel activation not needed any more
- Message sequence numbers generated per processor (no gaps)
- Sender references are journaled along with messages
- Processors can determine their recovery status
- No custom API on extension object, only messages
- Journal created by extension from config, not by application
- Applications only interact with processors and channels via messages
- Internal design prepared for having processor-specific journal actors (for later optimization possibilities)

Further additions and changes during review:

- Allow processor implementation classes to use inherited stash
- Channel support to resolve (potentially invalid) sender references
- Logical intead of physical deletion of messages
- Pinned dispatcher for LevelDB journal
- Processor can handle failures during recovery
- Message renamed to Persistent

This prototype has the following limitations:

- Serialization of persistent messages and their payload via JavaSerializer only (will be configurable later)
- The LevelDB journal implementation based on a LevelDB Java port, not the native LevelDB (will be configurable later)

The following features will be added later using separate tickets:

- Snapshot-based recovery
- Reliable channels
- Journal plugin API
- Optimizations
- ...
This commit is contained in:
Martin Krasser 2013-09-14 14:19:18 +02:00
parent 1187fecfcc
commit cdeea924ff
28 changed files with 3119 additions and 8 deletions

View file

@ -0,0 +1,187 @@
package docs.persistence
import akka.actor.ActorSystem
import akka.persistence.{ Recover, Persistent, Processor }
import akka.testkit.{ ImplicitSender, AkkaSpec }
trait PersistenceDocSpec {
val system: ActorSystem
val config =
"""
//#config
akka.persistence.journal.leveldb.dir = "target/journal"
//#config
"""
import system._
new AnyRef {
//#definition
import akka.persistence.{ Persistent, Processor }
class MyProcessor extends Processor {
def receive = {
case Persistent(payload, sequenceNr) // message has been written to journal
case other // message has not been written to journal
}
}
//#definition
//#usage
import akka.actor.Props
val processor = actorOf(Props[MyProcessor], name = "myProcessor")
processor ! Persistent("foo") // will be journaled
processor ! "bar" // will not be journaled
//#usage
//#recover-explicit
processor ! Recover()
//#recover-explicit
}
new AnyRef {
trait MyProcessor1 extends Processor {
//#recover-on-start-disabled
override def preStartProcessor() = ()
//#recover-on-start-disabled
//#recover-on-restart-disabled
override def preRestartProcessor(reason: Throwable, message: Option[Any]) = ()
//#recover-on-restart-disabled
}
trait MyProcessor2 extends Processor {
//#recover-on-start-custom
override def preStartProcessor() {
self ! Recover(toSequenceNr = 457L)
}
//#recover-on-start-custom
}
trait MyProcessor3 extends Processor {
//#deletion
override def preRestartProcessor(reason: Throwable, message: Option[Any]) {
message match {
case Some(p: Persistent) delete(p)
case _
}
super.preRestartProcessor(reason, message)
}
//#deletion
}
}
new AnyRef {
trait ProcessorMethods {
//#processor-id
def processorId: String
//#processor-id
//#recovery-status
def recoveryRunning: Boolean
def recoveryFinished: Boolean
//#recovery-status
//#current-message
implicit def currentPersistentMessage: Option[Persistent]
//#current-message
}
class MyProcessor1 extends Processor with ProcessorMethods {
//#processor-id-override
override def processorId = "my-stable-processor-id"
//#processor-id-override
def receive = {
case _
}
}
}
new AnyRef {
//#channel-example
import akka.actor.{ Actor, Props }
import akka.persistence.{ Channel, Deliver, Persistent, Processor }
class MyProcessor extends Processor {
val destination = context.actorOf(Props[MyDestination])
val channel = context.actorOf(Channel.props(), name = "myChannel")
def receive = {
case p @ Persistent(payload, _) {
channel ! Deliver(p.withPayload(s"processed ${payload}"), destination)
}
}
}
class MyDestination extends Actor {
def receive = {
case p @ Persistent(payload, _) {
println(s"received ${payload}")
p.confirm()
}
}
}
//#channel-example
class MyProcessor2 extends Processor {
import akka.persistence.Resolve
val destination = context.actorOf(Props[MyDestination])
val channel =
//#channel-id-override
context.actorOf(Channel.props("my-stable-channel-id"))
//#channel-id-override
def receive = {
case p @ Persistent(payload, _) {
//#channel-example-reply
channel ! Deliver(p.withPayload(s"processed ${payload}"), sender)
//#channel-example-reply
//#resolve-destination
channel ! Deliver(p, sender, Resolve.Destination)
//#resolve-destination
//#resolve-sender
channel forward Deliver(p, destination, Resolve.Sender)
//#resolve-sender
}
}
}
class MyProcessor3 extends Processor {
def receive = {
//#payload-pattern-matching
case Persistent(payload, _)
//#payload-pattern-matching
}
}
class MyProcessor4 extends Processor {
def receive = {
//#sequence-nr-pattern-matching
case Persistent(_, sequenceNr)
//#sequence-nr-pattern-matching
}
}
}
new AnyRef {
//#fsm-example
import akka.actor.FSM
import akka.persistence.{ Processor, Persistent }
class PersistentDoor extends Processor with FSM[String, Int] {
startWith("closed", 0)
when("closed") {
case Event(Persistent("open", _), counter) {
goto("open") using (counter + 1) replying (counter)
}
}
when("open") {
case Event(Persistent("close", _), counter) {
goto("closed") using (counter + 1) replying (counter)
}
}
}
//#fsm-example
}
}