Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Developing functional domain models with event sourcing (oakjug, sfscala)


Published on

Event sourcing persists each entity as a sequence of state changing events. An entity’s current state is derived by replaying those events. Event sourcing is a great way to implement event-driven microservices. When one service updates an entity, the new events are consumed by other services, which then update their own state.

In this talk we describe how to implement business logic using event sourcing. You will learn how to write functional, immutable domain models in Scala. We will compare and contrast a hybrid OO/FP design with a purely functional approach.

Published in: Software

Developing functional domain models with event sourcing (oakjug, sfscala)

  1. 1. @crichardson Developing functional domain models with event sourcing Chris Richardson Author of POJOs in Action Founder of the original @crichardson
  2. 2. @crichardson Presentation goal How to develop functional domain models based on event sourcing
  3. 3. @crichardson About Chris Consultant Startup Founder
  4. 4. @crichardson For more information
  5. 5. @crichardson Agenda Why event-sourcing? Developing functional domain models with event sourcing
  6. 6. @crichardson Money Transfer example from 2007
  7. 7. An example domain model debit(amount) credit(amount) balance Account amount date Banking Transaction BankingTransaction transfer(fromId, toId, amount) MoneyTransferService findAccount(id) Account Repository <<interface>> OverdraftPolicy addTransaction(…) BankingTransaction Repository NoOverdraft Policy limit Limited Overdraft from to State + Behavior Behavior
  8. 8. Mutable domain objects Entities persisted in RDBMS using Hibernate
  9. 9. Service invocation = transaction Transactional using Spring
  10. 10. @crichardson But today we apply the scale cube X axis - horizontal duplication Z axis -data partitioning Y axis - functional decomposition Scale by splitting sim ilar things Scale by splitting different things
  11. 11. @crichardson Applying the scale cube Y-axis splits/functional decomposition Application = Set[Microservice] - each with its own database Monolithic database is functionally decomposed Z-axis splits/sharding Accounts partitioned across multiple databases
  12. 12. @crichardson Account MoneyTransfer Account Money transfer DB Account DB1 Account DB2 to from How to maintain consistency without 2PC?
  13. 13. @crichardson SQL + Text Search engine example Application MySQL ElasticSearch How to maintain consistency without 2PC? Product #1 Product #1
  14. 14. @crichardson Use an event-driven architecture Components (e.g. services) publish events when state changes Components subscribe to events Maintains eventual consistency across multiple aggregates (in multiple datastores) Synchronize replicated data But how to atomically update state AND publish events without 2PC?
  15. 15. @crichardson Event sourcing For each aggregate: Identify (state-changing) domain events Define Event classes For example, Account: AccountOpenedEvent, AccountDebitedEvent, AccountCreditedEvent ShoppingCart: ItemAddedEvent, ItemRemovedEvent, OrderPlacedEvent
  16. 16. @crichardson Persists events NOT current state Account balance open(initial) debit(amount) credit(amount) AccountOpened Event table AccountCredited AccountDebited 101 450 Account table X 101 101 101 901 902 903 500 250 300
  17. 17. @crichardson Replay events to recreate state Account balance AccountOpenedEvent(balance) AccountDebitedEvent(amount) AccountCreditedEvent(amount) Events
  18. 18. @crichardson Before: update state + publish events Two actions that must be atomic Single action that can be done atomically Now: persist (and publish) events
  19. 19. @crichardson Request handling in an event-sourced application HTTP Handler Event Store pastEvents = findEvents(entityId) Account new() applyEvents(pastEvents) newEvents = processCmd(SomeCmd) saveEvents(newEvents) Microservice A (optimistic locking)
  20. 20. @crichardson Event Store publishes events - consumed by other services Event Store Event Subscriber subscribe(EventTypes) publish(event) publish(event) Aggregate NoSQL materialized view update() update() Microservice B
  21. 21. @crichardson Event store implementations Home-grown/DIY by Greg Young Talk to me about my project :-)
  22. 22. @crichardson Business benefits of event sourcing Built-in, reliable audit log Enables temporal queries Publishes events needed by big data/predictive analytics etc. Preserved history More easily implement future requirements
  23. 23. @crichardson Technical benefits of event sourcing Solves data consistency issues in a Microservice/NoSQL- based architecture: Atomically save and publish events Event subscribers update other aggregates ensuring eventual consistency Event subscribers update materialized views in SQL and NoSQL databases (more on that later) Eliminates O/R mapping problem
  24. 24. @crichardson Drawbacks of event sourcing Weird and unfamiliar Events = a historical record of your bad design decisions Handling duplicate events can be tricky Application must handle eventually consistent data Event store only directly supports PK-based lookup (more on that later)
  25. 25. @crichardson Agenda Why event-sourcing? Developing functional domain models with event sourcing
  26. 26. @crichardson Familiar building blocks Entity Value object Aggregate: root entity + value objects
  27. 27. @crichardson Aggregate root design class Account { var balance : Money; def debit(amount : Money) { balance = balance - amount } } case class Account(balance : Money) { def processCommand(cmd : Command) : Seq[Event] = ??? def applyEvent(event : Event) : Account = … } case class DebitCommand(amount : Money) case class AccountDebitedEvent(amount : Money) Classic, mutable domain model Event centric, immutable
  28. 28. @crichardson Designing domain events Naming Past tense to reflect that something occurred Ideally specific: AccountOpened/Debited/Credited Sometimes vague: FooUpdated Event attributes Id - TimeUUID Other attributes - from command, required to persist entity Event enrichment ProductAddedToCart(productId) vs. ProductAddedCart(productInfo) Extra data to support event consumers
  29. 29. @crichardson Hybrid OO/FP domain objects
  30. 30. @crichardson Aggregate traits Map Command to Events Apply event returning updated Aggregate
  31. 31. @crichardson Account - command processing Prevent overdraft
  32. 32. @crichardson Account - applying events Immutable
  33. 33. @crichardson Event Store API trait EventStore { def save[T <: Aggregate[T]](entity: T, events: Seq[Event], assignedId : Option[EntityId] = None): Future[EntityWithIdAndVersion[T]] def update[T <: Aggregate[T]](entityIdAndVersion : EntityIdAndVersion, entity: T, events: Seq[Event]): Future[EntityWithIdAndVersion[T]] def find[T <: Aggregate[T] : ClassTag](entityId: EntityId) : Future[EntityWithIdAndVersion[T]] def findOptional[T <: Aggregate[T] : ClassTag](entityId: EntityId) Future[Option[EntityWithIdAndVersion[T]]] def subscribe(subscriptionId: SubscriptionId): Future[AcknowledgableEventStream] }
  34. 34. @crichardson AccountService DSL concisely specifies: 1.Creates Account aggregate 2.Processes command 3.Applies events 4.Persists events
  35. 35. @crichardson Event handling 1.Load Account aggregate 2.Processes command 3.Applies events 4.Persists events
  36. 36. @crichardson FP-style domain objects
  37. 37. @crichardson Aggregate type classes/implicits
  38. 38. @crichardson Functional-style MoneyTransfer Aggregate State Behavior
  39. 39. @crichardson Events and Commands
  40. 40. @crichardson FP-style event store Enables inference of T, and EV Tells ES how to instantiate aggregate and apply events
  41. 41. @crichardson Summary Event sourcing solves a variety of problems in modern application architectures Scala is a great language for implementing domain models: Case classes Pattern matching Recreating state = functional fold over events
  42. 42. @crichardson @crichardson