ddding tools = akka persistence

Post on 08-May-2015

3.523 Views

Category:

Technology

5 Downloads

Preview:

Click to see full reader

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

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

top related