Exploring Lightweight Event Sourcing
Erik Rozendaal <erozendaal@zilverline.com> @erikrozendaal
GOTO Amsterdam 2011
Exploring Lightweight Event Sourcing Erik Rozendaal - - PowerPoint PPT Presentation
Exploring Lightweight Event Sourcing Erik Rozendaal <erozendaal@zilverline.com> @erikrozendaal GOTO Amsterdam 2011 Goals The problem of data persistence Understand event sourcing Show why Scala is well-suited as
Erik Rozendaal <erozendaal@zilverline.com> @erikrozendaal
GOTO Amsterdam 2011
Exploring lightweight event sourcing
implementation language
2
Exploring lightweight event sourcing
3
Exploring lightweight event sourcing
4
Exploring lightweight event sourcing
5
Exploring lightweight event sourcing 6
Exploring lightweight event sourcing
SET total_amount = 230
6
Exploring lightweight event sourcing
SET total_amount = 230
amount?
6
Exploring lightweight event sourcing
SET total_amount = 230
amount?
6
Exploring lightweight event sourcing
SET total_amount = 230
amount?
6
Exploring lightweight event sourcing
7
Exploring lightweight event sourcing
Invoice Status: "Draft" Total: 0.00
7
Exploring lightweight event sourcing
Invoice Status: "Draft" Total: 0.00 Invoice Status: "Draft" Recipient: "Erik" Total: 0.00
7
Exploring lightweight event sourcing
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95 Invoice Status: "Draft" Total: 0.00 Invoice Status: "Draft" Recipient: "Erik" Total: 0.00
7
Exploring lightweight event sourcing
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95 Invoice Status: "Draft" Total: 0.00 Invoice Status: "Draft" Recipient: "Erik" Total: 0.00
8
Exploring lightweight event sourcing
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95
8
Exploring lightweight event sourcing
9
Exploring lightweight event sourcing
9
Exploring lightweight event sourcing
9
Exploring lightweight event sourcing
9
variable (“the database”)
Exploring lightweight event sourcing
“Inexperienced programmers love magic because it saves their time. Experienced programmers hate magic because it wastes their time.” – @natpryce
10
Exploring lightweight event sourcing
private copy of data
with initial copy to generate UPDATEs
11
Exploring lightweight event sourcing
12
Exploring lightweight event sourcing
your ORM?
12
Exploring lightweight event sourcing
your ORM?
12
Exploring lightweight event sourcing
your ORM?
12
Exploring lightweight event sourcing
13
Exploring lightweight event sourcing
many parts
13
Exploring lightweight event sourcing
many parts
related to application behavior
13
Exploring lightweight event sourcing
many parts
related to application behavior
model
13
Exploring lightweight event sourcing
many parts
related to application behavior
model
13
Exploring lightweight event sourcing
14
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Model
Exploring lightweight event sourcing
14
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Model
Exploring lightweight event sourcing
14
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Model Service Layer
Exploring lightweight event sourcing
14
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Model Service Layer
Database
Exploring lightweight event sourcing
15
Exploring lightweight event sourcing
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
the software, the domain expert has deep knowledge of the subject.
used by all team members to connect all the activities of the team with the software.
16
Exploring lightweight event sourcing
17
using domain events
related data
behavior
Exploring lightweight event sourcing
18
Exploring lightweight event sourcing
18
RCS SCCS PVCS CVS Subversion Darcs Git Mercurial BitKeeper
Exploring lightweight event sourcing
19
Exploring lightweight event sourcing
Draft Invoice Created
generate
19
Exploring lightweight event sourcing
Invoice Status: "Draft" Total: 0.00 Draft Invoice Created
generate apply
19
Exploring lightweight event sourcing
Invoice Status: "Draft" Total: 0.00 Draft Invoice Created Recipient: "Erik" Invoice Recipient Changed
generate generate apply
19
Exploring lightweight event sourcing
Invoice Status: "Draft" Total: 0.00 Invoice Status: "Draft" Recipient: "Erik" Total: 0.00 Draft Invoice Created Recipient: "Erik" Invoice Recipient Changed
generate generate apply apply
19
Exploring lightweight event sourcing
Invoice Status: "Draft" Total: 0.00 Invoice Status: "Draft" Recipient: "Erik" Total: 0.00 Draft Invoice Created Recipient: "Erik" Invoice Recipient Changed Item: "Food" Item amount: 9.95 Total amount: 9.95 Invoice Item Added
generate generate generate apply apply
19
Exploring lightweight event sourcing
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95 Invoice Status: "Draft" Total: 0.00 Invoice Status: "Draft" Recipient: "Erik" Total: 0.00 Draft Invoice Created Recipient: "Erik" Invoice Recipient Changed Item: "Food" Item amount: 9.95 Total amount: 9.95 Invoice Item Added
generate generate generate apply apply apply
19
Exploring lightweight event sourcing
Event Store
Draft Invoice Created Recipient: "Erik" Invoice Recipient Changed Item: "Food" Item amount: 9.95 Total amount: 9.95 Invoice Item Added
20
.......
Exploring lightweight event sourcing
21
Exploring lightweight event sourcing
Invoice Status: "Draft" Total: 0.00 Draft Invoice Created
apply
21
Exploring lightweight event sourcing
Invoice Status: "Draft" Total: 0.00 Invoice Status: "Draft" Recipient: "Erik" Total: 0.00 Draft Invoice Created Recipient: "Erik" Invoice Recipient Changed
apply apply
21
Exploring lightweight event sourcing
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95 Invoice Status: "Draft" Total: 0.00 Invoice Status: "Draft" Recipient: "Erik" Total: 0.00 Draft Invoice Created Recipient: "Erik" Invoice Recipient Changed Item: "Food" Item amount: 9.95 Total amount: 9.95 Invoice Item Added
apply apply apply
21
Exploring lightweight event sourcing
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95 Invoice Status: "Draft" Total: 0.00 Invoice Status: "Draft" Recipient: "Erik" Total: 0.00 Draft Invoice Created Recipient: "Erik" Invoice Recipient Changed Item: "Food" Item amount: 9.95 Total amount: 9.95 Invoice Item Added
apply apply apply
21
Exploring lightweight event sourcing
22
Exploring lightweight event sourcing
products that customers previously removed from their shopping cart.
22
Exploring lightweight event sourcing
products that customers previously removed from their shopping cart.
a product but bought it later anyway?
22
Exploring lightweight event sourcing
products that customers previously removed from their shopping cart.
a product but bought it later anyway?
... and how much time passed in-between?
22
Exploring lightweight event sourcing
not been able to isolate it. Could you look at the production data to find out what’s going on?
23
Exploring lightweight event sourcing
24
Exploring lightweight event sourcing
simple as possible
24
Exploring lightweight event sourcing
simple as possible
databases, ORMs, etc.
24
Exploring lightweight event sourcing
simple as possible
databases, ORMs, etc.
24
Exploring lightweight event sourcing
25
Exploring lightweight event sourcing
25
Exploring lightweight event sourcing
(Memory Image)
25
Exploring lightweight event sourcing
(Memory Image)
define events and immutable data structures
25
Exploring lightweight event sourcing
(Memory Image)
define events and immutable data structures
composed into a single application
25
Exploring lightweight event sourcing
contain simple functionality
26
Exploring lightweight event sourcing 27
Exploring lightweight event sourcing 27
Create/Update/Delete
Exploring lightweight event sourcing 27
Create/Update/Delete
Validate input and generate event
HTTP POST
Exploring lightweight event sourcing 27
Event Store
Save Event
Create/Update/Delete
Validate input and generate event
HTTP POST
Exploring lightweight event sourcing 27
Event Store
Save Event
Create/Update/Delete
Validate input and generate event
HTTP POST
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Apply Event Presentation Model
Exploring lightweight event sourcing 27
Event Store
Save Event
Create/Update/Delete
Render View
Query
Validate input and generate event
HTTP POST
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Apply Event Presentation Model
Exploring lightweight event sourcing 27
Event Store
Save Event
Create/Update/Delete Read
Render View
Query HTTP GET
Validate input and generate event
HTTP POST
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Apply Event Presentation Model
Exploring lightweight event sourcing
28 post("/todo") { field("text", required) match { case Success(text) => commit(ToDoItemAdded(ToDoItem(UUID.randomUUID(), text))) redirect(url("/todo")) case Failure(error) => new ToDoView(toDoItems, Some(error)), } }
Event Store Save Event Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95 Apply Event Render View Query HTTP GET Validate input and generate event HTTP POSTExploring lightweight event sourcing
29
Event Store Save Event Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95 Apply Event Render View Query HTTP GET Validate input and generate event HTTP POSTcase class ToDoItems ( all : Map[UUID, ToDoItem] = Map.empty, recentlyAdded: Vector[UUID] = Vector.empty) { def apply(event: ToDoItemEvent) = event match { case ToDoItemAdded(item) => copy(all + (item.id -> item), recentlyAdded :+ item.id) // [... handle other event types ...] } def mostRecent(count: Int) = recentlyAdded.takeRight(count).map(all).reverse }
Exploring lightweight event sourcing
30
Event Store Save Event Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95 Apply Event Render View Query HTTP GET Validate input and generate event HTTP POST<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 31
Event Store
Save Event
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Apply Event
Read
Render View
Query HTTP GET
Validate input and generate event
HTTP POST
Exploring lightweight event sourcing 31
Event Store
Save Event
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Apply Event
Command Domain Model
Read
Render View
Query HTTP GET
Validate input and generate event
HTTP POST
Exploring lightweight event sourcing
32
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
33
case class DraftInvoice( recipient: Option[String] = None, nextItemId: Int = 1, items: Map[Int, InvoiceItem] = Map.empty) extends Invoice { def addItem(description: String, amount: BigDecimal): Behavior[DraftInvoice] = ... // [... other domain logic ...] private def itemAdded = when[InvoiceItemAdded] { event => // ... } }
Exploring lightweight event sourcing
33
case class DraftInvoice( recipient: Option[String] = None, nextItemId: Int = 1, items: Map[Int, InvoiceItem] = Map.empty) extends Invoice { def addItem(description: String, amount: BigDecimal): Behavior[DraftInvoice] = ... // [... other domain logic ...] private def itemAdded = when[InvoiceItemAdded] { event => // ... } }
The “Brains”
Exploring lightweight event sourcing
33
case class DraftInvoice( recipient: Option[String] = None, nextItemId: Int = 1, items: Map[Int, InvoiceItem] = Map.empty) extends Invoice { def addItem(description: String, amount: BigDecimal): Behavior[DraftInvoice] = ... // [... other domain logic ...] private def itemAdded = when[InvoiceItemAdded] { event => // ... } }
The “Brains” The “Muscle”
Exploring lightweight event sourcing
34
case class DraftInvoice( recipient: Option[String] = None, nextItemId: Int = 1, items: Map[Int, InvoiceItem] = Map.empty) extends Invoice { // ... def addItem(description: String, amount: BigDecimal): Behavior[DraftInvoice] = itemAdded(InvoiceItemAdded(InvoiceItem(nextItemId, description, amount), totalAmount + amount)) private def totalAmount = items.values.map(_.amount).sum private def readyToSend_? = recipient.isDefined && items.nonEmpty // ... private def itemAdded = when[InvoiceItemAdded] { event => copy(nextItemId = nextItemId + 1, items = items + (event.item.id -> event.item)) } }
Exploring lightweight event sourcing
35
case class DraftInvoice( recipient: Option[String] = None, nextItemId: Int = 1, items: Map[Int, InvoiceItem] = Map.empty) extends Invoice { ... /** Reload from history. */ protected[this] def applyEvent = recipientChanged orElse itemAdded orElse sent ... }
Exploring lightweight event sourcing
Validate input and generate event
HTTP POST
36
Event Store
Save Event
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Apply Event
Create/Update/Delete Read
Render View
Query HTTP GET
Exploring lightweight event sourcing
Validate input and generate event
HTTP POST
36
Event Store
Save Event
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Apply Event
Read
Render View
Query HTTP GET
Conflict Resolution
Submit Event Conflicting Events
Exploring lightweight event sourcing
Validate input and generate event
HTTP POST
36
Event Store
Save Event
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Apply Event
Read
Render View
Query HTTP GET
Conflict Resolution
Submit Event Conflicting Events
Checkout revision 23
Exploring lightweight event sourcing
Validate input and generate event
HTTP POST
36
Event Store
Save Event
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Apply Event
Read
Render View
Query HTTP GET
Conflict Resolution
Submit Event Conflicting Events
Checkout revision 23 Submit events based on revision 23
Exploring lightweight event sourcing
Validate input and generate event
HTTP POST
36
Event Store
Save Event
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Apply Event
Read
Render View
Query HTTP GET
Conflict Resolution
Submit Event Conflicting Events
Checkout revision 23 Submit events based on revision 23 Compare submitted events with intermediate events baed on current revision
Exploring lightweight event sourcing 37
Event Store
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Presentation Model
Exploring lightweight event sourcing 37
Event Store
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Presentation Model
Push
Exploring lightweight event sourcing 37
Event Store
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Presentation Model
Push
Exploring lightweight event sourcing 37
Event Store
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Presentation Model
Push RDBMS
Exploring lightweight event sourcing 37
Event Store
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Presentation Model
Push RDBMS Solr / Neo4J / HBase / ...
Exploring lightweight event sourcing 37
Event Store
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Presentation Model
Push RDBMS Solr / Neo4J / HBase / ... System Integration Order Fullfillment Payment Provider Legacy System
Exploring lightweight event sourcing 37
Event Store
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Presentation Model
Push Replica RDBMS Solr / Neo4J / HBase / ... System Integration Order Fullfillment Payment Provider Legacy System
Exploring lightweight event sourcing 37
Event Store
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Presentation Model
Push Replica RDBMS Solr / Neo4J / HBase / ... System Integration Order Fullfillment Payment Provider Legacy System
Exploring lightweight event sourcing 37
Event Store
Invoice Status: "Draft" Recipient: "Erik" Invoice Item Total: 9.95 Item: "Food" Amount: 9.95Presentation Model
Push Replica RDBMS Solr / Neo4J / HBase / ... System Integration Order Fullfillment Payment Provider Legacy System Auditors
Exploring lightweight event sourcing
38
committed
Exploring lightweight event sourcing
39 private class JournalFileWriter(file: File, var sequence: Long) { private val checksum = new CRC32() private val fileOutputStream = new FileOutputStream(file) private val dataOutputStream = new DataOutputStream( new CheckedOutputStream( new BufferedOutputStream(fileOutputStream), checksum)) def write(commit: Array[Byte]) { sequence += 1 checksum.reset() dataOutputStream.writeLong(sequence) dataOutputStream.writeInt(commit.size) dataOutputStream.write(commit) dataOutputStream.writeInt(checksum.getValue().toInt) } def sync(metadata: Boolean = false) { dataOutputStream.flush() fileOutputStream.getChannel().force(metadata) } }
Exploring lightweight event sourcing
event-sourcing-example
example
blog.zilverline.com
40
Exploring lightweight event sourcing
41
Exploring lightweight event sourcing
SQL, MongoDB, Amazon SimpleDB, etc.
(fixes startup time and memory usage)
boundary with unique identifier)
42
Exploring lightweight event sourcing
model
traditional ORM based approach
43
Exploring lightweight event sourcing
Young, “Unshackle Your Domain” http://www.infoq.com/presentations/greg-young-unshackle-qcon08
http://www.ics.uci.edu/~cs223/papers/cidr07p15.pdf
blog.zilverline.com/2011/02/10/towards-an-immutable-domain-model- monads-part-5/
MemoryImage.html
lmax.html
45