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.
10. 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
11. 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
12. Compared to “Good ol’ CRUD Model”
state
“Mutable Record”
state
=
apply(es)
“Series of Events”
29. 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
consistent
consistent
eventually consistent
47. 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)
48. 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)
49. 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!
50. 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
51. 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!
!
case notPersistentMsg =>!
// msg not persisted - like in normal Actor!
count += 1!
}!
}
52. 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!)
53. 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)
56. 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
57. var state = S0
!
def processorId = “a”
!
EventsourcedProcessor
Command
!
!
Journal
73. Eventsourced, recovery
/** MUST NOT SIDE-EFFECT! */!
def receiveRecover = {!
case replayedEvent: Event => !
updateState(replayedEvent)!
}
exact same code for all events!
90. 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!