ddding tools = akka persistence
DESCRIPTION
Event sourcing and Domain Driven Design are techniques that allow you to model your business more truthfully - by expressing it via commands, events and aggregates etc. The new akka-persistence module, included in Akka since the 2.3 release is aimed at easing implementing event sourced applications. Turns out the actor model and events as messages fit in here perfectly. During this session we'll discover how to build reactive, event sourcing based apps using the new abstractions provided, and investigate how to implement your own journals to back these persistent event sourced actors.TRANSCRIPT
DDDing with Akka Persistence !
Konrad 'ktoso' Malawski GeeCON 2014 @ Kraków, PL
Konrad `@ktosopl` Malawski
Konrad `@ktosopl` Malawski
hAkker @
Konrad `@ktosopl` Malawski
typesafe.com geecon.org
Java.pl / KrakowScala.pl sckrk.com / meetup.com/Paper-Cup @ London
GDGKrakow.pl meetup.com/Lambda-Lounge-Krakow
hAkker @
mainly by Martin Krasser !!(as contractor for Typesafe) !inspired by:
https://github.com/krasserm
https://github.com/eligosource/eventsourced
akka-persistence
Dependencies
libraryDependencies ++= Seq(! "com.typesafe.akka" %% “akka-actor" % "2.3.1",! "com.typesafe.akka" %% "akka-persistence-experimental" % "2.3.1"!)
Show of hands!
Show of hands!
Show of hands!
Show of hands!
sourcing styles
Command Sourcing Event Sourcing
msg: DoThing
msg persisted before receive
imperative, “do the thing”
business logic change, can be reflected in reaction
Processor
sourcing styles
Command Sourcing Event Sourcing
msg: DoThing msg: ThingDone
msg persisted before receive commands converted to events, must be manually persisted
imperative, “do the thing” past tense, “happened”
business logic change, can be reflected in reaction
business logic change, won’t change previous events
Processor EventsourcedProcessor
Compared to “Good ol’ CRUD Model”
state
“Mutable Record”
state =
apply(es)
“Series of Events”
Actors
count: 0 !
!
Actor
An Actor that keeps count of messages it processed !
Let’s send 2 messages to it (it’s “commands”)
Actor
!!class Counter extends Actor {! var count = 0! def receive = {! case _ => count += 1! }!}
count: 0 !
!
Actor
count: 0 !
!
Actor
count: 1 !
!
Actor
crash!
Actor
crash!
Actor
restart
count: 0 !
!
Actor
restart
count: 0 !
!
Actor
restarted
count: 1 !
!
Actor
restarted
count: 1 !
!wrong!
expected count == 2!
Actor
restarted
Consistency Boundary
Consistency Boundary equals Async Boundary
Boundaries
actor
actor
async
Boundaries
aggregate
aggregate
“eventual”
A.K.A. async
Business rules
aggregate
Any rule that spans AGGREGATES will not be expected to be up-to-date at all times. Through event processing, batch processing, or
other update mechanisms, other dependencies can be resolved within some specific time. [Evans, p. 128]
aggregate
consistentconsistent
eventually consistent
Let’s open the toolbox
Processor
Processor (command sourcing)
var count = 0 !
def processorId = “a” !
Journal (DB)
!
!
!
Processor
Journal (DB)
!
!
!
Processor
var count = 0 !
def processorId = “a” !
Journal (DB)
!
!
!
Processor
var count = 0 !
def processorId = “a” !
Journal (DB)
!
!
!
Processor
var count = 0 !
def processorId = “a” !
Journal (DB)
!
!
!
Processor
var count = 1 !
def processorId = “a” !
Journal (DB)
!
!
!
Processor
var count = 1 !
def processorId = “a” !
crash!
Journal (DB)
!
!
!
Processor
restart
Journal (DB)
!
!
!
Processor
var count = 0 !
def processorId = “a” !
restart
Journal (DB)
!
!
!
Processor
var count = 0 !
def processorId = “a” !
replay!
restart
Journal (DB)
!
!
!
Processor
var count = 0 !
def processorId = “a” !
replay!
Journal (DB)
!
!
!
Processor
var count = 1 !
def processorId = “a” !
replay!
Journal (DB)
!
!
!
Processor
var count = 1 !
def processorId = “a” !
Journal (DB)
!
!
!
Processor
var count = 2 !
def processorId = “a” !
yay!
Processor
import akka.persistence._!!class CounterProcessor extends Processor {! var count = 0! override val processorId = "counter"!! def receive = {! case Persistent(payload, seqNr) =>! // payload already persisted! count += 1! }!}
Processor
import akka.persistence._!!class CounterProcessor extends Processor {! var count = 0! override val processorId = "counter"!! def receive = {! case Persistent(payload, seqNr) =>! // payload already persisted! count += 1! }!}
counter ! Persistent(payload)
Processor
import akka.persistence._!!class CounterProcessor extends Processor {! var count = 0! override val processorId = "counter"!! def receive = {! case Persistent(payload, seqNr) =>! // payload already persisted! count += 1! }!}
counter ! Persistent(payload)
Processor
import akka.persistence._!!class CounterProcessor extends Processor {! var count = 0! override val processorId = "counter"!! def receive = {! case Persistent(payload, seqNr) =>! // payload already persisted! count += 1! }!}
counter ! Persistent(payload)
sequenceNr (generated by akka)
is already persisted!
Processor
import akka.persistence._!!class CounterProcessor extends Processor {! var count = 0! override val processorId = "counter"!! def receive = {! case notPersisted: Event =>! // will not replay this msg!! count += 1! }!}
counter ! payload
won’t persist
won’t replay
Processorimport akka.persistence._!!class CounterProcessor extends Processor {! var count = 0! override val processorId = "counter"!! def receive = {! case Persistent(payload, seqNr) =>! // payload already persisted! count += 1!! case notPersistentMsg =>! // msg not persisted - like in normal Actor! count += 1! }!}
Processor
Upsides
• Persistent Command Sourcing “out of the box”
• Pretty simple, persist handled for you
• Once you get the msg, it’s persisted
• Pluggable Journals (HBase, Cassandra, Mongo, …)
• Can replay to given seqNr (post-mortem etc!)
Processor
Downsides
• Exposes Persistent() to Actors who talk to you
• No room for validation before persisting
• There’s one Model, we act on the incoming msg
• Lower throughput than plain Actor (limited by DB)
Eventsourced Processor
Eventsourced Processor (event sourcing)
super quick domain modeling!
sealed trait Command!case class GiveMe(geeCoins: Int) extends Command!case class TakeMy(geeCoins: Int) extends Command
Commands - what others “tell” us; not persisted
case class Wallet(geeCoins: Int) {! def updated(diff: Int) = State(geeCoins + diff)!}
State - reflection of a series of events
sealed trait Event!case class BalanceChangedBy(geeCoins: Int) extends Event!
Events - reflect effects, past tense; persisted
var state = S0 !
def processorId = “a” !
EventsourcedProcessor
Command
!
!
Journal
EventsourcedProcessor
var state = S0 !
def processorId = “a” !
!
!
Journal
Generate Events
EventsourcedProcessor
var state = S0 !
def processorId = “a” !
!
!
Journal
Generate Events
E1
EventsourcedProcessor
ACK “persisted”
!
!
Journal
E1
var state = S0 !
def processorId = “a” !
EventsourcedProcessor
“Apply” event
!
!
Journal
E1
var state = S0 !
def processorId = “a” !
E1
EventsourcedProcessor
!
!
Journal
E1
var state = S0 !
def processorId = “a” !
E1
Okey!
EventsourcedProcessor
!
!
Journal
E1
var state = S0 !
def processorId = “a” !
E1
Okey!
EventsourcedProcessor
!
!
Journal
E1
var state = S0 !
def processorId = “a” !
E1
Ok, he got my $.
EventsourcedProcessor
class GeeCoinWallet extends EventsourcedProcessor {!! var state = Wallet(geeCoins = 0)!! def updateState(e: Event): State = {! case BalanceChangedBy(geeCoins) => state updated geeCoins! }! ! // API:!! def receiveCommand = ??? // TODO!! def receiveRecover = ??? // TODO!!}!
EventsourcedProcessor
def receiveCommand = {!! case TakeMy(geeCoins) =>! persist(BalanceChangedBy(geeCoins)) { changed =>! state = updateState(changed) ! }!!!!!!!}
async callback
EventsourcedProcessor
def receiveCommand = {!!!!!!! case GiveMe(geeCoins) if geeCoins <= state.geeCoins =>! persist(BalanceChangedBy(-geeCoins)) { changed =>! state = updateState(changed) ! sender() ! TakeMy(-geeCoins)! }!}
Safe to access sender here
async callback
EventsourcedProcessor
def receiveCommand = {!!!!!!! case GiveMe(geeCoins) if geeCoins <= state.geeCoins =>! persist(BalanceChangedBy(-geeCoins)) { changed =>! state = updateState(changed) ! sender() ! TakeMy(-geeCoins)! }!}
Is this safe? It’s async! Races?!
Ordering guarantees
!
!
Journal
E1
var state = S0 !
def processorId = “a” !
C1C2
C3
Ordering guarantees
!
!
Journal
E1
var state = S0 !
def processorId = “a” !
C1C2
C3
Commands get “stashed” until processing C1’s events are acted upon.
!
!
Journal
Ordering guarantees
var state = S0 !
def processorId = “a” !
C1C2
C3 E1
E2
E2E1
events get applied in-order
C2
!
!
Journal
Ordering guarantees
var state = S0 !
def processorId = “a” !
C3 E1 E2
E2E1
and the cycle repeats
Eventsourced, recovery
/** MUST NOT SIDE-EFFECT! */!def receiveRecover = {! case replayedEvent: Event => ! updateState(replayedEvent)!}
exact same code for all events!
Snapshots
Snapshots (in SnapshotStore)
Eventsourced, snapshots
def receiveCommand = {! case command: Command =>! saveSnapshot(state) // async!!}
/** MUST NOT SIDE-EFFECT! */!def receiveRecover = {! case SnapshotOffer(meta, snapshot: State) => ! this.state = state!! case replayedEvent: Event => ! updateState(replayedEvent)!}
snapshot!? how?
…sum of states…
Snapshots
!
!
Journal
E1 E2 E3 E4
E5 E6 E7 E8
state until [E8]
Snapshots
S8
!
!
Snapshot Store
snapshot!
!
!
Journal
E1 E2 E3 E4
E5 E6 E7 E8
state until [E8]
Snapshots
S8
!
!
Snapshot Store
!
!
Journal
E1 E2 E3 E4
E5 E6 E7 E8
S8
crash!
Snapshots
!
!
Snapshot Store
!
!
Journal
E1 E2 E3 E4
E5 E6 E7 E8
S8
crash!
“bring me up-to-date!”
Snapshots
!
!
Snapshot Store
!
!
Journal
E1 E2 E3 E4
E5 E6 E7 E8
S8
restart!replay!
“bring me up-to-date!”
Snapshots
!
!
Snapshot Store
S8
restart!replay!
S8!
!
Journal
E1 E2 E3 E4
E5 E6 E7 E8
state until [E8]
Snapshots
!
!
Snapshot Store
S8
restart!replay!
S8!
!
Journal
E1 E2 E3 E4
E5 E6 E7 E8
state until [E8]
Snapshots
!
!
Snapshot Store
S8
S8!
!
Journal
E1 E2 E3 E4
E5 E6 E7 E8
We could delete these!
trait MySummer extends Processor {! var nums: List[Int]! var total: Int! ! def receive = {! case "snap" => saveSnapshot(total)! case SaveSnapshotSuccess(metadata) => // ...! case SaveSnapshotFailure(metadata, reason) => // ...! }!}!
Snapshots, save
Async!
trait MySummer extends Processor {! var nums: List[Int]! var total: Int! ! def receive = {! case "snap" => saveSnapshot(total)! case SaveSnapshotSuccess(metadata) => // ...! case SaveSnapshotFailure(metadata, reason) => // ...! }!}!
Snapshots, success
final case class SnapshotMetadata(! processorId: String, sequenceNr: Long, ! timestamp: Long)
trait MySummer extends Processor {! var nums: List[Int]! var total: Int! ! def receive = {! case "snap" => saveSnapshot(total)! case SaveSnapshotSuccess(metadata) => // ...! case SaveSnapshotFailure(metadata, reason) => // ...! }!}!
Snapshots, success
Snapshot Recovery
class Counter extends Processor {! var total = 0! ! def receive = {! case SnapshotOffer(metadata, snap: Int) => ! total = snap!! case Persistent(payload, sequenceNr) => // ...! }!}
Snapshots
Upsides
• Simple!
• Faster recovery (!)
• Allows to delete “older” events
• “known state at point in time”
Snapshots
Downsides
• More logic to write
• Maybe not needed if events replay “fast enough”
• Possibly “yet another database” to pick
• snapshots are different than events, may be big!
Views
Journal (DB)
!
!
!
Views
!Processor
!def processorId = “a”
!
!View
!def processorId = “a”
!!!
pooling
Journal (DB)
!
!
!
Views
!Processor
!def processorId = “a”
!
!View
!def processorId = “a”
!!!
pooling
!View
!def processorId = “a”
!!!
pooling
different ActorPath, same processorId
View
class DoublingCounterProcessor extends View {! var state = 0! override val processorId = "counter"!! def receive = {! case Persistent(payload, seqNr) =>! // “state += 2 * payload” !! }!}
Akka Persistence Plugins
Plugins are Community maintained! (“not sure how production ready”)
http://akka.io/community/#journal_plugins
!
• Journals - Cassandra, HBase, Mongo …
• SnapshotStores - Cassandra, HDFS, HBase, Mongo …
SnapshotStore Plugins!
http://akka.io/community/#journal_plugins
Extra: Channels
Extras: Channel
Features:
• de-duplication
Extras: PersistentChannel
Features:
• “at least once delivery”
Try it now
Try it now() // !
typesafe.com/activator
akka-sample-persistence-scala
Links
• Official docs: http://doc.akka.io/docs/akka/2.3.0/scala/persistence.html
• Patrik’s Slides & Webinar: http://www.slideshare.net/patriknw/akka-persistence-webinar
• Papers:
• Sagas http://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf
• Life beyond Distributed Transactions: http://www-db.cs.wisc.edu/cidr/cidr2007/papers/cidr07p15.pdf
• Pics:
• http://misaspuppy.deviantart.com/art/Messenger-s-Cutie-Mark-A-Flying-Envelope-291040459
Mailing List
groups.google.com/forum/#!forum/akka-user
Links
Eric Evans: "The DDD book”
Talk: “Acknowledging CAP at the Root” !!!!!!
Vaughn Vernon’s Book and Talk !!
ktoso @ typesafe.com twitter: ktosopl github: ktoso blog: project13.pl team blog: letitcrash.com GeeCON 2014 @ Kraków, PL
!
Dzięki! Thanks! ありがとう! !
!
ping me:
©Typesafe 2014 – All Rights Reserved