Event Sourcing &
CQRS
Problems with
database driven applications
- Database-centric (all requests hits the database for read or write)
- Functionality coupled together that's not really related
- e.g.
user registered -> put in database, send welcome email
- e.g.
- Scale -> scale the database
Command Query Responsibility Segregation
Event Sourcing
- Reason about user intent, not updating data.
- Domain Events are the immutable source of truth
- Business language, userRegistered, depositedMoney
- State in the application is just a reflection of aggregated events
Command Query Responsibility Segregation
- Commands and Queries are separated (write and reads)
Event Sourcing
Why
- Time travel
- Audit log
- No bottlnecks (keep things in memory)
- Testing: feed your application events, look at the resulting state
- Incredibly scaleable
Why not
- Two models, one for read, one for write
- More "things" to think about, but the "things" will be loosely coupled
- Events are immutable, have to be handled
- snapshotting
POST
API
GET
POST
API
GET
Aggregate
EventHandler
Read model
EventStore
Register for changes
Process / company policy
Command
Domain Event
Aggregate
Actor
Read Model
Event Storming
External
System
Process / company policy
Deposit money
Money deposited
Bank account
Account owner
Account-overview view
External
System
Command
Read Model
Aggregate
Domain Event
Actor
defmodule BankAccount do
def execute(%BankAccount{} = account, %DepositMoney{amount: amount}) do
%MoneyDeposited{
account_number: account.account_number,
amount: amount,
balance: account.balance + amount
}
end
def apply(%BankAccount{} = state, %MoneyDeposited{balance: balance}) do
%BankAccount{state | balance: balance}
end
end
Deposit money
Money deposited
Bank account
Aggregate
defmodule BankAccount do
def execute(%BankAccount{} = account, %WithdrawMoney{amount: amount}) do
if amount > account.balance do
return {:error, :can_not_overdraw_account}
end
%MoneyWithdrawn{
account_number: account.account_number,
amount: amount,
balance: balance - amount
}
end
def apply(%BankAccount{} = state, %MoneyWithdrawn{balance: balance}) do
%BankAccount{state | balance: balance}
end
end
Deposit money
Money deposited
Bank account
Aggregate
defmodule ThankyouEmailHandler do
def handle(%DepositedMoney{account_id: account_id, amount: amount}, _metadata) do
Email.sendThankyouEmail(account_id, amount)
:ok
end
end
External
System
Event handler
if handler doesn't ack - the eventstore will send another event
defmodule CEOBonusHandler do
def handle(%MoneyInBonusAccount{} = bonus_account, %DepositedMoney{} = event) do
if (bonus_account.amount < 100_000 && bonus_account.account_id != event.account_id) do
%DepositMoney{
id: bonus_account.account_id
amount: event.amount / 100
}
end
end
end
Event handler
Takes in an Event, sends out a command
Process / company policy
Process / company policy
Deposit money
Money deposited
Bank account
Account owner
Account-overview view
External
System
Command
Read Model
Aggregate
Domain Event
Actor
Thank you Email
Further reading
- https://cqrs.nu/Faq
- https://www.youtube.com/watch?v=3R3qeMr_ANE
- https://martinfowler.com/eaaDev/EventSourcing.html
Event Sourcing and CQRS
By Mikael Gråborg
Event Sourcing and CQRS
- 171