Skip to content

Instantly share code, notes, and snippets.

@krasserm
Created November 29, 2011 11:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krasserm/1404555 to your computer and use it in GitHub Desktop.
Save krasserm/1404555 to your computer and use it in GitHub Desktop.
Building an event-sourced web application with Akka - Part 1
case class InvoiceAddress(street: String, city: String, country: String)
case class InvoiceItem(description: String, count: Int, amount: BigDecimal)
case class Invoice(
id: String,
items: List[InvoiceItem] = Nil,
discount: Option[BigDecimal] = None,
sentTo: Option[InvoiceAddress] = None) extends EventSourced[InvoiceEvent, Invoice] {
def addItem(item: InvoiceItem): Update[InvoiceEvent, Invoice] = ...
def setDiscount(discount: BigDecimal): Update[InvoiceEvent, Invoice] = ...
def sendTo(address: InvoiceAddress): Update[InvoiceEvent, Invoice] = ...
def handle(event: InvoiceEvent): Invoice = event match {
case InvoiceItemAdded(_, item) => copy(items = item :: items)
case InvoiceDiscountSet(_, discount) => copy(discount = Some(discount))
case InvoiceSent(_, to) => copy(sentTo = Some(to))
}
}
sealed trait InvoiceEvent extends Event { def invoiceId: String }
case class InvoiceItemAdded(invoiceId: String, item: InvoiceItem) extends InvoiceEvent
case class InvoiceDiscountSet(invoiceId: String, discount: BigDecimal) extends InvoiceEvent
case class InvoiceSent(invoiceId: String, to: InvoiceAddress) extends InvoiceEvent
// ...
trait EventSourced[E <: Event, A <: EventSourced[E, A]] {
def update(event: E): Update[E, A] = Update.accept(event, handle(event))
def handle(event: E): A
}
case class Invoice(...) extends EventSourced[InvoiceEvent, Invoice] {
// ...
def sendTo(address: InvoiceAddress): Update[InvoiceEvent, Invoice] =
if (items.isEmpty) Update.reject(DomainError("cannot send empty invoice"))
else update(InvoiceSent(id, address))
def handle(event: Event): Invoice = event match {
// ...
case InvoiceSent(_, to) => copy(sentTo = Some(to))
}
}
val invoice: Invoice = ...
invoice.sendTo(InvoiceAddress("foo", "bar", "baz")).result() match {
case Success(updated) => ...
case Failure(errors) => ...
}
invoice.sendTo(InvoiceAddress("foo", "bar", "baz")).result { (events: List[InvoiceEvent], updated: Invoice) =>
// do something with generated events ...
// do something with updated invoice ...
}
val overallUpdate: Update[InvoiceEvent, Invoice] = for {
updated1 <- invoice.addItem(InvoiceItem("x", 1, 10))
updated2 <- updated1.addItem(InvoiceItem("y", 2, 12))
updated3 <- updated2.sendTo(InvoiceAddress("foo", "bar", "baz"))
} yield updated3
val update = for {
created <- Invoice.create("test")
updated1 <- created.addItem(InvoiceItem("a", 1, 1))
updated2 <- updated1.addItem(InvoiceItem("b", 2, 2))
} yield updated2
update.result { (events, updated) =>
assert(updated == Invoice.handle(events.reverse)) // reconstruct Invoice from events
assert(events == List(
InvoiceItemAdded("test", InvoiceItem("b", 2, 2)),
InvoiceItemAdded("test", InvoiceItem("a", 1, 1)),
InvoiceCreated("test"))
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment