exploring lightweight event sourcing
play

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


  1. Reloading from history Draft Invoice Invoice Recipient Created Changed Recipient: "Erik" apply apply Status: Status: Recipient: "Draft" "Draft" "Erik" Total: Total: Invoice Invoice 0.00 0.00 Exploring lightweight event sourcing 21

  2. Reloading from history Draft Invoice Invoice Recipient Invoice Item Created Changed Added Recipient: "Erik" Item: "Food" Item amount: 9.95 Total amount: 9.95 apply apply apply Status: Status: Recipient: Status: Recipient: "Draft" "Draft" "Erik" "Draft" "Erik" Total: Total: Total: Invoice Invoice Invoice 0.00 0.00 9.95 Item: Invoice Item "Food" Amount: 9.95 Exploring lightweight event sourcing 21

  3. Reloading from history Draft Invoice Invoice Recipient Invoice Item Created Changed Added Recipient: "Erik" Item: "Food" Item amount: 9.95 Total amount: 9.95 apply apply apply Status: Status: Recipient: Status: Recipient: "Draft" "Draft" "Erik" "Draft" "Erik" Total: Total: Total: Invoice Invoice Invoice 0.00 0.00 9.95 Item: Invoice Item "Food" Amount: 9.95 Exploring lightweight event sourcing 21

  4. YAGNI? Exploring lightweight event sourcing 22

  5. YAGNI? • On the checkout page we’d like to promote products that customers previously removed from their shopping cart. Exploring lightweight event sourcing 22

  6. 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 22

  7. 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 22

  8. 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 23

  9. Is it worth it? Exploring lightweight event sourcing 24

  10. Is it worth it? • Make the event sourcing implementation as simple as possible Exploring lightweight event sourcing 24

  11. 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 24

  12. 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 24

  13. Implementation Exploring lightweight event sourcing 25

  14. Implementation • Events for durability Exploring lightweight event sourcing 25

  15. Implementation • Events for durability • Keep current state in RAM (Memory Image) Exploring lightweight event sourcing 25

  16. Implementation • Events for durability • Keep current state in RAM (Memory Image) • Scala case classes to define events and immutable data structures Exploring lightweight event sourcing 25

  17. Implementation • Events for durability • Keep current state in RAM (Memory Image) • Scala case classes to define events and immutable data structures • Independent components composed into a single application Exploring lightweight event sourcing 25

  18. Simple functionality example • “CRUD”: no domain logic, so no aggregate • So we’ll persist events directly from the UI • Useful to get started • ... and even complex applications still contain simple functionality Exploring lightweight event sourcing 26

  19. “CRUD” Exploring lightweight event sourcing 27

  20. “CRUD” Create/Update/Delete Exploring lightweight event sourcing 27

  21. “CRUD” Create/Update/Delete HTTP POST Validate input and generate event Exploring lightweight event sourcing 27

  22. “CRUD” Create/Update/Delete Save Event HTTP POST Validate input and Event generate event Store Exploring lightweight event sourcing 27

  23. “CRUD” Create/Update/Delete Save Event HTTP POST Validate input and Event generate event Store Apply Event Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 Item: Invoice Item "Food" Amount: 9.95 Presentation Model Exploring lightweight event sourcing 27

  24. “CRUD” Create/Update/Delete Save Event HTTP POST 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 Presentation Model Exploring lightweight event sourcing 27

  25. “CRUD” Create/Update/Delete Save Event HTTP POST Validate input and Event generate event Store Apply Event Read Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 HTTP GET Query Render View Item: Invoice Item "Food" Amount: 9.95 Presentation Model Exploring lightweight event sourcing 27

  26. 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") { field("text", required) match { case Success(text) => commit(ToDoItemAdded(ToDoItem(UUID.randomUUID(), text))) redirect(url("/todo")) case Failure(error) => new ToDoView(toDoItems, Some(error)), } } Exploring lightweight event sourcing 28

  27. 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[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 29

  28. 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 30

  29. Domain Driven Design Save Event HTTP POST Validate input and Event generate event Store Apply Event Read Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 HTTP GET Query Render View Item: Invoice Item "Food" Amount: 9.95 Exploring lightweight event sourcing 31

  30. Domain Driven Design Domain Model Command Save Event HTTP POST Validate input and Event generate event Store Apply Event Read Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 HTTP GET Query Render View Item: Invoice Item "Food" Amount: 9.95 Exploring lightweight event sourcing 31

  31. 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 32

  32. Domain Code 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

  33. Domain Code case class DraftInvoice( recipient: Option[String] = None, The “Brains” 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

  34. Domain Code case class DraftInvoice( recipient: Option[String] = None, The “Brains” nextItemId: Int = 1, items: Map[Int, InvoiceItem] = Map.empty) extends Invoice { def addItem(description: String, amount: BigDecimal): Behavior[DraftInvoice] = ... // [... other domain logic ...] The “Muscle” private def itemAdded = when[InvoiceItemAdded] { event => // ... } } Exploring lightweight event sourcing 33

  35. Domain Code 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 34

  36. Domain Code 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 35

  37. Cooperating Users Create/Update/Delete Save Event HTTP POST Validate input and Event generate event Store Apply Event Read Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 HTTP GET Query Render View Item: Invoice Item "Food" Amount: 9.95 Exploring lightweight event sourcing 36

  38. Cooperating Users Submit Event Save Event HTTP POST Validate input and Event Conflict Resolution generate event Store Conflicting Events Apply Event Read Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 HTTP GET Query Render View Item: Invoice Item "Food" Amount: 9.95 Exploring lightweight event sourcing 36

  39. Cooperating Users Submit Event Save Event HTTP POST Validate input and Event Conflict Resolution generate event Store Conflicting Events Apply Event Read Checkout revision 23 Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 HTTP GET Query Render View Item: Invoice Item "Food" Amount: 9.95 Exploring lightweight event sourcing 36

  40. Cooperating Users Submit events based on revision 23 Submit Event Save Event HTTP POST Validate input and Event Conflict Resolution generate event Store Conflicting Events Apply Event Read Checkout revision 23 Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 HTTP GET Query Render View Item: Invoice Item "Food" Amount: 9.95 Exploring lightweight event sourcing 36

  41. Cooperating Users Compare submitted events Submit events with intermediate based on revision 23 events baed on current revision Submit Event Save Event HTTP POST Validate input and Event Conflict Resolution generate event Store Conflicting Events Apply Event Read Checkout revision 23 Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 HTTP GET Query Render View Item: Invoice Item "Food" Amount: 9.95 Exploring lightweight event sourcing 36

  42. Events Event Store Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 Item: Invoice Item "Food" Amount: 9.95 Presentation Model Exploring lightweight event sourcing 37

  43. Events Event Store Push Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 Item: Invoice Item "Food" Amount: 9.95 Presentation Model Exploring lightweight event sourcing 37

  44. Events Event Store Push Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 Item: Invoice Item "Food" Amount: 9.95 Presentation Model Exploring lightweight event sourcing 37

  45. Events Event Store Push Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 Item: RDBMS Invoice Item "Food" Amount: 9.95 Presentation Model Exploring lightweight event sourcing 37

  46. Events Event Store Push Status: Recipient: "Draft" "Erik" Invoice Total: 9.95 Item: RDBMS Invoice Item Solr / Neo4J / "Food" HBase / ... Amount: 9.95 Presentation Model Exploring lightweight event sourcing 37

  47. Events Event Store System Push Integration Order Fullfillment Payment Provider Status: Recipient: Legacy System "Draft" "Erik" Invoice Total: 9.95 Item: RDBMS Invoice Item Solr / Neo4J / "Food" HBase / ... Amount: 9.95 Presentation Model Exploring lightweight event sourcing 37

  48. Events Replica Event Store System Push Integration Order Fullfillment Payment Provider Status: Recipient: Legacy System "Draft" "Erik" Invoice Total: 9.95 Item: RDBMS Invoice Item Solr / Neo4J / "Food" HBase / ... Amount: 9.95 Presentation Model Exploring lightweight event sourcing 37

  49. Events Replica Event Store System Push Integration Order Fullfillment Payment Provider Status: Recipient: Legacy System "Draft" "Erik" Invoice Total: 9.95 Item: RDBMS Invoice Item Solr / Neo4J / "Food" HBase / ... Amount: 9.95 Presentation Model Exploring lightweight event sourcing 37

  50. Events Auditors Replica Event Store System Push Integration Order Fullfillment Payment Provider Status: Recipient: Legacy System "Draft" "Erik" Invoice Total: 9.95 Item: RDBMS Invoice Item Solr / Neo4J / "Food" HBase / ... Amount: 9.95 Presentation Model Exploring lightweight event sourcing 37

Recommend


More recommend