1. Exploring Lightweight
Event Sourcing
Erik Rozendaal <erozendaal@zilverline.com>
@erikrozendaal
2. Goals
• The problem of data persistence
• Understand event sourcing
• Show why Scala is well-suited as
implementation language
Exploring lightweight event sourcing 2
7. N-tier
• Presentation, Business, and Data Layers
• Shared data model (“domain”)
• Heavy use of a single, global, mutable
variable (“the database”)
Exploring lightweight event sourcing 4
8. N-tier
• Presentation, Business, and Data Layers
• Shared data model (“domain”)
• Heavy use of a single, global, mutable
variable (“the database”)
• Is it any surprise that we’re struggling to
build modular, maintainable applications?
Exploring lightweight event sourcing 4
9. Domain Driven Design
• Domain-Driven Design An approach to software development that suggests
that (1) For most software projects, the primary focus should be on the
domain and domain logic; and (2) Complex domain designs should be based
on a model.
• Domain Expert A member of a software project whose field is the domain
of the application, rather than software development. Not just any user of
the software, the domain expert has deep knowledge of the subject.
• Ubiquitous Language A language structured around the domain model and
used by all team members to connect all the activities of the team with the
software.
Exploring lightweight event sourcing 5
10. Data must be durable
Exploring lightweight event sourcing 6
13. Lossy?
• UPDATE invoice WHERE id = 1234
SET total_amount = 230
Exploring lightweight event sourcing 8
14. Lossy?
• UPDATE invoice WHERE id = 1234
SET total_amount = 230
• What happened to the previous order
amount?
Exploring lightweight event sourcing 8
15. Lossy?
• UPDATE invoice WHERE id = 1234
SET total_amount = 230
• What happened to the previous order
amount?
• Why was the order amount changed?
Exploring lightweight event sourcing 8
16. Lossy?
• UPDATE invoice WHERE id = 1234
SET total_amount = 230
• What happened to the previous order
amount?
• Why was the order amount changed?
• Application behavior is not captured!
Exploring lightweight event sourcing 8
23. ORM implementation
“Inexperienced programmers love magic because
it saves their time. Experienced programmers hate
magic because it wastes their time.”
– @natpryce
Exploring lightweight event sourcing 11
24. Event Sourcing
• All state changes are explicitly captured
using domain events
• Capture the intent of the user and the
related data
• Events represent the outcome of application
behavior
Exploring lightweight event sourcing 12
32. Only the events need
to be stored on disk
Event
Store Draft Invoice Invoice Recipient Invoice Item
.......
Created Changed Added
Recipient: "Erik" Item: "Food"
Item amount: 9.95
Total amount: 9.95
Exploring lightweight event sourcing 14
39. YAGNI?
• On the checkout page we’d like to promote
products that customers previously
removed from their shopping cart.
Exploring lightweight event sourcing 16
40. YAGNI?
• On the checkout page we’d like to promote
products that customers previously
removed from their shopping cart.
• Can you tell us how many people removed
a product but bought it later anyway?
Exploring lightweight event sourcing 16
41. YAGNI?
• On the checkout page we’d like to promote
products that customers previously
removed from their shopping cart.
• Can you tell us how many people removed
a product but bought it later anyway?
• ... over the past 5 years?
... and how much time passed in-between?
Exploring lightweight event sourcing 16
42. YAGNI?
• We have reports of a strange bug, but have
not been able to isolate it. Could you look
at the production data to find out what’s
going on?
Exploring lightweight event sourcing 17
43. Is it worth it?
Exploring lightweight event sourcing 18
44. Is it worth it?
• Make the event sourcing implementation as
simple as possible
Exploring lightweight event sourcing 18
45. Is it worth it?
• Make the event sourcing implementation as
simple as possible
• ... while avoiding the complexities of
databases, ORMs, etc.
Exploring lightweight event sourcing 18
46. Is it worth it?
• Make the event sourcing implementation as
simple as possible
• ... while avoiding the complexities of
databases, ORMs, etc.
• ... unless you want or need them :)
Exploring lightweight event sourcing 18
55. “CRUD”
Create/Update/Delete
HTTP POST
Validate input and
generate event
Exploring lightweight event sourcing 20
56. “CRUD”
Create/Update/Delete
HTTP POST Save Event
Validate input and Event
generate event Store
Exploring lightweight event sourcing 20
57. “CRUD”
Create/Update/Delete
HTTP POST Save Event
Validate input and Event
generate event Store
Apply Event
Status: Recipient:
"Draft" "Erik"
Invoice Total: 9.95
Item:
Invoice Item
"Food"
Amount:
9.95
Exploring lightweight event sourcing 20
58. “CRUD”
Create/Update/Delete
HTTP POST Save Event
Validate input and Event
generate event Store
Apply Event
Status: Recipient:
"Draft" "Erik"
Invoice Total: 9.95
Query
Render View
Item:
Invoice Item
"Food"
Amount:
9.95
Exploring lightweight event sourcing 20
59. “CRUD”
Create/Update/Delete
HTTP POST Save Event
Validate input and Event
generate event Store
Apply Event
Read Status:
"Draft"
Recipient:
"Erik"
Invoice Total: 9.95
HTTP GET Query
Render View
Item:
Invoice Item
"Food"
Amount:
9.95
Exploring lightweight event sourcing 20
60. HTTP POST Save Event
Validate input and Event
Controller
generate event Store
Apply Event
Status: Recipient:
"Draft" "Erik"
Invoice Total: 9.95
HTTP GET Query
Render View
Item:
Invoice Item
"Food"
Amount:
9.95
post("/todo") {
val item = field("item", required) map (_.collapseWhitespace)
item.fold(
error => new ToDoView(toDoItems, Some(error)),
value => {
record(Created(ToDoItem(UUID.randomUUID(), value)))
redirect(url("/todo"))
})
}
Exploring lightweight event sourcing 21
61. HTTP POST Save Event
Validate input and Event
Memory Image
generate event Store
Apply Event
Status: Recipient:
"Draft" "Erik"
Invoice Total: 9.95
HTTP GET Query
Render View
Item:
Invoice Item
"Food"
Amount:
9.95
case class ToDoItems (
all : Map[String, ToDoItem] = Map.empty,
recentlyAdded: Vector[String] = Vector.empty) {
def apply(event: Any) = event match {
case Created(item: ToDoItem) =>
copy(all.updated(item.id, item), recentlyAdded :+ item.id)
case Updated(item: ToDoItem) =>
copy(all.updated(item.id, item))
case Deleted(id: String) =>
copy(all - id, recentlyAdded.filter(_ != id))
}
def mostRecent(count: Int) = recentlyAdded.takeRight(count).map(all).reverse
}
Exploring lightweight event sourcing 22
62. HTTP POST Save Event
Validate input and Event
View
generate event Store
Apply Event
Status: Recipient:
"Draft" "Erik"
Invoice Total: 9.95
HTTP GET Query
Render View
Item:
Invoice Item
"Food"
Amount:
9.95
<table class="zebra-striped">
<thead><tr><th>To-Do</th></tr></thead>
<tbody>{
for (item <- toDoItems.mostRecent(20)) yield {
<tr><td>{ item.text }</td></tr>
}
}</tbody>
</table>
Exploring lightweight event sourcing 23
63. Domain Model
Domain Model
2: Save Event
1: Send Command
Validate input and
generate command Event Store
3: Apply Event
3: Apply Event
Status: Recipient:
"Draft" "Erik"
Invoice Total: 9.95
Item:
Invoice Item
"Food"
Amount:
9.95
Presentation Model
Exploring lightweight event sourcing 24
64. Domain Code
sealed trait InvoiceEvent
case class InvoiceCreated() extends InvoiceEvent
case class InvoiceRecipientChanged(recipient: String) extends InvoiceEvent
case class InvoiceItemAdded(item: InvoiceItem, totalAmount: BigDecimal) extends InvoiceEvent
case class InvoiceSent(sentOn: LocalDate, paymentDueOn: LocalDate) extends InvoiceEvent
Exploring lightweight event sourcing 25
65. Domain Code
sealed trait InvoiceEvent
case class InvoiceCreated() extends InvoiceEvent
case class InvoiceRecipientChanged(recipient: String) extends InvoiceEvent
case class InvoiceItemAdded(item: InvoiceItem, totalAmount: BigDecimal) extends InvoiceEvent
case class InvoiceSent(sentOn: LocalDate, paymentDueOn: LocalDate) extends InvoiceEvent
sealed trait Invoice extends AggregateRoot {
protected[this] type Event = InvoiceEvent
}
Exploring lightweight event sourcing 25
70. Domain Code
case class DraftInvoice(
recipient: Option[String] = None,
nextItemId: Int = 1,
items: Map[Int, InvoiceItem] = Map.empty) extends Invoice {
...
protected[this] def applyEvent = recipientChanged orElse itemAdded orElse sent
...
}
Exploring lightweight event sourcing 28
71. Event Store
• Stores sequence of events
• Dispatches events when successfully
committed
• Replays events on startup
• It’s your application’s transaction log
Exploring lightweight event sourcing 29
72. Conclusion
• Event Sourcing captures full historical
information
• Fully encapsulated domain code decoupled
from persistence and presentation layers
• Easier to fully understand compared to
traditional ORM based approach
• Need to learn “Event-Driven” thinking
Exploring lightweight event sourcing 30
74. Implementation
Limitations
• Single-threaded event store using disruptor
pattern
• ~ 5.000 commits per second
• Number of events to replay on startup
• ~ 70.000 JSON serialized events per second
• Total number of objects to store in main memory
• JVM can run with large heaps (tens of gigabytes)
Exploring lightweight event sourcing 32
75. But ready to scale
• Advanced event store implementations support
SQL, MongoDB, Amazon SimpleDB, etc.
• Use persisted view model for high volume objects
(fixes startup time and memory usage)
• Easy partitioning of aggregates (consistency
boundary with unique identifier)
• Load aggregates on-demand, use snapshotting
Exploring lightweight event sourcing 33
77. References
• Example code https://github.com/erikrozendaal/scala-event-sourcing-example
• CQRS http://cqrsinfo.com/
• Greg Young, “Unshackle Your Domain”
http://www.infoq.com/presentations/greg-young-unshackle-qcon08
• Pat Helland, “Life Beyond Distributed Transactions: An Apostate's Opinion”
http://www.ics.uci.edu/~cs223/papers/cidr07p15.pdf
• Erik Rozendaal, “Towards an immutable domain model” http://
blog.zilverline.com/2011/02/10/towards-an-immutable-domain-model-
monads-part-5/
• Martin Fowler, “Memory Image”, http://martinfowler.com/bliki/
MemoryImage.html
• Martin Fowler, “The LMAX Architecture”, http://martinfowler.com/articles/
lmax.html
Exploring lightweight event sourcing 35
Notas del editor
\n
\n
\n
\n
\n
\n
\n
\n
\n
DDD context: communication between programmers, users and domain experts\nKeep technology out of your domain code!\n
DDD with plain objects would be easy, but we require durability. No one likes to be told &#x201C;don&#x2019;t turn off the system, it will lose data!&#x201D;\n
But if durability is so important, why do current systems forget so much?\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Not a problem if implementation difficulty only affected implementers, but:\n- lazy/not lazy\n- select 1+n\n- large sessions\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
case classes: events, etc\nimmutable data structures: isolation, safe-sharing, to replace copying/locking by ORM/DB\nSRP: composing our system from simpler parts\n
case classes: events, etc\nimmutable data structures: isolation, safe-sharing, to replace copying/locking by ORM/DB\nSRP: composing our system from simpler parts\n
case classes: events, etc\nimmutable data structures: isolation, safe-sharing, to replace copying/locking by ORM/DB\nSRP: composing our system from simpler parts\n
case classes: events, etc\nimmutable data structures: isolation, safe-sharing, to replace copying/locking by ORM/DB\nSRP: composing our system from simpler parts\n
case classes: events, etc\nimmutable data structures: isolation, safe-sharing, to replace copying/locking by ORM/DB\nSRP: composing our system from simpler parts\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
No infrastructure stuff on events (mandatory interface or fields)\n
Pure! Behavior&#x2019;s are like Hibernate&#x2019;s Session or JPA&#x2019;s EntityManager\n- access current aggregates, track changes (events), ... but return a result + monad + flatMap etc.\n- why split? because we need to load from history without re-executing business logic\n- Return type of &#x201C;send&#x201D;, &#x2018;when&#x2019; builds Event => Behavior[A] functions\n
Pure! Behavior&#x2019;s are like Hibernate&#x2019;s Session or JPA&#x2019;s EntityManager\n- access current aggregates, track changes (events), ... but return a result + monad + flatMap etc.\n- why split? because we need to load from history without re-executing business logic\n- Return type of &#x201C;send&#x201D;, &#x2018;when&#x2019; builds Event => Behavior[A] functions\n
Pure! Behavior&#x2019;s are like Hibernate&#x2019;s Session or JPA&#x2019;s EntityManager\n- access current aggregates, track changes (events), ... but return a result + monad + flatMap etc.\n- why split? because we need to load from history without re-executing business logic\n- Return type of &#x201C;send&#x201D;, &#x2018;when&#x2019; builds Event => Behavior[A] functions\n
Pure! Behavior&#x2019;s are like Hibernate&#x2019;s Session or JPA&#x2019;s EntityManager\n- access current aggregates, track changes (events), ... but return a result + monad + flatMap etc.\n- why split? because we need to load from history without re-executing business logic\n- Return type of &#x201C;send&#x201D;, &#x2018;when&#x2019; builds Event => Behavior[A] functions\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Implementation is not trivial! But I expect all members of the team to be able to understand each component (unlike ORM)\n