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
  • 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