This document discusses developing functional domain models using event sourcing. It covers why event sourcing is useful, how to design domain models and events, and how event sourcing relates to service design. The document discusses using familiar domain-driven design concepts like entities, value objects, aggregates and repositories with event sourcing. It also provides examples of implementing event sourcing in Scala.
Developing functional domain models with event sourcing (sbtb, sbtb2015)
1. @crichardson
Developing functional domain
models with event sourcing
Chris Richardson
Author of POJOs in Action
Founder of the original CloudFoundry.com
Founder of a microservices platform startup
@crichardson
chris@chrisrichardson.net
http://plainoldobjects.com
http://microservices.io
5. @crichardson
About Chris
Founder of a startup that is creating
a platform that makes it easy for
application developers write
microservices
(http://bit.ly/trialeventuate)
10. @crichardson
Today: use a microservice, polyglot
architecture
Banking UI
Account Management Service
MoneyTransfer Management
Service
Account
Database MoneyTransfer Database
Standalone
services
Sharded SQLNoSQL DB
12. @crichardson
Customer management
Maintaining invariants across
service boundaries
Order management
Order Service
placeOrder()
Customer Service
updateCreditLimit()
Customer
creditLimit
...
has ordersbelongs toOrder
total
Invariant:
sum(open order.total) <= customer.creditLimit
?
13. @crichardson
Maintaining invariants with (non-
ACID) NoSQL databases
Non-ACID NoSQL database
Order Service
placeOrder()
Customer Service
updateCreditLimit()
Customer
creditLimit
...
has ordersbelongs toOrder
total
Invariant:
sum(open order.total) <= customer.creditLimit
?
14. @crichardson
Use an event-driven
architecture
Services publish events when state changes
Services subscribe to events and update their state
Maintain eventual consistency across multiple aggregates
(in multiple datastores)
Synchronize replicated data
15. @crichardson
Order Management
Order
id : 4567
total: 343
state = CREATED
Customer Management
Customer
creditLimit : 12000
creditReservations: {}
Customer
creditLimit : 12000
creditReservations: { 4567 -> 343}
Order
id : 4567
total: 343
state = OPEN
Eventually consistent credit checking
Message Bus
createOrder()
Publishes:
Subscribes to:
Subscribes to:
publishes:
OrderCreatedEvent
CreditReservedEvent
OrderCreatedEvent CreditReservedEvent
18. @crichardson
Event sourcing
For each aggregate (aka. business entity):
Identify (state-changing) domain events
Define Event classes
For example,
Account: AccountOpenedEvent, AccountDebitedEvent,
AccountCreditedEvent
ShoppingCart: ItemAddedEvent, ItemRemovedEvent,
OrderPlacedEvent
19. @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
20. @crichardson
Replay events to recreate
state
Account
balance
AccountOpenedEvent(balance)
AccountDebitedEvent(amount)
AccountCreditedEvent(amount)
Events
Periodically snapshot to avoid loading all events
23. @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)
24. @crichardson
Event Store publishes events -
consumed by other services
Event
Store
Event
Subscriber
subscribe(EventTypes)
publish(event)
publish(event)
Aggregate
CQRS View
update()
update()
Microservice B
send notifications
…
25. @crichardson
Benefits of event sourcing
Solves data consistency issues in a Microservice/NoSQL-based
architecture
Reliable event publishing: publishes events needed by predictive
analytics etc, user notifications,…
Eliminates O/R mapping problem (mostly)
Reifies state changes:
Built-in, reliable audit log,
temporal queries
Preserved history More easily implement future requirements
26. @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 => use
Command Query Responsibility Segregation (CQRS) to handle
queries
29. @crichardson
Partition the domain model
into Aggregates
Order
OrderLine
Item
quantity
…
Address
street
city
…
Customer
Product
name
price
30. Aggregate design
Graph consisting of a root
entity and one or more other
entities and value objects
Each core business entity =
Aggregate: e.g. customer,
Account, Order, Product, ….
Reference other aggregate
roots via primary key
Often contains partial copy
of other aggregates’ data
Order
OrderLine
Item
quantity
productId
productName
productPrice
customerId
Address
street
city
…
32. @crichardson
Aggregate granularity is
important
Transaction = processing one command by one aggregate
No opportunity to update multiple aggregates within a
transaction
If an update must be atomic (i.e. no compensating
transaction) then it must be handled by a single aggregate
e.g. scanning boarding pass at security checkpoint or when
entering jetway
34. Designing domain events
Records state changes for an aggregate
Records key “business events”
Part of the public API of the domain
model ProductAddedToCart
id : TimeUUID
productId
productName
productPrice
shoppingCartId
Required by
aggregate
Enrichment:
Required by
consumers
35. @crichardson
Designing commands
Created by a service from incoming request
Processed by an aggregate
Immutable
Contains value objects for
Validating request
Creating event
Auditing user activity
37. @crichardson
OO = State + Behavior
creditLimit
creditReservations
Customer
processCommand :
PartialFunction[Command, Events]
applyEvent :
PartialFunction[Event, Account]
State
Behavior
38. @crichardson
Aggregate traits
Map Command to Events
Apply event returning
updated Aggregate
Used by
Event Store
to
reconstitute
aggregate
Event types are not
precisely typed :-(
43. @crichardson
A detour to Haskell
http://www.meetup.com/oakland-scala/
Oakland Advanced Scala Study Group
https://www.flickr.com/photos/georgikeith/2658949664
48. @crichardson
“This section briefly explains GHC Haskell’s associated types
and shows in detail how they can be encoded in Scala using
type members …”
49. @crichardson
FP = Separation of State and
Behavior
Customer
creditLimit
…
CustomerAggregate
processCommand(Account, Command) :
Seq[Events]
applyEvent(Account, Event) : Account
State Behavior
58. @crichardson
Place Order example
As a customer
I want to place an order
So that I get the needed products
Given that my available credit is $1500
When I place a $250 order
Then the order is created
Then my available credit is $1250
Story
Scenario
Post
conditions
Pre
conditions
60. … becomes eventually
consistent (BASE)
Updating multiple aggregates
multi-step, event-driven flow
each step updates one
Aggregate
Service creates saga to coordinate
workflow
A state machine
Part of the domain, e.g. Order
aggregate OR Synthetic
aggregate
Post-conditions eventually true
Order
Customer
Created
Credit reserved
public Order createOrder() {
… Creates Order …
}
Approved
62. @crichardson
Creating an order
DSL concisely specifies:
1.Creates Customer aggregate
2.Processes command
3.Applies events
4.Persists events
63. @crichardson
Event handling in Account
Durable subscription name
Triggers BeanPostProcessor
1.Load Customer aggregate
2.Processes command
3.Applies events
4.Persists events
64. @crichardson
Summary
Event sourcing solves a variety of problems in modern
application architectures
ES-based architecture = choice of monolith or microservices
Scala is a great language for implementing ES-based domain
models:
Case classes
Pattern matching
Recreating state = functional fold over events