event-sourced architectures with akka

81
Event-sourced architectures with Akka @Sander_Mak Luminis Technologies

Upload: sander-mak-sandermak

Post on 29-Nov-2014

2.777 views

Category:

Software


6 download

DESCRIPTION

Slides for my JavaOne 2014 talk event-sourced architectures with Akka. Discusses Akka Persistence as mechanism to do event-sourcing.

TRANSCRIPT

Page 1: Event-sourced architectures with Akka

Event-sourced architectures

with Akka

@Sander_Mak Luminis Technologies

Page 2: Event-sourced architectures with Akka

Today's journey

Event-sourcing

Actors

Akka Persistence

Design for ES

Page 3: Event-sourced architectures with Akka

Event-sourcing

Is all about getting the facts straight

Page 4: Event-sourced architectures with Akka

Typical 3 layer architectureUI/Client

Service layer

Database

fetch ↝ modify ↝ store

Page 5: Event-sourced architectures with Akka

Typical 3 layer architectureUI/Client

Service layer

Database

fetch ↝ modify ↝ store

Databases are shared mutable state

Page 6: Event-sourced architectures with Akka

Concert !

artist: String date: Date availableTickets: int price: int ...

TicketOrder !

noOfTickets: int userId: String

1 *

Typical entity modelling

Page 7: Event-sourced architectures with Akka

Typical entity modellingConcert

!

artist = Aerosmith availableTickets = 100 price = 10 ...

!

TicketOrder !

noOfTickets = 3 userId = 1

TicketOrder !

noOfTickets = 3 userId = 1

TicketOrder

noOfTickets = 3userId = 1

Page 8: Event-sourced architectures with Akka

Changing the price

Typical entity modellingConcert

!

artist = Aerosmith availableTickets = 100 price = 10 ...

!

TicketOrder !

noOfTickets = 3 userId = 1

TicketOrder !

noOfTickets = 3 userId = 1

TicketOrder

noOfTickets = 3userId = 1

Page 9: Event-sourced architectures with Akka

Changing the price

Typical entity modellingConcert

!

artist = Aerosmith availableTickets = 100 price = 10 ...

!

TicketOrder !

noOfTickets = 3 userId = 1

TicketOrder !

noOfTickets = 3 userId = 1

TicketOrder

noOfTickets = 3userId = 1

✘100

Page 10: Event-sourced architectures with Akka

Canceling an order

Typical entity modellingConcert

!

artist = Aerosmith availableTickets = 100 price = 10 ...

!

TicketOrder !

noOfTickets = 3 userId = 1

TicketOrder !

noOfTickets = 3 userId = 1

TicketOrder

noOfTickets = 3userId = 1

Page 11: Event-sourced architectures with Akka

Canceling an order

Typical entity modellingConcert

!

artist = Aerosmith availableTickets = 100 price = 10 ...

!

TicketOrder !

noOfTickets = 3 userId = 1

TicketOrder !

noOfTickets = 3 userId = 1

Page 12: Event-sourced architectures with Akka

Congratulations, you are !

LOSING DATA EVERY DAY

Update or delete statements in your app?

Page 13: Event-sourced architectures with Akka

Event-sourced modellingConcertCreated

!

artist = Aerosmith availableTickets = 100 price = 10 ...

!

TicketsOrdered !

noOfTickets = 3 userId = 1

TicketsOrdered !

noOfTickets = 3 userId = 1

TicketsOrdered !

noOfTickets = 3 userId = 1

time

Page 14: Event-sourced architectures with Akka

Changing the price

Event-sourced modellingConcertCreated

!

artist = Aerosmith availableTickets = 100 price = 10 ...

!

TicketsOrdered !

noOfTickets = 3 userId = 1

TicketsOrdered !

noOfTickets = 3 userId = 1

TicketsOrdered !

noOfTickets = 3 userId = 1

time

Page 15: Event-sourced architectures with Akka

Changing the price

Event-sourced modellingConcertCreated

!

artist = Aerosmith availableTickets = 100 price = 10 ...

!

PriceChanged !

price = 100

TicketsOrdered !

noOfTickets = 3 userId = 1

TicketsOrdered !

noOfTickets = 3 userId = 1

TicketsOrdered !

noOfTickets = 3 userId = 1

time

Page 16: Event-sourced architectures with Akka

Event-sourced modellingConcertCreated

!

artist = Aerosmith availableTickets = 100 price = 10 ...

!

PriceChanged !

price = 100

TicketsOrdered !

noOfTickets = 3 userId = 1

TicketsOrdered !

noOfTickets = 3 userId = 1

TicketsOrdered !

noOfTickets = 3 userId = 1

Canceling an order

time

Page 17: Event-sourced architectures with Akka

Event-sourced modellingConcertCreated

!

artist = Aerosmith availableTickets = 100 price = 10 ...

!

PriceChanged !

price = 100

OrderCancelled !

userId = 1

TicketsOrdered !

noOfTickets = 3 userId = 1

TicketsOrdered !

noOfTickets = 3 userId = 1

TicketsOrdered !

noOfTickets = 3 userId = 1

Canceling an order

time

Page 18: Event-sourced architectures with Akka

Event-sourced modelling‣ Immutable events ‣ Append-only storage (scalable) ‣ Replay events: reconstruct historic state ‣ Events as integration mechanism ‣ Events as audit mechanism

Page 19: Event-sourced architectures with Akka

Events: where from?

Event-sourcing: capture all changes to application state as a sequence of events

Page 20: Event-sourced architectures with Akka

Events&CommandsDo something (active)

It happened. Deal with it. (facts)

Can be rejected (validation)

Can be responded to

Page 21: Event-sourced architectures with Akka

Querying & event-sourcingHow do you query a log?

Page 22: Event-sourced architectures with Akka

Querying & event-sourcing

CommandQueryResponsibilitySegregation

How do you query a log?

Page 23: Event-sourced architectures with Akka

CQRS without ES

UI/Client

Service layer

Database

Command Query

Page 24: Event-sourced architectures with Akka

CQRS without ES

UI/Client

Service layer

Database

Command Query

UI/Client

Command Model

Datastore

Query Model(s)

DatastoreDatastoreDatastore

Command Query

?

Page 25: Event-sourced architectures with Akka

Event-sourced CQRS

UI/Client

Command Model

Journal

Query Model(s)

DatastoreDatastoreDatastore

Command Query

Events

Page 26: Event-sourced architectures with Akka

Actors‣ Mature and open source ‣ Scala & Java API ‣ Akka Cluster

Page 27: Event-sourced architectures with Akka

Actors"an island of consistency in a sea of concurrency"

Actor !

!

!

mailbox

state

behavior

async message send

Process message: ‣ update state ‣ send messages ‣ change behavior Don't worry about concurrency

Page 28: Event-sourced architectures with Akka

ActorsA good fit for event-sourcing?

Actor !

!

!

mailbox

state

behavior

mailbox is non-durable (lost messages)

state is transient

Page 29: Event-sourced architectures with Akka

ActorsJust store all incoming messages?

Actor !

!

!

mailbox

state

behavior

async message send

store in journal

Problems with command-sourcing: ‣ side-effects ‣ poisonous

(failing) messages

Page 30: Event-sourced architectures with Akka

Persistence‣ Experimental Akka module ‣ Scala & Java API ‣ Actor state persistence based

on event-sourcing

Page 31: Event-sourced architectures with Akka

Persistent ActorPersistentActor

actor-id !

!

!

event

async message send (command)

‣ Derive events from commands

‣ Store events ‣ Update state ‣ Perform side-

effects

journal (actor-id)

event

state

Page 32: Event-sourced architectures with Akka

Persistent ActorPersistentActor

actor-id !

!

!

event

!

Recover by replaying events, that update the state (no side-effects) !

journal (actor-id)

event

state

Page 33: Event-sourced architectures with Akka

Persistent Actor!case object Increment // command case object Incremented // event !class CounterActor extends PersistentActor { def persistenceId = "counter" ! var state = 0 ! val receiveCommand: Receive = { case Increment => persist(Incremented) { evt => state += 1 println("incremented") } } ! val receiveRecover: Receive = { case Incremented => state += 1 } }

Page 34: Event-sourced architectures with Akka

Persistent Actor!case object Increment // command case object Incremented // event !class CounterActor extends PersistentActor { def persistenceId = "counter" ! var state = 0 ! val receiveCommand: Receive = { case Increment => persist(Incremented) { evt => state += 1 println("incremented") } } ! val receiveRecover: Receive = { case Incremented => state += 1 } }

Page 35: Event-sourced architectures with Akka

Persistent Actor!case object Increment // command case object Incremented // event !class CounterActor extends PersistentActor { def persistenceId = "counter" ! var state = 0 ! val receiveCommand: Receive = { case Increment => persist(Incremented) { evt => state += 1 println("incremented") } } ! val receiveRecover: Receive = { case Incremented => state += 1 } }

async callback (but safe to close over state)

Page 36: Event-sourced architectures with Akka

Persistent Actor!case object Increment // command case object Incremented // event !class CounterActor extends PersistentActor { def persistenceId = "counter" ! var state = 0 ! val receiveCommand: Receive = { case Increment => persist(Incremented) { evt => state += 1 println("incremented") } } ! val receiveRecover: Receive = { case Incremented => state += 1 } }

Page 37: Event-sourced architectures with Akka

Persistent Actor!case object Increment // command case object Incremented // event !class CounterActor extends PersistentActor { def persistenceId = "counter" ! var state = 0 ! val receiveCommand: Receive = { case Increment => persist(Incremented) { evt => state += 1 println("incremented") } } ! val receiveRecover: Receive = { case Incremented => state += 1 } }

Isn't recovery with lots of events slow?

Page 38: Event-sourced architectures with Akka

Snapshotsclass SnapshottingCounterActor extends PersistentActor { def persistenceId = "snapshotting-counter" ! var state = 0 ! val receiveCommand: Receive = { case Increment => persist(Incremented) { evt => state += 1 println("incremented") } case "takesnapshot" => saveSnapshot(state) } ! val receiveRecover: Receive = { case Incremented => state += 1 case SnapshotOffer(_, snapshotState: Int) => state = snapshotState } }

Page 39: Event-sourced architectures with Akka

Snapshotsclass SnapshottingCounterActor extends PersistentActor { def persistenceId = "snapshotting-counter" ! var state = 0 ! val receiveCommand: Receive = { case Increment => persist(Incremented) { evt => state += 1 println("incremented") } case "takesnapshot" => saveSnapshot(state) } ! val receiveRecover: Receive = { case Incremented => state += 1 case SnapshotOffer(_, snapshotState: Int) => state = snapshotState } }

Page 40: Event-sourced architectures with Akka

Snapshot&JournalCassandra

KafkaKafka

HBase

DynamoDB

MongoDB

HBase

MapDB

JDBC JDBC

Cassandra

MongoDB

Plugins:

Page 41: Event-sourced architectures with Akka

Default: Java serialization

Pluggable through Akka: ‣ Protobuf ‣ Kryo ‣ Avro ‣ Your own

Plugins:Serialization

Page 42: Event-sourced architectures with Akka

Persistent ViewPersistent

Actor

journal

Persistent View

Persistent View

Views poll the journal ‣ Eventually consistent ‣ Polling configurable ‣ Actor may be inactive ‣ Views track single

persistence-id ‣ Views can have own

snapshots snapshot store

other datastore

Page 43: Event-sourced architectures with Akka

!

"The Database is a cache of a subset of the log" - Pat Helland

Persistent ViewPersistent

Actor

journal

Persistent View

Persistent View

snapshot store other

datastore

Page 44: Event-sourced architectures with Akka

Persistent View!case object ComplexQuery class CounterView extends PersistentView { override def persistenceId: String = "counter" override def viewId: String = "counter-view" var queryState = 0 def receive: Receive = { case Incremented if isPersistent => { queryState = someVeryComplicatedCalculation(queryState) // Or update a document/graph/relational database } case ComplexQuery => { sender() ! queryState; // Or perform specialized query on datastore } } }

Page 45: Event-sourced architectures with Akka

Persistent View!case object ComplexQuery class CounterView extends PersistentView { override def persistenceId: String = "counter" override def viewId: String = "counter-view" var queryState = 0 def receive: Receive = { case Incremented if isPersistent => { queryState = someVeryComplicatedCalculation(queryState) // Or update a document/graph/relational database } case ComplexQuery => { sender() ! queryState; // Or perform specialized query on datastore } } }

Page 46: Event-sourced architectures with Akka

Persistent View!case object ComplexQuery class CounterView extends PersistentView { override def persistenceId: String = "counter" override def viewId: String = "counter-view" var queryState = 0 def receive: Receive = { case Incremented if isPersistent => { queryState = someVeryComplicatedCalculation(queryState) // Or update a document/graph/relational database } case ComplexQuery => { sender() ! queryState; // Or perform specialized query on datastore } } }

Page 47: Event-sourced architectures with Akka

Sell concert ticketsConcertActor

!

!

price availableTickets startTime salesRecords

!

Commands: CreateConcert BuyTickets ChangePrice AddCapacity

journal

ConcertHistoryView !

!

!

!

015304560

$50 $75 $100

050

100

code @ bit.ly/akka-es

Page 48: Event-sourced architectures with Akka

Scaling out: Akka Cluster

Cluster node Cluster node Cluster node

Persistent Actor

id = "1"

Persistent Actor

id = "1"

Persistent Actor

id = "1"

Persistent Actor

id = "2"

Persistent Actor

id = "1"

Persistent Actor

id = "3"

Single writer: persistent actor must be singleton, views may be anywhere

distributed journal

Page 49: Event-sourced architectures with Akka

Scaling out: Akka Cluster

Cluster node Cluster node Cluster node

Persistent Actor

id = "1"

Persistent Actor

id = "1"

Persistent Actor

id = "1"

Persistent Actor

id = "2"

Persistent Actor

id = "1"

Persistent Actor

id = "3"

Single writer: persistent actor must be singleton, views may be anywhere

How are persistent actors distributed over cluster?

distributed journal

Page 50: Event-sourced architectures with Akka

Scaling out: Akka Cluster

Cluster node Cluster node Cluster node

Persistent Actor

id = "1"

Persistent Actor

id = "1"

Persistent Actor

id = "1"

Persistent Actor

id = "2"

Persistent Actor

id = "1"

Persistent Actor

id = "3"

Sharding: Coordinator assigns ShardRegions to nodes (consistent hashing) Actors in shard can be activated/passivated, rebalanced

Shard Region

Shard Region

Shard Region

Coordinator

distributed journal

Page 51: Event-sourced architectures with Akka

Scaling out: Shardingval idExtractor: ShardRegion.IdExtractor = { case cmd: Command => (cmd.concertId, cmd) }

IdExtractor allows ShardRegion to route commands to actors

Page 52: Event-sourced architectures with Akka

Scaling out: Shardingval idExtractor: ShardRegion.IdExtractor = { case cmd: Command => (cmd.concertId, cmd) }

IdExtractor allows ShardRegion to route commands to actors

val shardResolver: ShardRegion.ShardResolver = msg => msg match { case cmd: Command => hash(cmd.concert) }

ShardResolver assigns new actors to shards

Page 53: Event-sourced architectures with Akka

Scaling out: Shardingval idExtractor: ShardRegion.IdExtractor = { case cmd: Command => (cmd.concertId, cmd) }

IdExtractor allows ShardRegion to route commands to actors

val shardResolver: ShardRegion.ShardResolver = msg => msg match { case cmd: Command => hash(cmd.concert) }

ShardResolver assigns new actors to shards

ClusterSharding(system).start( typeName = "Concert", entryProps = Some(ConcertActor.props()), idExtractor = ConcertActor.idExtractor, shardResolver = ConcertActor.shardResolver)

Initialize ClusterSharding extension

Page 54: Event-sourced architectures with Akka

Scaling out: Shardingval idExtractor: ShardRegion.IdExtractor = { case cmd: Command => (cmd.concertId, cmd) }

IdExtractor allows ShardRegion to route commands to actors

val shardResolver: ShardRegion.ShardResolver = msg => msg match { case cmd: Command => hash(cmd.concert) }

ShardResolver assigns new actors to shards

ClusterSharding(system).start( typeName = "Concert", entryProps = Some(ConcertActor.props()), idExtractor = ConcertActor.idExtractor, shardResolver = ConcertActor.shardResolver)

Initialize ClusterSharding extension

val concertRegion: ActorRef = ClusterSharding(system).shardRegion("Concert") concertRegion ! BuyTickets(concertId = 123, user = "Sander", quantity = 1)

Page 55: Event-sourced architectures with Akka

Design for event-sourcing

Page 56: Event-sourced architectures with Akka

DDD: Domain Driven Design DDD+CQRS+ES

Aggregate !

Aggregate !

Eventual Consistency

Fully consistent Fully consistent

Root entity

entityentityentity

Root entity

entity entity

Page 57: Event-sourced architectures with Akka

DDD: Domain Driven Design DDD+CQRS+ES

Aggregate !

Aggregate !

Eventual Consistency

Fully consistent Fully consistent

Root entity

entityentityentity

Root entity

entity entity

Persistent Actor

Persistent Actor

Message passing

Page 58: Event-sourced architectures with Akka

DDD: Domain Driven Design DDD+CQRS+ES

Aggregate !

Aggregate !

Eventual Consistency

Fully consistent Fully consistent

Akka Persistence is not a DDD/CQRS frameworkBut it comes awfully close

Root entity

entityentityentity

Root entity

entity entity

Persistent Actor

Persistent Actor

Message passing

Page 59: Event-sourced architectures with Akka

Designing aggregatesFocus on events Structural representation(s) follow ERD

Page 60: Event-sourced architectures with Akka

Designing aggregatesFocus on events Structural representation(s) follow ERD

Size matters. Faster replay, less write contention Don't store derived info (use views)

Page 61: Event-sourced architectures with Akka

Designing aggregatesFocus on events Structural representation(s) follow ERD

Size matters. Faster replay, less write contention Don't store derived info (use views)

With CQRS read-your-writes is not the default When you need this, model it in a single Aggregate

Page 62: Event-sourced architectures with Akka

Between aggregates‣ Send commands to other aggregates by id !

‣ No distributed transactions ‣ Use business level ack/nacks ‣ SendInvoice -> InvoiceSent/InvoiceCouldNotBeSent ‣ Compensating actions for failure ‣ No guaranteed delivery ‣ Alternative: AtLeastOnceDelivery

Page 63: Event-sourced architectures with Akka

Kafka

topic 1 topic 2 derived

Between systemsApplication

System integration through event-sourcing

Akka Persistence

Spark Streaming

External Application

Page 64: Event-sourced architectures with Akka

Designing commands‣ Self-contained ‣ Unit of atomic change ‣ Granularity and intent

Page 65: Event-sourced architectures with Akka

Designing commands‣ Self-contained ‣ Unit of atomic change ‣ Granularity and intent

UpdateAddress street = ...

city = ... vs

ChangeStreet street = ...

ChangeCity street = ...

Page 66: Event-sourced architectures with Akka

Designing commands‣ Self-contained ‣ Unit of atomic change ‣ Granularity and intent

UpdateAddress street = ...

city = ... vs

ChangeStreet street = ...

ChangeCity street = ...

Move street = ...

city = ... vs

Page 67: Event-sourced architectures with Akka

Designing eventsCreateConcert

Page 68: Event-sourced architectures with Akka

Designing eventsCreateConcert ConcertCreated

Past tense Irrefutable

Page 69: Event-sourced architectures with Akka

Designing eventsCreateConcert ConcertCreated

Past tense Irrefutable

TicketsBought newCapacity = 99

Page 70: Event-sourced architectures with Akka

Designing eventsCreateConcert ConcertCreated

Past tense Irrefutable

TicketsBought newCapacity = 99

TicketsBought quantity = 1

Delta-based No derived info

Page 71: Event-sourced architectures with Akka

Designing eventsCreateConcert

ConcertCreated

ConcertCreatedPast tense Irrefutable

TicketsBought newCapacity = 99

TicketsBought quantity = 1

Delta-based No derived info

Page 72: Event-sourced architectures with Akka

Designing eventsCreateConcert

ConcertCreated ConcertCreated

who/when/..

Add metadata to all events

ConcertCreatedPast tense Irrefutable

TicketsBought newCapacity = 99

TicketsBought quantity = 1

Delta-based No derived info

Page 73: Event-sourced architectures with Akka

Versioning

event v1

event v1

event v2

event v2

Actor logic: v1 and v2

Page 74: Event-sourced architectures with Akka

Versioning

event v1

event v1

event v2

event v2

Actor logic: v1 and v2

event v1

event v1

event v2

event v2

event v2

event v2

Actor logic: v2

Page 75: Event-sourced architectures with Akka

Versioning

event v1

event v1

event v2

event v2

Actor logic: v1 and v2

event v1

event v1

event v2

event v2

event v2

event v2

Actor logic: v2

event v1

event v1

event v2

event v2

snapshot

Actor logic: v2

Page 76: Event-sourced architectures with Akka

Versioning

event v1

event v1

event v2

event v2

Actor logic: v1 and v2

event v1

event v1

event v2

event v2

event v2

event v2

Actor logic: v2

event v1

event v1

event v2

event v2

snapshot

Actor logic: v2

‣ Be backwards compatible

‣ Avro/Protobuf ‣ Serializer can do

translation !

‣ Snapshot versioning: harder

Page 77: Event-sourced architectures with Akka

In conclusionEvent-sourcing is...

Powerful

but unfamiliar

Page 78: Event-sourced architectures with Akka

In conclusionEvent-sourcing is...

Powerful

but unfamiliar

Combines well with

DDD/CQRS

UI/Client

Command Model

Journal

Query Model

DatastoreDatastoreDatastore

Events

Page 79: Event-sourced architectures with Akka

In conclusionEvent-sourcing is...

Powerful

but unfamiliar

Combines well with

DDD/CQRS

UI/Client

Command Model

Journal

Query Model

DatastoreDatastoreDatastore

Events

Not a

Page 80: Event-sourced architectures with Akka

In conclusionAkka Actors & Akka Persistence

A good fit for event-sourcing

PersistentActor actor-id

!!

!

async message send (command)

journal (actor-id)

eventevent

state

Experimental, view improvements needed

Page 81: Event-sourced architectures with Akka

Thank you. !

code @ bit.ly/akka-es

@Sander_Mak Luminis Technologies