4. Martin Krasser
Twitter: mrt1nz
Github: krasserm
Eventsourced
akka-persistence
September 2013 - February 2014
contractor for Typesafe
Awesome work, Martin!
7. Akka Persistence Webinar
Domain Events
• things that have completed, facts
• immutable
• verbs in past tense
• CustomerRelocated
• CargoShipped
• InvoiceSent
“State transitions are an important part of our problem
space and should be modeled within our domain.”
Greg Young, 2008
8. Akka Persistence Webinar
Benefits
• Bullet-proof auditing and historical tracing
• Support future ways of looking at data
• Performance and scalability
• Testability
• Reconstruct production scenarios
• No object-relational impedance mismatch
• Nice fit with actors
9. Akka Persistence Webinar
Command Sourcing Event Sourcing
write-ahead-log derive events from a command
same behavior during recovery as
normal operation
external interaction can be problematic
only state-changing behavior during
recovery
persisted before validation events cannot fail
allows retroactive changes to the
business logic
fixing the business logic will not
affect persisted events
naming: represent intent, imperative naming: things that have completed,
verbs in past tense
10. Akka Persistence Webinar
Consistency boundary
• An actor is a consistency boundary
• DDD Aggregate
• No distributed transactions
• eventually consistent
• compensating actions
11. Akka Persistence Webinar
Life beyond Distributed Transactions:
an Apostate’s Opinion
Position Paper
Pat Helland
“In general, application developers simply do not implement
large scalable applications assuming distributed transactions.”
Pat Helland
http://www-‐db.cs.wisc.edu/cidr/cidr2007/papers/cidr07p15.pdf
13. Processor
import akka.persistence.{ Persistent, Processor }
!
class MyProcessor extends Processor {
def receive = {
case Persistent(payload, sequenceNr) =>
// message successfully written to journal
case other =>
// message not written to journal
}
}
val processor = context.actorOf(Props[MyProcessor],
name = "myProcessor")
!
processor ! Persistent("foo") // will be journaled
processor ! "bar" // will not be journaled
14. Processor
class InvoiceService extends Processor {
var invoices = Map.empty[String, Invoice]
!
def receive: Receive = {
case Persistent(CreateInvoice(id), _) =>
invoices = invoices.updated(id, Invoice(id))
}
}
15. Processor
class InvoiceService extends Processor {
var invoices = Map.empty[String, Invoice]
!
def receive: Receive = {
case Persistent(CreateInvoice(id), _) =>
invoices = invoices.updated(id, Invoice(id))
!
case Persistent(AddInvoiceItem(id, item), _) =>
invoices.get(id) match {
case Some(inv) =>
invoices = invoices.updated(id, inv.addItem(item))
case None => // TODO
}
!
case GetInvoice(id) =>
sender() ! invoices.getOrElse(id, "not found: " + id)
!
case Persistent(SendInvoiceTo(id, address), _) =>
// TODO send to the invoice printing service
}
}
16. Processor
case class CreateInvoice(invoiceId: String)
case class AddInvoiceItem(invoiceId: String, invoiceItem: InvoiceItem)
case class SendInvoiceTo(invoiceId: String, to: InvoiceAddress)
case class GetInvoice(invoiceId: String)
!
case class Invoice(id: String,
items: IndexedSeq[InvoiceItem] = Vector.empty) {
def addItem(item: InvoiceItem): Invoice =
copy(items = items :+ item)
}
!
case class InvoiceItem(description: String, count: Int,
amount: BigDecimal)
!
case class InvoiceAddress(name: String, street: String, city: String)
17. Processor Identifier
override def processorId = "my-stable-processor-id"
Default is actor path without address
/user/top/myProcessor
Don’t use anonymous processors
18. Akka Persistence Webinar
Processor
• Automatic recovery on start and restart
• Stashing until recovery completed
• Failure handling with supervisor strategy
• Might want to delete erroneous messages
20. Processor with Channel
val printingChannel = context.actorOf(Channel.props(),
name = "printingChannel")
val printingDestination = context.system / "printingService"
!
def receive: Receive = {
case p @ Persistent(SendInvoiceTo(id, address), _) =>
// send to the invoice printing service
invoices.get(id) match {
case Some(inv) =>
printingChannel ! Deliver(p.withPayload(
PrintingOrder(inv, address)), printingDestination)
invoices -= inv.id
case None => // TODO
}
}
class PrintingService extends Actor {
def receive = {
case p @ ConfirmablePersistent(payload, sequenceNr, redeliveries) =>
// ...
p.confirm()
}
}
21. Akka Persistence Webinar
EventsourcedProcessor
• Incoming messages (commands) are not persisted
• Steps:
1. Validate command
2. Create domain event and explicitly persist it
3. Update internal state, by applying the event
4. External side effects
• During recovery the internal state is updated by applying the events
• no external side effects
24. object BlogPost {
case class AddPost(author: String, title: String)
}
!
class BlogPost extends EventsourcedProcessor {
import BlogPost._
!
override def receiveCommand: Receive = {
case AddPost(author, title) =>
persist(PostAdded(author, title)) { evt =>
state = state.updated(evt)
}
}
!
override def receiveRecover: Receive = ???
}
EventsourcedProcessor
25. EventsourcedProcessor
object BlogPost {
case class AddPost(author: String, title: String)
!
sealed trait Event
case class PostAdded(author: String, title: String) extends Event
!
private case class State(author: String, title: String) {
def updated(evt: Event): State = evt match {
case PostAdded(author, title) => copy(author, title)
}
}
}
!
class BlogPost extends EventsourcedProcessor {
import BlogPost._
private var state = State("", “")
!
override def receiveCommand: Receive = {
case AddPost(author, title) =>
persist(PostAdded(author, title)) { evt =>
state = state.updated(evt)
}
}
!
override def receiveRecover: Receive = ???
}
26. EventsourcedProcessor
object BlogPost {
case class AddPost(author: String, title: String)
case class ChangeBody(body: String)
case object Publish
!
sealed trait Event
case class PostAdded(author: String, title: String) extends Event
case class BodyChanged(body: String) extends Event
case object PostPublished extends Event
!
private case class State(author: String, title: String,
body: String, published: Boolean) {
!
def updated(evt: Event): State = evt match {
case PostAdded(author, title) => copy(author, title)
case BodyChanged(b) => copy(body = b)
case PostPublished => copy(published = true)
}
}
}
27. EventsourcedProcessor
class BlogPost extends EventsourcedProcessor {
import BlogPost._
private var state = State("", "", "", false)
!
override def receiveCommand: Receive = {
case AddPost(author, title) =>
if (state.body == "" && author != "" && title != "")
persist(PostAdded(author, title)) { evt =>
state = state.updated(evt)
}
case ChangeBody(body) =>
if (!state.published)
persist(BodyChanged(body)) { evt =>
state = state.updated(evt)
}
case Publish =>
if (!state.published)
persist(PostPublished) { evt =>
state = state.updated(evt)
// call external web content service ...
}
}
!
override def receiveRecover: Receive = {
case evt: Event => state = state.updated(evt)
}
}
28. Snapshots
class MyProcessor extends Processor {
var state: Any = _
def receive = {
case "snap" => saveSnapshot(state)
case SaveSnapshotSuccess(metadata) => // ...
case SaveSnapshotFailure(metadata, reason) => // ...
}
}
29. Snapshots
class MyProcessor extends Processor {
var state: Any = _
def receive = {
case "snap" => saveSnapshot(state)
case SaveSnapshotSuccess(metadata) => // ...
case SaveSnapshotFailure(metadata, reason) => // ...
!
case SnapshotOffer(metadata, offeredSnapshot) => state = offeredSnapshot
case Persistent(payload, _) => // ...
}
}
30. Akka Persistence Webinar
View
• replays Persistent messages from a Processor’s journal
• query side of CQRS
• features
• auto-update interval
• Update message
• limit
• may store its own snapshots
31. View
class InvoiceCounter extends View {
import InvoiceCounter._
override def processorId: String = "/user/invoiceService"
override def autoUpdateInterval = 10.seconds
!
var count = 0L
!
def receive: Actor.Receive = {
case Persistent(payload: SendInvoiceTo, _) =>
count += 1
case _: Persistent =>
case GetInvoiceCount =>
sender ! InvoiceCount(count)
}
}
!
object InvoiceCounter {
case object GetInvoiceCount
case class InvoiceCount(count: Long)
}
35. Akka Persistence Webinar
Channels
• re-deliver messages until confirmed
• application level confirmation
• different semantics
• duplicates may be received
• message order not retained
• after a crash and restart messages are still delivered
• listener for RedeliverFailure notifications
• recommendation: one destination per channel
exception: replies via channel
36. Akka Persistence Webinar
Channel vs. PersistentChannel
• Channel
• use from Processor
• in-memory
• PersistentChannel
• standalone usage
• conceptually: processor + channel
• persist messages before delivering
• reply ack when persisted
• more advanced delivery flow control
37. Processor with Channel
class MyProcessor extends Processor {
val channel = context.actorOf(Channel.props(), name = "myChannel")
!
def receive = {
case p @ Persistent(payload, _) =>
val destination = context.system / "myDestination"
channel ! Deliver(p.withPayload("output msg"), destination)
}
}
!
class MyDestination extends Actor {
def receive = {
case p @ ConfirmablePersistent(payload, sequenceNr, redeliveries) =>
// ...
p.confirm()
}
}
38. PersistentChannel
class Endpoint extends Actor {
val channel = context.actorOf(PersistentChannel.props(
PersistentChannelSettings(redeliverInterval = 3.seconds, redeliverMax = 10,
replyPersistent = true)), name = "myChannel")
val destination = context.system / "jobManager"
!
import context.dispatcher
implicit val timeout = Timeout(5.seconds)
!
def receive = {
case job: Job =>
(channel ? Deliver(Persistent(job), destination)) map {
case _: Persistent => "OK: " + job.id
} recover {
case e => "FAILED: " + job.id
} pipeTo sender()
}
}
53. Akka Persistence Webinar
Akka in Action
• All registrants for this webinar qualify for a free E-Book PDF subset of
Akka in Action by Raymond Roestenburg, Rob Bakker and Rob Williams.
Additionally, you will qualify for the Typesafe discount and can save 40%
on the full book.