Implementing CQRS on top of OTP
(in Elixir)
Hubert Łępicki
Elixir Warsaw 2016-06-09
CQRS?
- Command Query Responsibility Segregation
- design pattern
- architectural pattern
- Greg Young
Achtung!
You probably don't need to do CQRS
...at least not for whole application.
We have implemented CQRS in Elixir
and I'll show you how
Commands & Queries
- Command - introduce change of state
- Query - read state
- Commands & Queries are API of your application
CQRS & DDD
- Event Sourcing
- Domain Model
- Ubiquitous Language
- Bounded Contexts
- Aggregates
Building blocks
- Commands & Queries
- Streams of Events
- Aggregates
- Event Handlers
- Read Model
Life cycle of a Command
- Arrives from the user (from the UI/API etc.)
- It's payload is validated
- It's aggregate is initialized (unless already present)
- Is being routed to aggregate
- Business logic is validated
- One or more Events are appended to aggregate's events stream
Life cycle of a Query
- Arrives from the user (from the UI/API etc.)
- It's payload is validated
- Queries Read Model
- Assembles response
Life cycle of an Aggregate
- Is initialized with stream of past events
- Restores it's current state
- Waits for command
- Processes the command (generates events)
- Persists events
- Handles the events (updating internal state, read model, notifying external systems)
Events are the source of truth
Events populate aggregate's state
Command's business logic runs within aggregate
New events are appended to the stream and update state
Aggregates are pretty important!
Aggregate in CQRS
- defines bounded context
- provides application-level transactions
- contains current state of the system
An example!
User's wallets system
Our aggregates
- Wallet for User #1
- Wallet for User #2
- Wallet for User #3
Wallet's state
- label i.e. "Main wallet"
- balance i.e. 100
- blocked funds [{txId, amount}, ...]
Commands
- %RenameWallet{name: "Savings"}
- %TopUp{amount: 100}
- %AuthorizeFunds{amount: 10, txId: 123}
- %CaptureFunds{amount: 8, txId: 123}
Wallet Events
- {WalletRenamed, %{name: "Savings"}}
- {WalletToppedUp, %{from: 0, to: 100}}
- {FundsAuthorized, %{amount: 10, txId: 10}}
- {FundsCaptured, %{amount: 8, txId: 10}}
Aggregate the OTP way
GenServer
Supervisor for Aggregates
- Dynamically adds/removes children
- :simple_one_for_one
- Registers under aggregate type (i.e. Wallet)
defmodule CqrsExample do
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
supervisor(AggregateSupervisor, [[name: Wallets]])
supervisor(AggregateSupervisor, [[name: Accounts]])
supervisor(AggregateSupervisor, [[name: Products]])
supervisor(AggregateSupervisor, [[name: Services]])
]
opts = [strategy: :one_for_one, name: CqrsExample.Supervisor]
Supervisor.start_link(children, opts)
end
end
defmodule AggregateSupervisor do
def start_link([name: aggregate_name]) do
import Supervisor.Spec
children = [
worker(aggregate_for_name(aggregate_name), [])
]
Supervisor.start_link(
children,
[strategy: :simple_one_for_one, name: aggregate_name]
)
end
def get_aggregate(name, id) do
...
end
defp aggregate_for_name(name) do
...
end
end
CQRS does not require objects
CQRS is all about the proper handling of the state
Make your aggregates GenServers
Don't choose CQRS because you think it's cool
Moar resources
- https://vimeo.com/97318824
- https://github.com/bryanhunter/cqrs-with-erlang/tree/ndc-oslo
- http://jfcloutier.github.io/jekyll/update/2015/11/04/cqrs_elixir_phoenix.html
Thank you!
Questions?
Implementing CQRS in Elixir
By Hubert Łępicki
Implementing CQRS in Elixir
- 4,772