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.
Develop functional domain models with event sourcing
1. @crichardson
Developing functional domain
models with event sourcing
Chris Richardson
Author of POJOs in Action
Founder of the original CloudFoundry.com
@crichardson
chris@chrisrichardson.net
http://plainoldobjects.com
http://microservices.io
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. @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
13. @crichardson
SQL + Text Search engine
example
Application
MySQL ElasticSearch
How to maintain consistency without 2PC?
Product #1 Product #1
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. @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. @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. @crichardson
Replay events to recreate
state
Account
balance
AccountOpenedEvent(balance)
AccountDebitedEvent(amount)
AccountCreditedEvent(amount)
Events
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. @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. @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
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. @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. @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)
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. @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
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